Apr 27, 2012

Wcf Operation Invoker - IOperationInvoker

Operation invoker is the last element in the WCF runtime which is invoked before the service implementation is reached – it’s the invoker responsibility to actually call the service operation on behalf of the runtime. This can be used to implement caching logic for expensive operations.

public class CachingOperationInvoker : Attribute, IOperationBehavior,IOperationInvoker
{
 readonly IOperationInvoker _originalInvoker;
 readonly double _cacheDuration;

 public CachingOperationInvoker(IOperationInvoker originalInvoker, double cacheDuration)
 {
  _originalInvoker = originalInvoker;
  _cacheDuration = cacheDuration;
 } 
 
 public double SecondsToCache { get; set; }
 
 #region Implementation of IOperationInvoker

 public object Invoke(object instance, object[] inputs, out object[] outputs)
 {
  ObjectCache cache = GetCache();

  string cacheKey = CreateCacheKey(inputs);
  CachedResult cacheItem = cache[cacheKey] as CachedResult;
  if (cacheItem != null)
  {
   outputs = cacheItem.Outputs;
   return cacheItem.ReturnValue;
  }
  object result = _originalInvoker.Invoke(instance, inputs, out outputs);
  cacheItem = new CachedResult { ReturnValue = result, Outputs = outputs };
  cache.Add(cacheKey, cacheItem, DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(_cacheDuration)));

  return result;
 }
 
 #endregion
 
 #region Implementation of IOperationBehavior
 
 public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
 {
  dispatchOperation.Invoker = new CachingOperationInvoker(dispatchOperation.Invoker, SecondsToCache);
 }
 
 #endregion
Now you can easily decorate your operation with the attribute
  
 [CachingOperationInvoker(SecondsToCache = 100)]
    Product GetProduct(int id); 

One thing I would like to point out here is that the caching is happening at the server level which will mean that it will go through the wcf pipeline, the saving which we are doing here is the actual operation call. The other approach to consider here would be to come up with caching logic at the client side. Unlike REST services, SOAP services don’t have any standard way of defining caching options for their operations.

Apr 10, 2012

Conditional Cache for RESTful service

As per the HTTP specification, GET and HEAD shouldn’t have any side effects that would adversely effect the caching of the response, unless server prohibits caching. Most of the browser caches HTTP/200 responses, unless Expires, Pragma, or Cache-Control headers are present.

Just by having ETag or Last-Modified time, your GET method will be cached in the browser. You can easily test this using fiddler.

 In this post I am going to write a conditional cache logic based on ETag and/or LastModified. In the following code Product object has two additional fields : ETag and LastModifiedDate, based on this (both or eather) field, we will determine if we need to suppress Entity Body and accordingly set the response status to NotModified.

 
 public Product GetProductRestStyle(string id)
 {
  OutgoingWebResponseContext outgoingResponse = WebOperationContext.Current.OutgoingResponse;
  IncomingWebRequestContext incomingRequest = WebOperationContext.Current.IncomingRequest;

  //retrieve product object from any repository
  Product product = GetProduct(Convert.ToInt32(id));

  if(product == null)
  {
   outgoingResponse.SetStatusAsNotFound();
   return null;
  }

  //Product object contains LastModifiedDate and ETag, which we will compare in the incomingRequest 
  //and if it is same we will supress the response body
  CheckModifiedSince(incomingRequest, outgoingResponse, product.LastModifiedDate);
  CheckETag(incomingRequest, outgoingResponse, product.ETag);

  //Set LastModifiedDate and ETag on the outgoing response 
  outgoingResponse.ETag = product.ETag.ToString();
  outgoingResponse.LastModified = product.LastModifiedDate;

  //Give a cache hint
  //Resource will expire in 10 sec
  outgoingResponse.Headers.Add(HttpResponseHeader.CacheControl, "max-age=10");
  
  return product;
 }
 
 private static void CheckETag(IncomingWebRequestContext incomingRequest, OutgoingWebResponseContext outgoingResponse, Guid ServerETag)
 {
  string test = incomingRequest.Headers[HttpRequestHeader.IfNoneMatch];
  if (test == null)
   return;
  Guid? eTag = new Guid( test);
  if (eTag != ServerETag) return;
  outgoingResponse.SuppressEntityBody = true;
  outgoingResponse.StatusCode = HttpStatusCode.NotModified;
 }

 private static void CheckModifiedSince(IncomingWebRequestContext incomingRequest, OutgoingWebResponseContext outgoingResponse, DateTime serverModifiedDate)
 {
  DateTime modifiedSince = Convert.ToDateTime(incomingRequest.Headers[HttpRequestHeader.LastModified]);

  if (modifiedSince != serverModifiedDate) return;
  outgoingResponse.SuppressEntityBody = true;
  outgoingResponse.StatusCode = HttpStatusCode.NotModified;
  
 }


The above code will be useful in the following conditions:
In the case where the client cannot tolerate a stale cache and a "hint" is not good enough, they may issue a request to the server, if the cache is still fresh, the server will send a 304 "Not Modified" response with no entity body. The client can then safely source the data from it’s cache.

Another scenario is the case where the client abides by the caching hint, but the cache is expired. As opposed to simply throwing away the cache, the client can issue a Conditional GET to see if the cache is still good.