2.02.2011

Setting up WCF to use StructureMap

As I previously posted, I recently deployed a new web service using WCF. My team is using NHibernate as our ORM and using StructureMap to handle inversion of control and dependency injection. Obviously, we do not want to reinvent the wheel when deploying a WCF service so we needed to have StructureMap handle dependency injection for us. Jimmy Bogard came to our rescue with a post detailing how this can be done. However, there are a few things that can be trimmed from his example and a way to setup the service to work for all services in a project. The first thing that needs to be created is an implementation of IInstanceProvider.
public class StructureMapInstanceProvider : IInstanceProvider
{
    private Type _serviceType;

    public StructureMapInstanceProvider(Type serviceType)
    {
        this._serviceType = serviceType;
    }

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return ObjectFactory.GetInstance(_serviceType);
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        return this.GetInstance(instanceContext, null);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        //No cleanup required
    }
}
This is almost verbatim from Jimmy's post. So now we have a way that WCF can instantiate objects. We just need to tell WCF about it. This can be done by implementing the IServiceBehavior interface. Again, we use Jimmy's code:
public class StructureMapServiceBehavior : IServiceBehavior
{
    public void ApplyDispatchBehavior(ServiceDescription desc, ServiceHostBase host)
    {
        foreach (ChannelDispatcherBase cdb in host.ChannelDispatchers)
        {
            ChannelDispatcher cd = cdb as ChannelDispatcher;
            if (cd != null)
            {
                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.InstanceProvider = 
                        new StructureMapInstanceProvider(desc.ServiceType);
                }
            }
        }
    }

    public void AddBindingParameters(ServiceDescription desc, ServiceHostBase host,  
                                     Collection endpoints, 
                                     BindingParameterCollection bindingParameters)
    {
    }

    public void Validate(ServiceDescription desc, ServiceHostBase host)
    {
    }
}
This ties our IInstanceProvider to every endpoint that we expose via our service. Jimmy then recommends extending ServiceHost and ServiceHostFactory and using those classes to attach the Factory to each service. I wanted to be able to configure this behavior once for the entire project. So I modified the StructureMapServiceBehavior class to extend BehaviorExtensionElement. This forced me to implement the following methods:
public class StructureMapInstanceProvider : IInstanceProvider
{
...
public override Type BehaviorType
{
    get { return this.GetType(); }
}

protected override object CreateBehavior()
{
    ObjectFactory.Initialize(cfg =>
    {
        cfg.Scan(scan =>
        {
            scan.WithDefaultConventions();
        });
    });
    return this;
}
...
}
This will initialize StructureMap. With this complete, I can use StructureMapServiceBehavior as a behavior in my Web.Config.
<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <StructureMapServiceBehavior />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <extensions>
      <behaviorExtensions>
        <add name="StructureMapServiceBehavior" 
             type="Services.StructureMapServiceBehavior, Services, Version=1.0.0.0, Culture=neutral"/>
      </behaviorExtensions>
    </extensions>
  </system.serviceModel>
Now StructureMap is loaded up and controls instantiation for any of my classes, just like in my MVC projects. This can be used with any dependency injection framework. Just modify the IInstanceProvider.GetInstance methods and the BehaviorExtensionElement.CreateBehavior method.

9 comments:

  1. Scott, Thanks for the post. I was not able to get this to work. Can you please post source code for a project that uses this? If not can you provide more detail on the last two items in the blog? I think what is causing me problems is implementing the behavior and the web configuration.

    ReplyDelete
  2. Scott, I wanted to give you more detail about what I am struggling with. Sorry I am a wcf and structuremap newbie. How is the sturcturemapbehavior triggered by wcf? Where should the behaviortype and createbehavior methods be placed? You say you implement these methods but where do you implement them? Thanks for your help.

    ReplyDelete
  3. @Dave Thanks for contacting me. The BehaviorType and CreateBehavior should be implemented on the StructureMapServiceBehavior class. I've updated the post to reflect that. The StructureMapBehavior gets triggered by adding the behavior extension in the web.config.

    ReplyDelete
  4. Scott, When you post code, please make sure it works. Also, naming parameters "base" doesn't compile.

    ReplyDelete
  5. @Anonymous, thanks for pointing out the unfortunate choice of parameter name. I needed to sanitize this code before posting it and obviously made a mistake. It has been corrected.

    ReplyDelete
  6. Hi Scott
    Similar to Dave I am struggling with where I put the 2 override parts (GetType,createBehavior)...it seems the code example is incomplete. If I add those methods to the StructureMapServiceBeahvior class I get compile errors. Would be good if you could update the blog or provide example code for download? :)
    Thanks
    Matt, Nottingham, UK

    ReplyDelete
  7. GetType and CreateBehavior are part of BehaviorExtensionElement. StructureMapServiceBehavior should extend BehaviorExtensionElement as Scott mentioned (but seem forgot to update the code sample)

    ReplyDelete
  8. @Scott, thanks for the post. It was nice. But I have one issue with my wcf web service. I need to increase the maxBufferSize in server side. I'm confused, how to increase them with structure map. Because as I can see, service take the default values instead of getting values from the config file. May be something wrong with my integration. I hope you will advise me to do this.
    Thanks

    ReplyDelete
  9. This comment has been removed by a blog administrator.

    ReplyDelete

Note: Only a member of this blog may post a comment.