Skip to content

Instantly share code, notes, and snippets.

@daniiiol
Created May 23, 2014 07:55
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 daniiiol/88724069d29537b11fcd to your computer and use it in GitHub Desktop.
Save daniiiol/88724069d29537b11fcd to your computer and use it in GitHub Desktop.
namespace Dani.Samples.Internet.WebApi.Filters.Actions
{
using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
// Proof of Concept !!!
// ------------------------------
// Test ob ETags für OData-Queries berücksichtigt werden kann
// TODO: 1 - Aus dem Proc-Modus in eine DB-Variante wechseln
// TODO: 2 - Allgemein E-Tag kann als Message-Handler bei der WebAPI eingebunden werden und nicht als ActionFilter
// TODO: 3 - Ressourcen-Invalidierung als ActionFilter sauber erstellen (rudimentär hier mit Param der DependendenyKeys)
public class EnableETag : ActionFilterAttribute
{
//This could be from a network source e.g database
private static readonly ConcurrentDictionary<string, EntityTagHeaderValue> EtTagHeaderValues = new ConcurrentDictionary<string, EntityTagHeaderValue>();
private readonly string _dependencyKeys;
public EnableETag() { }
public EnableETag(string dependencyKeys)
{
_dependencyKeys = dependencyKeys;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!string.IsNullOrEmpty(_dependencyKeys)) // TODO: oder wenn es kein GET ist...
{
foreach (var entityTagHeaderValue in EtTagHeaderValues)
{
var splittedKey = entityTagHeaderValue.Key.Split('|');
var result = string.Empty;
foreach (var dependencyKey in _dependencyKeys.Split('|'))
{
result = splittedKey.FirstOrDefault(k => k.Equals(dependencyKey, StringComparison.InvariantCultureIgnoreCase));
if (!string.IsNullOrEmpty(result))
{
EntityTagHeaderValue value;
EtTagHeaderValues.TryRemove(entityTagHeaderValue.Key, out value);
break;
}
}
}
}
//Get the request
var request = actionContext.Request;
if (request.Method == HttpMethod.Get)
{
var key = GetKey(actionContext);
//Get if If-None match header
var clientEtags = request.Headers.IfNoneMatch;
if (clientEtags.Count > 0)
{
EntityTagHeaderValue etag = null;
if (EtTagHeaderValues.TryGetValue(key, out etag) && clientEtags.Any(t => t.Tag == etag.Tag))
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.NotModified);
SetCacheControl(actionContext.Response);
}
}
}
}
private void SetCacheControl(HttpResponseMessage httpResponseMessage)
{
httpResponseMessage.Headers.CacheControl = new CacheControlHeaderValue()
{
MaxAge = TimeSpan.FromSeconds(6),
MustRevalidate = true,
Private = true
};
}
private string GetKey(HttpActionContext actionContext)
{
var request = actionContext.Request;
string uri = string.Empty;
if (request.RequestUri.AbsoluteUri.Contains('?'))
{
uri = request.RequestUri.AbsoluteUri.Substring(request.RequestUri.AbsoluteUri.IndexOf('?'));
}
var uriArray = uri.Split('&');
var expand = uriArray.FirstOrDefault(segment => segment.StartsWith("$expand=", StringComparison.InvariantCultureIgnoreCase));
var sbExpand = new StringBuilder();
if (expand != null)
{
var expandEntities = expand.Split(new[] { '/', ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var expandEntity in expandEntities)
{
sbExpand.Append(expandEntity);
sbExpand.Append("|");
}
}
sbExpand.Append(actionContext.ControllerContext.ControllerDescriptor.ControllerName);
return sbExpand.ToString();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var request = actionExecutedContext.Request;
var key = GetKey(actionExecutedContext.ActionContext);
EntityTagHeaderValue entityTag;
if (!EtTagHeaderValues.TryGetValue(key, out entityTag) || request.Method == HttpMethod.Post || request.Method == HttpMethod.Post)
{
entityTag = new EntityTagHeaderValue("\"" + Guid.NewGuid().ToString() + "\"");
EtTagHeaderValues.AddOrUpdate(key, entityTag, (k, val) => entityTag);
}
actionExecutedContext.Response.Headers.ETag = entityTag;
SetCacheControl(actionExecutedContext.Response);
}
}
public class CacheKey
{
public string Headers { get; set; }
public string Identifiers { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment