Tuesday, January 4, 2011

Integrating StructureMap, Unit of Work and WCF

When I initially set off on using StructureMap as my IoC of choice on a recent ASP.NET MVC/WCF project, I didn’t realize how deep the rabbit hole actually was.  Oh, the MVC part was easy.  StructureMap integration in an MVC application has been demonstrated a thousand times over and is made even easier with the latest release of ASP.NET MVC 3 and NuGet.  Getting StructureMap to serve up objects in a WCF application, now THAT’S a horse of a different color.

Standing on Someone Else’s Shoulders

Two blog posts helped me tremendously when attempting this:

I highly recommend reading both posts first, in that order.

Service Request/Response

Before going any further, I need to take a moment to define the pattern I was using on the project for calling the WCF service layer.  For each service operation I defined two data contracts: [OperationName]Request; and [OperationName]Response.  Both of these classes are decorated with the DataContract attribute and encapsulated the parameters in both the request and response to the service.  This allows the values that are passed into and returned from the service to change without breaking the service contract.  Much more flexible, IMHO.  Here’s an example.

  1. [ServiceContract]
  2. public interface IInventoryService
  3. {
  4.     [OperationContract]
  5.     public CreateProductResponse CreateProduct(CreateProductRequest request);
  6. }
  1. [DataContract]
  2. public class CreateProductRequest
  3. {
  4.     [DataMember]
  5.     public string Name { get; set; }
  7.     [DataMember]
  8.     public decimal Price { get; set; }
  9. }
  1. [DataContract]
  2. public class CreateProductResponse
  3. {
  4.     [DataMember]
  5.     public int NewProductId { get; set; }
  6. }

Taking it a Step Further

Andreas’ example would rollback the transaction if an unhandled exception was thrown in the WCF application.  This is all well and good but I didn’t like the idea of throwing exceptions from my service layer.  I thought a much cleaner approach would be to return one or more OperationResult objects in the service response object if a problem occurred.  An OperationResult would basically just consist of a Key/Message pair that identifies exactly what or where something went wrong (in the Key) as well as details of the error (in the Message).  In most cases, this could represent validation errors bubbled up from the domain model.  The Key would be the property name that was invalid and the Message would contain, well, the error message of the validation error (e.g., “Email address is in the wrong format”).  But this could also include other, more generic, errors as well.

  1. [Serializable]
  2. public class OperationResult
  3. {
  4.     public string Key { get; set; }
  5.     public string Message { get; set; }
  6. }

I decided to create a base class that contained a collection of OperationResult objects so that I wouldn’t have to reinvent the wheel for every Response object I created.  It looks like this:

  1. public interface IOperationResultResponse
  2. {
  3.     public void AddResult(string key, string message);
  4.     public IList<OperationResult> Results { get; set; }
  5. }
  7. [DataContract]
  8. public class OperationResultResponse : IOperationResultResponse
  9. {
  10.     private void initResults()
  11.     {
  12.         this.Results = new List<OperationResult>();
  13.     }
  15.     public IList<OperationResult> Results { get; set; }
  17.     public void AddResult(string key, string message)
  18.     {
  19.         if (this.Results == null)
  20.             initResults();
  22.         this.Results.Add(new OperationResult { Key = key, Message = message });
  23.     }
  25.     public OperationResultResponse()
  26.     {
  27.         initResults();
  28.     }
  29. }

And then, for all (or some, if not applicable) of your response simply inherit from the OperationResultResponse base class.

Inherit from response base
  1. [DataContract]
  2. public class CreateProductResponse : OperationResultResponse
  3. {
  4.     [DataMember]
  5.     public int NewProductId { get; set; }
  6. }

Where the Magic Happens

OK, that was a lot of lead up to the big reveal.  How do we take this service request/response object model and use it to rollback our Unit of Work (and consequently our NHibernate session transaction)?  I got one, err, two, ish words for you: IParameterInspector.  This is another one of WCF’s many extensibility points and, like IDispatchMessageInspector, it allows you to perform work when a request is received and before a response is sent.  In our case, we want to perform the following steps before each response is sent:

  1. Get a reference to the Unit of Work for the current request
  2. Check the response object type and see if it implements IOperationResultResponse
  3. If the response object DOES implement IOperationResultResponse, interrogate the Results property to see if there are any errors contained therein
  4. If there ARE errors in the Results property, rollback the Unit of Work by calling Rollback()
  5. If none of the above applies, call Commit() and Dispose() on the UoW before sending the response on its way

Here’s what our IParameterInspector implementation looks like:

Custom IParameterInspector
  1. public class OperationResultParameterInspector : IParameterInspector
  2. {
  3.     private IContainer _container;
  5.     public OperationResultParameterInspector(IContainer container)
  6.     {
  7.         _container = container;
  8.     }
  10.     public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
  11.     {
  12.         // Step 1: Get UoW
  13.         var uow = _container.GetInstance<IUnitOfWork>();
  15.         // Step 2: Check for IOperationResult
  16.         var opResultResponse = returnValue as IOperationResultResponse;
  17.         if (opResultResponse != null)
  18.         {
  19.             // Step 3: Interrogate Results for errors
  20.             if (opResultResponse.Results.Count > 0)
  21.             {
  22.                 // Step 4: Rollback our UoW before sending response
  23.                 uow.Rollback();
  24.             }
  25.         }
  26.         else
  27.         {
  28.             // Step 5: If we're still here, commit and dispose
  29.             uow.Commit();
  30.         }
  32.         uow.Dispose();
  33.     }
  35.     public object BeforeCall(string operationName, object[] inputs)
  36.     {
  37.         // No implementation
  38.         return null;
  39.     }
  40. }

Why We Can’t Use BeforeSendReply

We’re still going to reuse the Andreas’ UnitOfWorkMessageInspector to create and initialize the Unit of Work in the AfterReceiveRequest but instead of committing and disposing of the UoW in the BeforeSendReply method, we’re going to rely on our parameter inspector’s AfterCall method to do the work.  The reason we’re not performing the above logic in the BeforeSendReply method of IDispatchMessageInspector is because BeforeSendReply happens AFTER the response has been serialized into a Message object, making it much more difficult to interrogate the values.  Believe me, I tried.  AfterCall of IParameterInspector is invoked BEFORE the return value has been serialized into a Message object so you can easily just cast the return value to our IOperationResultResponse interface. 

Wiring It All Up

Wiring up our IParameterInspector is just like wiring up the IDispatchMessageInspectors, IErrorHandlers, etc.  Iterate through each EndPoint, get the list of IParameterInspectors  (you may have more than one!) from StructureMap and append them to each DispatchOperation.  Here’s what our updated StructureMapServiceBehavior class looks like.  I’ve omitted the other details for brevity.

  1. public class StructureMapServiceBehavior : IServiceBehavior
  2. {
  3.     private IContainer _container;
  5.     public StructureMapServiceBehavior(IContainer container)
  6.     {
  7.         _container = container;
  8.     }
  10.     public void Validate(ServiceDescription serviceDescription,
  11.         ServiceHostBase serviceHostBase) { /* No implementation */ }
  12.     public void AddBindingParameters(ServiceDescription serviceDescription,
  13.         ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
  14.         BindingParameterCollection bindingParameters) { /* No implementation */ }
  16.     public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  17.     {
  18.         foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
  19.         {
  20.             var cd = cdb as ChannelDispatcher;
  21.             if (cd != null)
  22.             {
  23.                 foreach (EndpointDispatcher ed in cd.Endpoints)
  24.                 {
  25.                     // Not shown: Add InstanceContextInitializers, IDispatchMessageInspectors, ICallContextInitializers, etc.
  27.                     foreach (DispatchOperation op in ed.DispatchRuntime.Operations)
  28.                     {
  29.                         // Add custom IParameterInspectors
  30.                         foreach (var pi in _container.GetAllInstances<IParameterInspector>())
  31.                         {
  32.                             op.ParameterInspectors.Add(pi);
  33.                         }
  34.                     }
  35.                 }
  36.             }
  38.             // Not shown: Add IErrorHandlers
  39.         }
  40.     }
  41. }

And of course, we add the mapping between IParameterInspector and our custom OperationResultParameterInspector in the traditional StructureMap way:

  1. public class ServicesRegistry : StructureMap.Configuration.DSL.Registry
  2. {
  3.     public ServicesRegistry()
  4.     {
  5.         // Not shown: wiring up all other WCF extensions
  6.         For<IParameterInspector>().Add<OperationResultParameterInspector>();
  7.     }
  8. }


This really turned into an exercise in learning the various WCF extension points, what you can and cannot do at each, etc.  We’ve learned though that the best place for inspecting the actual service response objects before they’re sent across the wire is in the AfterCall method of the IParameterInspector interface.

No comments:

Post a Comment