Reputation: 5342
I am trying to add IoC support to my REST WCF service (Windows Server 2008). I am new to this and am following the instructions provided in the below video:
http://www.dimecasts.net/Content/WatchEpisode/150
The video walks through a number of classes that help me get StructureMap's IoC up and running while exposing WCF endpoints. I have posted all of the code at the end of this post.
When I run my code, the custom class StructureMapServiceHost throws an error @ the StructureMapServiceHost(Type serviceType, params Uri[] baseAddress ) method:
public class StructureMapServiceHost : ServiceHost
{
public StructureMapServiceHost() {}
public StructureMapServiceHost(Type serviceType, params Uri[] baseAddress)
: base(serviceType, baseAddress)
{
}
protected override void OnOpening()
{
Description.Behaviors.Add( new IoCServiceBehavior());
base.OnOpening();
}
}
I am being told that:
The service type provided could not be loaded as a service because it does not have a default (parameter-less) constructor. To fix the problem, add a default constructor to the type, or pass an instance of the type to the host.
This is true, it doesn't. But the video example didn't have one either. Below is my service:
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class UserService : IUserService
{
public UserService(IUserRepository specification)
{
Specification = specification;
}
public List<User> GetAllUsers()
{
return Specification.GetAllUsers();
}
public User GetUser(string userId)
{
return Specification.GetUserById(new Guid(userId));
}
private List<User> SearchForUsers(string searchString)
{
return Specification.SearchUsers(searchString);
}
public IUserRepository Specification { get; set; }
}
public class IoCServiceBehavior : IServiceBehavior
{
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
new StructureMapInstanceProvider(serviceDescription.ServiceType);
}
}
}
public class StructureMapInstanceProvider : IInstanceProvider
{
private readonly Type _serviceType;
public StructureMapInstanceProvider(Type serviceType)
{
_serviceType = serviceType;
}
public object GetInstance(InstanceContext instanceContext)
{
return GetInstance(instanceContext, null);
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
var instance = ObjectFactory.GetInstance(_serviceType);
return instance;
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
throw new NotImplementedException();
}
}
public class StructureMapServiceHostFactory : ServiceHostFactory
{
public StructureMapServiceHostFactory()
{
IoCBootstrap.SetupIoc();
}
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new StructureMapServiceHost(serviceType, baseAddresses);
}
}
Any ideas? Thanks.
EDIT ********************************
From StructureMapServiceHost I removed:
public StructureMapServiceHost(Type serviceType, params Uri[] baseAddress)
: base(serviceType, baseAddress) { }
And added:
public StructureMapServiceHost(Object singletonInstance, params Uri[] baseAddress)
: base( singletonInstance, baseAddress) { }
And then removed the parameter from my UserService constructor. I am not getting the error:
The HTML document does not contain Web service discovery information.
Upvotes: 5
Views: 8692
Reputation: 4407
Your service uses InstanceContextMode.SingleCall and the WCF team have decided, in their infinite wisdom, that when the InstanceContextMode is SingleCall the IInstanceProvider is not invoked to create the instance (See https://learn.microsoft.com/en-us/archive/blogs/carlosfigueira/wcf-extensibility-iinstanceprovider - second para below Interface declration heading).
Currrently I have a less than ideal way of getting around that in the service host factory:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Activation;
using StructureMap;
using StructureMap.Pipeline;
using System.Linq;
using ServiceHostCreator = System.Func<System.Type, System.Uri[], System.ServiceModel.ServiceHost>;
namespace x.ServiceExtensions
{
public class xWebServiceHostFactory : ServiceHostFactory
{
private readonly IDictionary<InstanceContextMode, ServiceHostCreator> _serviceHostCreators;
public xWebServiceHostFactory()
{
ObjectFactory.Initialize( init =>
init.Scan( scan =>
{
scan.AssembliesFromApplicationBaseDirectory();
scan.IgnoreStructureMapAttributes();
scan.LookForRegistries();
} ) );
_serviceHostCreators = new Dictionary<InstanceContextMode, ServiceHostCreator>
{
{ InstanceContextMode.PerCall, ( t, a ) => PerCallServiceHostCreator( t, a ) },
{ InstanceContextMode.PerSession, ( t, a ) => PerSessionServiceHostCreator( t, a ) },
{ InstanceContextMode.Single, ( t, a ) => SingleInstanceServiceHostCreator( t, a ) }
};
}
protected override ServiceHost CreateServiceHost( Type serviceType, Uri[] baseAddresses )
{
var serviceInstanceContextMode = GetServiceInstanceContextMode( serviceType );
var serviceHostCreator = _serviceHostCreators[ serviceInstanceContextMode ];
return serviceHostCreator( serviceType, baseAddresses );
}
private static InstanceContextMode GetServiceInstanceContextMode( Type serviceType )
{
var serviceBehaviour = serviceType
.GetCustomAttributes( typeof ( ServiceBehaviorAttribute ), true )
.Cast<ServiceBehaviorAttribute>()
.SingleOrDefault();
return serviceBehaviour.InstanceContextMode;
}
private static ServiceHost PerCallServiceHostCreator( Type serviceType, Uri[] baseAddresses )
{
var args = new ExplicitArguments();
args.Set( serviceType );
args.Set( baseAddresses );
var serviceHost = ObjectFactory.GetInstance<TelaWebServiceHost>( args );
return serviceHost;
}
private static ServiceHost PerSessionServiceHostCreator( Type serviceType, Uri[] baseAddresses )
{
return PerCallServiceHostCreator( serviceType, baseAddresses );
}
private static ServiceHost SingleInstanceServiceHostCreator( Type serviceType, Uri[] baseAddresses )
{
var service = ObjectFactory.GetInstance( serviceType );
var args = new ExplicitArguments();
args.Set( typeof(object), service );
args.Set( baseAddresses );
var serviceHost = ObjectFactory.GetInstance<TelaWebServiceHost>( args );
return serviceHost;
}
}
}
This is a work in progress and there may be a better way, but at the moment I can't find one.
Upvotes: 18
Reputation: 2621
To anyone who is trying to do something like that, I'd currently strongly recommend using Spring.NET as IoC container. While it may not be as easy to use as some other containers (I'm not particularly fond of its XML configuration), it has by far the best WCF integration. It also comes with a clever and transparent workaround for the InstanceContextMode.SingleCall problem (using its AOP / dynamic proxy framework).
http://www.springframework.net/docs/1.2.0-M1/reference/html/wcf.html
http://www.springframework.net/doc-latest/reference/html/wcf-quickstart.html
Upvotes: 0
Reputation: 80870
I can't take a look at the video right now (Internet limitations), but I'm pretty sure the class in their example didn't have any constructor at all. And in that case, the compiler generates an empty parameterless constructor on your behalf. Therefore, their class did have a default constructor after all.
As far as the exception, that seems pretty straightforward: your parameterless constructor doesn't initialize the Specification
property, so it is always null
- which, naturally, causes a NullReferenceException
once you try to access it in your methods.
It seems that what you intended here is to create the UserService
object yourself and pass a IUserRepository
to it, didn't you? (or, perhaps, use your IoC framework for that?)
In that case, you'd better use the overload of ServiceHost
's constructor that takes an object
instead of Type
. That way, you will have complete control over your UserService
object, and you won't need a default constructor at all.
Upvotes: 0