Thursday, July 29, 2010

Dependency Injection using Munq

I'm in the middle of a building a WCF Rest(ful) web service and I want to use some form of DI. I've used Daniel Cazzulino's Funq on a previous project and although it doesn't necessarily provide all the bells and whistles or have the market-share of some of the other frameworks it is extremely fast, easy to set up, and has a simple lambda-based configuration. It also comes with an awesome set of videos documenting the construction process, worth looking at just to see a developer doing some great TDD.

For a WCF or MVC project there are some constraints that would make Funq a bit more interesting to set up. Fortunately there is a derivative of Funq, Matthew Dennis's Munq project (example usage for ASP.NET MVC is available on CodeProject) that is intended for this purpose.

Using Munq for WCF differs from the ASP.NET MVC configuration example and I went through the exercise long enough ago that the only evidence I have is an old project that I've been trawling through to try and figure out how it is all wired together. I do recall following someone else's instructions (Oran Dennison's I think) for implementing a different IOC framework with WCF and modifying it for Munq. This particular blog entry is at least in part to avoid having go through the same head scratching exercise the next time I want to use DI in a WCF service.

My WCF service will be hosted in IIS and I created the initial project using the WCF REST Service Application template. The idea is to allow Munq to serve up instances of the service and Microsoft provides a convenient hook for doing this using the IInstanceProvider interface.

1  public class MunqInstanceProvider : IInstanceProvider
2  {
3
4      private readonly Type serviceType;
5
6      public MunqInstanceProvider(Type serviceType)
7      {
8          this.serviceType = serviceType;
9      }
10
11 #region Implementation of IInstanceProvider
12
13      /// <summary>
14     /// Returns a service object given the specified <see cref="T:System.ServiceModel.InstanceContext"/> object.
15     /// </summary>
16      /// <returns>
17     /// A user-defined service object.
18     /// </returns>
19     /// <param name="instanceContext">The current <see cref="T:System.ServiceModel.InstanceContext"/> object.</param>
20      public object GetInstance(InstanceContext instanceContext)
21     {
22         return GetInstance(instanceContext, null);
23     }
24
25     /// <summary>
26     /// Returns a service object given the specified <see cref="T:System.ServiceModel.InstanceContext"/> object.
27      /// </summary>
28     /// <returns>
29     /// The service object.
30     /// </returns>
31     /// <param name="instanceContext">The current <see cref="T:System.ServiceModel.InstanceContext"/> object.</param><param name="message">The message that triggered the creation of a service object.</param>
32      public object GetInstance(InstanceContext instanceContext, Message message)
33      {
34         var instance = Global.Di.Resolve(serviceType);
35         return instance;
36     }
37
38     /// <summary>
39     /// Called when an <see cref="T:System.ServiceModel.InstanceContext"/> object recycles a service object.
40     /// </summary>
41     /// <param name="instanceContext">The service's instance context.</param><param name="instance">The service object to be recycled.</param>
42      public void ReleaseInstance(InstanceContext instanceContext, object instance)
43     {
44         // container disposes of instance?
45         //if (instance is IDisposable)
46         // ((IDisposable)instance).Dispose();
47      }
48
49     #endregion
50 }

That call to Global.Di.Resolve() is where Munq returns an instance of the service. Global.Di needs to be defined somewhere, I'll get to that in a bit. Having implemented IInstanceProvider I now need to provide a custom serviceBehavior using the IServiceBehavior interface for the service endpoint. The following code creates a new MunqInstanceProvider for each ChannelDispatcher endpoint.

Note ForAll() is an extension method not included in .NET but defined as you might expect. For an explanation as to why it wasn't included in the framework refer to Eric Lippert's blog. For a long time I assiduously used a foreach loop to perform some action on every item in a sequence. Recently I've been reading Effective C# (Covers C# 4.0): 50 Specific Ways to Improve Your C#. Item 8 suggests I should Prefer Query Syntax to Loops and I absolutely do.

1 public class MunqServiceBehavior : IServiceBehavior
2 {
3     #region IServiceBehavior Members
4
5     public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) {}
6
7     public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
8     {
9         var endpoints = from cd in serviceHostBase.ChannelDispatchers.Cast<ChannelDispatcher>()
10                 where cd != null
11                 from endpoint in cd.Endpoints
12                 select endpoint;
13
14         endpoints.ForAll(ep => ep.DispatchRuntime.InstanceProvider = new MunqInstanceProvider(serviceDescription.ServiceType));
15     }
16
17     public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) {}
18
19     #endregion
20 }

Web.config is used to wire this all together but in order to reference the custom behavior a Munq-specific BehaviorExtensionElement is required.

1  public class MunqInstanceProviderExtensionElement : BehaviorExtensionElement
2  {
3      public override Type BehaviorType
4      {
5          get { return typeof (MunqServiceBehavior); }
6      }
7
8      protected override object CreateBehavior()
9      {
10          return new MunqServiceBehavior();
11     }
12 }

The relevant section of Web.config is provided below and replaces the system.serviceModel section. Getting each element correct is important but the stumbling block for me was getting the service name correct and I couldn't figure out why none of my Munq classes were being hit.

1  <system.serviceModel>
2      <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
3                   multipleSiteBindingsEnabled="true" />
4       <services>
5           <service name="StillPearlingService.StillPearling" behaviorConfiguration="MunqServiceBehavior"></service>
6       </services>
7       <behaviors>
8          <serviceBehaviors>
9              <behavior name="MunqServiceBehavior">
10                  <MunqInstanceProvider />
11            </behavior>
12            <behavior name="">
13                <serviceMetadata httpGetEnabled="true" />
14                <serviceDebug includeExceptionDetailInFaults="false" />
15            </behavior>
16        </serviceBehaviors>
17     </behaviors>
18      <extensions>
19          <behaviorExtensions>
20              <add name="MunqInstanceProvider" type="StillPearling.IOC.MunqInstanceProviderExtensionElement, StillPearling, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
21          </behaviorExtensions>
22      </extensions>
23      <standardEndpoints>
24          <webHttpEndpoint>
25      <!--
26         Configure the WCF REST service base address via the global.asax.cs file and the default endpoint
27         via the attributes on the <standardEndpoint> element below
28     -->
29              <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" />
30          </webHttpEndpoint>
31      </standardEndpoints>
32 </system.serviceModel>


So having completed the majority of the wiring there is still the small matter of declaring and instantiating the Munq DI container. This happens in Global.asax, along with registering other objects I want to wire into the container. In this case I have a Model class that I use for accessing a database. Within my service I declare a Model property with a public setter that is used as a hook for Munq to inject the correct Model object.

1  public class Global : HttpApplication
2  {
3      private static Logger logger = LogManager.GetCurrentClassLogger();
4      public static Container Di { get; private set; }
5
6      void Application_Start(object sender, EventArgs e)
7      {
8           logger.Info(() => "Application_Start");
9           Di = new Container();
10         RegisterInterfaces();
11         RegisterRoutes();
12     }
13
14     private static void RegisterInterfaces()
15     {
16         logger.Debug(() => "RegisterInterfaces");
17         Di.Register<IStillPearlingModel>(di => new Model());
18         Di.Register(di => new StillPearling { Model = di.Resolve<IStillPearlingModel>(), });
19     }
20
21     private static void RegisterRoutes()
22     {
23         // Edit the base address of Service1 by replacing the "Service1" string below
24         RouteTable.Routes.Add(new ServiceRoute("StillPearlingService", new WebServiceHostFactory(), typeof(StillPearling)));
25     }
26 }
27

Note this was all done using Munq 1.0 and Munq 2.0 has recently been released. I don't expect any issues transitioning to version 2.0 but I'll try to provide an update if there are any potholes to be avoided. If you've read this far I appreciate it and I would further appreciate feedback on what I'm doing wrong. Finally I apologise for any formatting issues with the code above - I'm still looking for a good encoding tool that works with blogger.

2 comments:

rouftop said...

THANK YOU. I was tearing my hair out. Do you have any idea why this older method doesn't work with WCF REST?

http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/07/29/integrating-structuremap-with-wcf.aspx

For some reason when I went this route, using a WebServiceHostFactory as the base class of my own factory, it never calls the IInstanceProvider's GetInstance method.

Thanks again for sharing your experiences.

David Clarke said...

Thanks for the feedback. I had a quick look at that link and in the comments section there's a reference to this post that might be useful. I'm just not familiar with structuremap tho' I've heard good things.