Click here to Skip to main content
15,881,882 members
Articles / Web Development / ASP.NET / ASP.NET Core

Handling Concurrency in ASP.NET Core 2.0 Web API

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
5 Sep 2017CPOL1 min read 15.6K   7   3
How to handle concurrency in ASP.NET Core Web API. Continue reading...

Problem

This post discusses how to handle concurrency in ASP.NET Core Web API.

Solution

Create an empty project and update Startup class to add services and middleware for MVC:

C#
public void ConfigureServices(
	IServiceCollection services)
{
    services.AddMvc();
}

public void Configure(
            IApplicationBuilder app, 
            IHostingEnvironment env)
{
    app.UseMvcWithDefaultRoute();
}

Add a controller with GET and PUT, to demonstrate concurrency:

C#
[Route("movies")]
public class MoviesController : Controller
{
    const string ETAG_HEADER = "ETag";
    const string MATCH_HEADER = "If-Match";

    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        var model_from_db = new Movie
        {
            Id = 1,
            Title= "Thunderball",
            ReleaseYear = 1965,
        };

        var eTag = HashFactory.GetHash(model_from_db);
        HttpContext.Response.Headers.Add(ETAG_HEADER, eTag);

        if (HttpContext.Request.Headers.ContainsKey(MATCH_HEADER) &&
            HttpContext.Request.Headers[MATCH_HEADER].RemoveQuotes() == eTag)
            return new StatusCodeResult(StatusCodes.Status304NotModified);

        return Ok(model_from_db);
    }

    [HttpPut("{id}")]
    public IActionResult Put(int id, [FromBody]Movie model)
    {
        var model_from_db = new Movie
        {
            Id = 1,
            Title = "Thunderball-changed", // data changed
            ReleaseYear = 1965,
        };

        var eTag = HashFactory.GetHash(model_from_db);
        HttpContext.Response.Headers.Add(ETAG_HEADER, eTag);

        if (!HttpContext.Request.Headers.ContainsKey(MATCH_HEADER) ||
            HttpContext.Request.Headers[MATCH_HEADER].RemoveQuotes() != eTag)
        {
            return new StatusCodeResult(StatusCodes.Status412PreconditionFailed);
        }
        else
        {
            // saving should be OK
        }

        return NoContent();
    }
}

Send a GET request and observe ETag header (using Postman):

Image 1

Now using this ETag value, send a PUT request:

Image 2

Discussion

We want to ensure that users are not overriding changes made by other users between the time they retrieved the data and submit their changes.

At its core, implementing such concurrency is a simple two-step process:

GET

We add a magic value to the response based on data we hold at the time. Usually ETag header is added for this purpose containing hashed value based on data/body of response.

Clients send this ETag value in subsequent requests as If-Match header, which we compare against ETag produced for current data and if unchanged, send a 304 (Not Modified) response.

PUT

We compare against ETag produced for current data and if changed, send a 412 (Precondition Failed) response.

Other Methods

Using hashed values of model representation is not the only way to implement concurrency. Entity Framework also provides a mechanism to implement concurrency. I’ll discuss in a later post on EF Core.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
QuestionUnable to use HashFactory Pin
Ann_Swap8-Jan-20 1:17
Ann_Swap8-Jan-20 1:17 
QuestionIf-Match not If-None-Match and quoted eTags Pin
acw4-Sep-17 23:24
acw4-Sep-17 23:24 
AnswerRe: If-Match not If-None-Match and quoted eTags Pin
User 10432645-Sep-17 2:04
User 10432645-Sep-17 2:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.