This is the Patch
method of my OdataController
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> patch)
{
Validate(patch.GetInstance());
Product product = await service.Products.GetAsync(key);
if (product == null)
return NotFound();
patch.Put(product);
try
{
await service.Products.UpdateAsync(product);
}
catch (DbUpdateConcurrencyException)
{
if (!await service.Products.ExistAsync(key))
return NotFound();
else
throw;
}
return Updated(product);
}
My model has a property:
[Timestamp]
public byte[] RowVersion { get; set; }
the DbUpdateConcurrencyException
seems not working at all.
I need to implement concurrency checking mechanism using Etag.
I have seen some examples here.But they are not using Delta in there method.
Something Like:
[CustomConcurrencyCheck]
public async Task<IHttpActionResult> Put([FromODataUri] int key, Delta<Product> patch)
{
...
}
Providing a simple example will be highly appreciated.
First in WebApiConfig
while creating your model you have to specify which property is the ETag, in your case:
var builder = new ODataConventionModelBuilder();
builder.EntityType<Product>()
.Property(p => p.RowVersion)
.IsConcurrencyToken();
Later you can retrieve the ETag from the ODataQueryOptions<T>
parameter of your Patch
method in the controller:
[AcceptVerbs("PATCH", "MERGE")]
public IHttpActionResult Patch([FromODataUri] int key, Delta<Product> delta, ODataQueryOptions<Product> options) {
var existingEntity = //Code to get existing entity by ID, 404 if not found
byte[] requestETag = options.IfMatch["RowVersion"] as byte[];
if(!requestETag.SequanceEqual(existingEntity.RowVersion)) { //Simplified if-statement, also do null-checks and such
// ETags don't match, return HTTP 412
return StatusCode(HttpStatusCode.PreconditionFailed);
} else {
// The ETags match, implement code here to update your entity
// You can use the 'Delta<Product> delta' parameter to get the changes and use the 'Validate' function here
// ...
This is the solution I use, it is a simple check to see if the client which requests the update has the same version of the object the service has. A notable drawback of my solution is that I have to retrieve the existing object from the DB to get it to work, that costs some performance.
This is code for the If-Match
header, ODataQueryOptions<T>
also has .IfNoneMatch[VersionColumnName]
available. Which you can use in your Get
method. If the If-None-Match
header equals your RowVersion
you can return a HTTP 304 (Not modified)
and save some bandwidth.
This is a very simple example, if you want to implement your own custom attribute that's up to you. At the very least I would move some of this logic to a helper class so it can be reused.