Skip to content

Instantly share code, notes, and snippets.

@howarddierking
Created July 1, 2011 23:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save howarddierking/1059599 to your computer and use it in GitHub Desktop.
Save howarddierking/1059599 to your computer and use it in GitHub Desktop.
sample message handler for managing etags in web api
public class EtagMessageHandler : DelegatingChannel
{
readonly IETagStore _eTagStore;
public EtagMessageHandler(HttpMessageChannel innerChannel) : base(innerChannel) {
Trace.WriteLine("EtagMessageHandler - Ctor");
_eTagStore = new InMemoryETagStore();
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) {
Trace.WriteLine("EtagMessageHandler - SendAsync");
var taskFactory = new TaskFactory<HttpResponseMessage>();
if (request.Method == HttpMethod.Get) {
//if this is not a conditional get
if (request.Headers.IfNoneMatch.Count == 0)
return base.SendAsync(request, cancellationToken).ContinueWith(task => {
Trace.WriteLine("EtagMessageHandler - Task - Not a conditional GET");
var resp = task.Result;
resp.Headers.ETag = new EntityTagHeaderValue(_eTagStore.Fetch(request.RequestUri));
return resp;
});
//if this is not modified, stop processing the message and return a 304; make sure the etag is also set
if (IfNoneMatchContainsStoredEtagValue(request))
return taskFactory.StartNew(() =>
{
Trace.WriteLine("EtagMessageHandler - Task - Conditional GET match");
var resp = new HttpResponseMessage(HttpStatusCode.NotModified, "Not Modified");
resp.Headers.ETag = new EntityTagHeaderValue(_eTagStore.Fetch(request.RequestUri));
return resp;
});
//if this resource has been updated, let the request proceed to the operation and set a new etag on the return
return base.SendAsync(request, cancellationToken).ContinueWith(task => {
Trace.WriteLine("EtagMessageHandler - Task - Conditional GET not matched");
var resp = task.Result;
resp.Headers.ETag = new EntityTagHeaderValue(_eTagStore.Fetch(request.RequestUri));
return resp;
});
}
if (request.Method == HttpMethod.Put || request.Method == HttpMethod.Post)
//let the request processing continue; new etag value for resource; update response header
if (IfMatchContainsStoredEtagValue(request))
return base.SendAsync(request, cancellationToken).ContinueWith(task => {
Trace.WriteLine("EtagMessageHandler - Task - Conditional update matched");
var resp = task.Result;
resp.Headers.ETag = new EntityTagHeaderValue(_eTagStore.UpdateETagFor(request.RequestUri));
return resp;
});
//stop processing and return a 412/precondition failed; update response header
else
return taskFactory.StartNew(() => {
Trace.WriteLine("EtagMessageHandler - Task - Conditional update not matched");
var resp = new HttpResponseMessage(HttpStatusCode.PreconditionFailed, "Precondition Failed");
resp.Headers.ETag = new EntityTagHeaderValue(_eTagStore.Fetch(request.RequestUri));
return resp;
});
//by default, let the request keep moving up the message handler stack
return base.SendAsync(request, cancellationToken);
}
bool IfNoneMatchContainsStoredEtagValue(HttpRequestMessage request) {
//we're going to assume that this handler will only care about 1 entity tag value for the specified resource
if (request.Headers.IfNoneMatch.Count == 0)
return false;
return request.Headers.IfNoneMatch.Select(v => v.Tag).Contains(_eTagStore.Fetch(request.RequestUri));
}
bool IfMatchContainsStoredEtagValue(HttpRequestMessage request) {
if (request.Headers.IfMatch.Count == 0)
return false;
return request.Headers.IfMatch.Select(v => v.Tag).Contains(_eTagStore.Fetch(request.RequestUri));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment