I have been looking for a way to use Unity for Dependency Injection in my WCF service. I have been trying to understand the code described in these two blogs, which is quite similar:
Integrating Unity with WCF
WCF and Unity 2.0
So I added this code to a separate project in my solution and refer to the custom servicehostfactory in a SVC file to my WFC service (separate project).
The question is now: How do I access the objects in my container from my WCF Service methods?
EDIT
These are my implementations:
ServiceHostFactory...
namespace UnityWcfAssembler
{
public class UnityServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
UnityServiceHost serviceHost = new UnityServiceHost(serviceType, baseAddresses);
UnityContainer container = new UnityContainer();
serviceHost.Container = container;
//TODO configuration from app.config
//configure container
//UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
//section.Configure(serviceHost.Container);
InitializeSessionFactories(container);
return serviceHost;
}
private static void InitializeSessionFactories(UnityContainer container)
{
Dictionary<String, ISessionFactory> sessions = new Dictionary<string, ISessionFactory>();
Configuration Cfg = new Configuration();
Cfg.Configure();
Cfg.SetProperty("connection.connection_string",
"Data Source=(Local);Initial Catalog=Fossils;Integrated Security=true;");
ISessionFactory Factory = Cfg.BuildSessionFactory();
sessions.Add("fossils", Factory);
Cfg.SetProperty("connection.connection_string",
"Data Source=(Local);Initial Catalog=TypeCollection;Integrated Security=true;");
ISessionFactory typeFactory = Cfg.BuildSessionFactory();
sessions.Add("type", typeFactory);
Cfg.SetProperty("connection.connection_string",
"Data Source=(Local);Initial Catalog=PersonalCollection;Integrated Security=true;");
ISessionFactory persFactory = Cfg.BuildSessionFactory();
sessions.Add("personal", persFactory);
container.RegisterInstance(sessions);
}
}
}
ServiceHost...
namespace UnityWcfAssembler
{
public class UnityServiceHost : ServiceHost
{
public UnityContainer Container { get; set; }
public UnityServiceHost()
{
Container = new UnityContainer();
}
public UnityServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses)
{
Container = new UnityContainer();
}
protected override void OnOpening()
{
new UnityServiceBehavior(Container).AddToHost(this);
base.OnOpening();
if (Description.Behaviors.Find<UnityServiceBehavior>() == null)
Description.Behaviors.Add(new UnityServiceBehavior(Container));
}
}
}
InstanceProvider...
namespace UnityWcfAssembler
{
public class UnityInstanceProvider : IInstanceProvider
{
public UnityContainer Container { set; get; }
public Type ServiceType { set; get; }
public UnityInstanceProvider() : this(null)
{
}
public UnityInstanceProvider(Type type)
{
ServiceType = type;
Container = new UnityContainer();
}
// Get Service instace via unity container
public object GetInstance(InstanceContext instanceContext, Message message)
{
return Container.Resolve(ServiceType);
}
public object GetInstance(InstanceContext instanceContext)
{
return GetInstance(instanceContext, null);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
}
}
}
IServiceBehavior...
namespace UnityWcfAssembler
{
public class UnityServiceBehavior : IServiceBehavior
{
public UnityInstanceProvider InstanceProvider { get; set; }
private ServiceHost serviceHost;
public UnityServiceBehavior()
{
InstanceProvider = new UnityInstanceProvider();
}
public UnityServiceBehavior(UnityContainer unity)
{
InstanceProvider = new UnityInstanceProvider();
InstanceProvider.Container = unity;
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
InstanceProvider.ServiceType = serviceDescription.ServiceType;
ed.DispatchRuntime.InstanceProvider = InstanceProvider;
}
}
}
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
public void AddToHost(ServiceHost host)
{
// only add to host once
if (serviceHost != null) return;
host.Description.Behaviors.Add(this);
serviceHost = host;
}
}
}
Wcf Service...
namespace FossilsWcfService
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class FossilsService : IFossilsService
{
private readonly Dictionary<string, ISessionFactory> sessionFactories;
public FossilsService(Dictionary<string, ISessionFactory> s)
{
sessionFactories = s;
}
public SpeciesList GetAllSpecies()
{
SpeciesList list = new SpeciesList();
ISessionFactory factory = sessionFactories["fossils"];
if(factory == null)
{
list.Species.Add(new FossilSpecies { GenusName = "Session factory could not be resolved from container!" });
return list;
}
ISession session = factory.OpenSession();
SpeciesManager speciesManager = new SpeciesManager(session);
IList<FossilSpecies> species = speciesManager.GetAllSpecies();
foreach (FossilSpecies fossilSpecies in species)
{
list.Species.Add(fossilSpecies);
}
return list;
}
FossilsWcfService.svc...
<%# ServiceHost Language="C#" Debug="true"
Service="Server.Services.ExampleService"
Factory="UnityWcfAssembler.UnityServiceHostFactory" %>
Should the latter have a different filename perhaps?
Now that you have the factory working, you should create a property with the interface, and declare the constructor and Unity will do the rest
public class InvoiceService : IInvoiceService {
private IPayService payService;
public IPayService PayService
{
get { return payService; }
set { payService= value; }
}
public InvoiceService(IPayService provider)
{
this.payService= provider;
}
public bool Pay(){
return PayService.Pay();
}
}
My Service Factory Implementaion
public class InvoiceFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(
Type serviceType, Uri[] baseAddresses)
{
UnityServiceHost host = new UnityServiceHost(serviceType, baseAddresses);
UnityContainer unity = new UnityContainer();
host.Container = unity;
//I'm doing it like this because I put some AOP in the service injected
var clazz = Intercept.ThroughProxy<IPayService>(new PayServiceConcreteClass(),
new InterfaceInterceptor(), new[] { new LoggingInjection() });
unity.RegisterType<IPayService>().RegisterInstance(clazz);
return host;
}
}
EDIT after code in the question
I'm not really sure what could be wrong, but I spot two things that I'm not sure about:
ServiceHostFactory...
//Better as an Interface
IDictionary<String, ISessionFactory> sessions = new Dictionary<string, ISessionFactory>();
//container.RegisterInstance(sessions);
//Registering the type not the class
container.RegisterType<IDictionary<String, ISessionFactory>>().RegisterInstance(sessions);
Wcf Service...
private readonly IDictionary<string, ISessionFactory> sessionFactories;
public FossilsService(IDictionary<string, ISessionFactory> s)
{
sessionFactories = s;
}
FossilsWcfService.svc
It's not missing the codebehind attribute? something like this
<%# ServiceHost Language="C#" Debug="true" Factory="InvoiceFactory" Service="InvoiceService" CodeBehind="InvoiceService.svc.cs" %>
...
Related
I followed article to include WCF service into my ASP.NET core application.
Looking at below line in reference.cs, it seems default endpoint configuration is hard-coded inside reference.cs.
return new System.ServiceModel.EndpointAddress("http://localhost:49945/SimpleService.svc");
This is how I can create my client in asp.net core controller-
BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
EndpointAddress endpointAddress = new EndpointAddress("http://localhost:49945/SimpleService.svc");
wcfClient = new SimpleServiceClient(basicHttpBinding, endpointAddress);
So my questions are-
Where should I maintain endpoint details so that it can be easily configurable during deployment?
How I can pass endpoint details (address and binding) dynamically from configuration file (appsetting.json)?
Generated Reference.cs file looks like follows-
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// //
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SimpleServiceReference
{
using System.Runtime.Serialization;
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.5.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="CompositeType", Namespace="http://schemas.datacontract.org/2004/07/SimpleService")]
public partial class CompositeType : object
{
private bool BoolValueField;
private string StringValueField;
[System.Runtime.Serialization.DataMemberAttribute()]
public bool BoolValue
{
get
{
return this.BoolValueField;
}
set
{
this.BoolValueField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string StringValue
{
get
{
return this.StringValueField;
}
set
{
this.StringValueField = value;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.5.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="SimpleServiceReference.ISimpleService")]
public interface ISimpleService
{
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ISimpleService/GetData", ReplyAction="http://tempuri.org/ISimpleService/GetDataResponse")]
System.Threading.Tasks.Task<string> GetDataAsync(int value);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ISimpleService/GetDataUsingDataContract", ReplyAction="http://tempuri.org/ISimpleService/GetDataUsingDataContractResponse")]
System.Threading.Tasks.Task<SimpleServiceReference.CompositeType> GetDataUsingDataContractAsync(SimpleServiceReference.CompositeType composite);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.5.0.0")]
public interface ISimpleServiceChannel : SimpleServiceReference.ISimpleService, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.5.0.0")]
public partial class SimpleServiceClient : System.ServiceModel.ClientBase<SimpleServiceReference.ISimpleService>, SimpleServiceReference.ISimpleService
{
/// <summary>
/// Implement this partial method to configure the service endpoint.
/// </summary>
/// <param name="serviceEndpoint">The endpoint to configure</param>
/// <param name="clientCredentials">The client credentials</param>
static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
public SimpleServiceClient() :
base(SimpleServiceClient.GetDefaultBinding(), SimpleServiceClient.GetDefaultEndpointAddress())
{
this.Endpoint.Name = EndpointConfiguration.BasicHttpBinding_ISimpleService.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public SimpleServiceClient(EndpointConfiguration endpointConfiguration) :
base(SimpleServiceClient.GetBindingForEndpoint(endpointConfiguration), SimpleServiceClient.GetEndpointAddress(endpointConfiguration))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public SimpleServiceClient(EndpointConfiguration endpointConfiguration, string remoteAddress) :
base(SimpleServiceClient.GetBindingForEndpoint(endpointConfiguration), new System.ServiceModel.EndpointAddress(remoteAddress))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public SimpleServiceClient(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress) :
base(SimpleServiceClient.GetBindingForEndpoint(endpointConfiguration), remoteAddress)
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public SimpleServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public System.Threading.Tasks.Task<string> GetDataAsync(int value)
{
return base.Channel.GetDataAsync(value);
}
public System.Threading.Tasks.Task<SimpleServiceReference.CompositeType> GetDataUsingDataContractAsync(SimpleServiceReference.CompositeType composite)
{
return base.Channel.GetDataUsingDataContractAsync(composite);
}
public virtual System.Threading.Tasks.Task OpenAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginOpen(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndOpen));
}
public virtual System.Threading.Tasks.Task CloseAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginClose(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndClose));
}
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_ISimpleService))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_ISimpleService))
{
return new System.ServiceModel.EndpointAddress("http://localhost:49945/SimpleService.svc");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
private static System.ServiceModel.Channels.Binding GetDefaultBinding()
{
return SimpleServiceClient.GetBindingForEndpoint(EndpointConfiguration.BasicHttpBinding_ISimpleService);
}
private static System.ServiceModel.EndpointAddress GetDefaultEndpointAddress()
{
return SimpleServiceClient.GetEndpointAddress(EndpointConfiguration.BasicHttpBinding_ISimpleService);
}
public enum EndpointConfiguration
{
BasicHttpBinding_ISimpleService,
}
}
}
I needed the same thing in the past, and ended up storing the connection details for a WCF service in the app's options. I stored the details in the appsettings.json file, created an Options class, and registered it with the services setup logic so I could request it when creating the WCF service.
Bare with my code, I just whipped this up quickly. I have not tested it for common errors like missing braces, semicolons, or misspellings :-P
Options class
public class MyServiceOptions
{
public string EndpointUrl {get;set;}
}
Excerpt from startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyServiceOptions>Configuration.GetSection("MyService"));
//Other calls as needed...
}
appsettings.json
{
"MyService": {
"EndpointUrl": "http://localhost:49945/SimpleService.svc"
}
}
Then you can access your options by requesting an instance of IOptions<MyServiceOptions> from the dependency injection container in a variaty of ways.
public class MyController : Controller
{
//Option 1, in controller constructor
private IOptions<MyServiceOptions> myOptions;
public MyController(IOptions<MyServiceOptions> myOptions1)
{
myOptions = myOptions1
}
//Option 2, in action method signature
public IActionResult MyAction([FromServices]IOptions<MyServiceOptions> myOptions2)
{
//Option 3, directly
var myOptions3 = HttpContext.RequestServices.GetService<IControllerFactory>();
//NOTE: The GetService<>() method is an extension method from the Microsoft.Extensions.DependencyInjection namespace
BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
EndpointAddress endpointAddress = new EndpointAddress(myOptions.Value.EndpointUrl);
wcfClient = new SimpleServiceClient(basicHttpBinding, endpointAddress);
}
}
I have setup Castle Windsor and WebApi after reading about it in the below 2 posts. Here is my a highlight of my setup:
Reference Posts:
How do I get Web API / Castle Windsor to recognize a Controller?
Dependency Injection in WebAPI with Castle Windsor
Code Setup:
public static class GlassMapperScCustom
{
public static void CastleConfig(IWindsorContainer container)
{
container.AddFacility<TypedFactoryFacility>();
var config = new Config
{
UseWindsorContructor = true
};
//MVC
container.Register(Component.For<SitecoreController>().LifestyleTransient());
container.Register(Types.FromThisAssembly().BasedOn<Controller>().LifestyleTransient());
DependencyResolver.SetResolver(new WindsorMvcDependencyResolver(container));
ControllerBuilder.Current.SetControllerFactory(new WindsorMvcControllerFactory(container.Kernel));
//WebApiInstaller
container.Register(Types.FromThisAssembly().BasedOn<ApiController>().LifestyleTransient());
var resolver = new WindsorResolver(container); //Shown Below
GlobalConfiguration.Configuration.DependencyResolver = resolver;
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new WindsorWebApiControllerActivator(resolver));
}
}
public class WindsorMvcDependencyResolver : IDependencyResolver
{
private readonly IWindsorContainer _container;
public WindsorMvcDependencyResolver(IWindsorContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public object GetService(Type serviceType)
{
return _container.Kernel.HasComponent(serviceType) ? _container.Resolve(serviceType) : null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _container.ResolveAll(serviceType).Cast<object>().ToArray();
}
}
public class WindsorMvcControllerFactory : DefaultControllerFactory
{
private readonly IKernel _kernel;
public WindsorMvcControllerFactory(IKernel kernel)
{
this._kernel = kernel;
}
public override void ReleaseController(IController controller)
{
_kernel.ReleaseComponent(controller);
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.",
requestContext.HttpContext.Request.Path));
}
return (IController)_kernel.Resolve(controllerType);
}
}
internal class WindsorResolver : IDependencyResolver, IDependencyScope, IDisposable
{
private readonly IWindsorContainer _container;
public WindsorResolver(IWindsorContainer container)
{
this._container = container;
}
public IDependencyScope BeginScope()
{
return new WindsorDependencyScope(this._container);
}
public void Dispose()
{
this._container.Dispose();
}
public object GetService(Type serviceType)
{
if (!this._container.Kernel.HasComponent(serviceType))
return (object)null;
else
return this._container.Resolve(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
if (!this._container.Kernel.HasComponent(serviceType))
return (IEnumerable<object>)new object[0];
else
return Enumerable.Cast<object>((IEnumerable)this._container.ResolveAll(serviceType));
}
}
public class WindsorWebApiControllerActivator : IHttpControllerActivator
{
private readonly IDependencyResolver _container;
public WindsorWebApiControllerActivator(IDependencyResolver container)
{
_container = container;
}
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
var scope = _container.BeginScope();
var controller = (IHttpController)scope.GetService(controllerType);
request.RegisterForDispose(scope);
return controller;
}
}
//WebApiConfig.cs
public static class WebApiConfig
{
public static void Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
public static void Register(HttpConfiguration config)
{
// initialize and map all attribute routed Web API controllers (note: this does not enable MVC attribute routing)
config.MapHttpAttributeRoutes();
config.EnsureInitialized();
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new {id = RouteParameter.Optional});
// force JSON responses only (no XML)
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());
}
}
//Global.asax.cs
public class MvcApplication : Sitecore.Web.Application
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
If I add a test ApiController and try to go to '/api/Test' it gives me a 404 everytime. I used RouteDebugger to view whats wrong and I get the below error everytime:
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
I am not sure where "api/sitecore" is coming from. I followed the instructions on [WebApi2 Attribute Routing with Sitecore][1] post as well but unable to get it working. Can someone point me to what I am doing wrong?
Sitecore is using /api/ as its default route URL.
Rename your controller to something else than ApiController or change Sitecore's default route in the Global.asax and web.config
From a quick glance it
looks like there are no routes registered. The only route in WebApiConfig is commented out.
I have created a custom ServiceHostFactory, a ServiceHost, a ServiceBehavior and a ServiceInstanceProvider for DI using unity in my IIS hosted WCF service.
After this UDP discovery has stopped working. How is the ServiceDiscovery behavior created by WCF? Where is the UDPDiscoveryEndpoint created? Am I overriding some behavior in the code below?
public class ServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory
{
protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new ServiceHost(serviceType, baseAddresses);
}
}
public class ServiceHost : System.ServiceModel.ServiceHost
{ ....
protected override void OnOpen(TimeSpan timeout)
{
Description.Behaviors.Add(new ServiceBehavior());
base.OnOpen(timeout);
}
}
public class ServiceBehavior : IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
var cd = cdb as ChannelDispatcher;
if (cd != null)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.InstanceProvider = new ServiceInstanceProvider(serviceDescription.ServiceType);
}
}
}
}
}
public class ServiceInstanceProvider : IInstanceProvider
{...
public object GetInstance(InstanceContext instanceContext, Message message)
{
return Container.Instance.Resolve(_serviceType);
}
}
I am trying to create self-hosted System.ServiceModel.Web.WebServiceHost (.NET 4), however I am running into an issue with the constructor. There are three options:
WebServiceHost() a parameterless constructor that seems pointless, as there's no way to specify the type of the service, or even the contract. Reflecting on it, it doesn't do anything- just an empty default constructor that doesn't call base.
WebServiceHost(object singletonInstance, params Uri[] baseAddresses) I don't want a singleton instance, as this is a InstanceContextMode.PerCall class.
WebServiceHost(System.Type serviceType, params Uri[] baseAddresses) The type I want to instantiate as a service doesn't have a parameterless constructor (which is a requirement of this method). I'm using NInject to push the parameters into the constructor. However, I'm trying to run this as a self-hosted test, so I want to avoid DI.
Are there any options along these lines, or will I have to not self-host?
To support service classes without parameter-less constructors you need to use an IInstanceProvider implementation which knows how to create the service class. The code below shows one with the WebServiceHost, and you can find more about instance providers at http://blogs.msdn.com/b/carlosfigueira/archive/2011/05/31/wcf-extensibility-iinstanceprovider.aspx.
public class StackOverflow_9997163
{
[ServiceContract]
public class Service
{
private int increment;
public Service(int increment)
{
this.increment = increment;
}
[WebGet]
public int Add(int x, int y)
{
return x + y + increment;
}
}
class MyInstanceProvider : IInstanceProvider
{
Func<Service> serviceCreator;
public MyInstanceProvider(Func<Service> serviceCreator)
{
this.serviceCreator = serviceCreator;
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
return this.serviceCreator();
}
public object GetInstance(InstanceContext instanceContext)
{
return this.serviceCreator();
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
}
}
class MyServiceBehavior : IServiceBehavior
{
Func<Service> serviceCreator;
public MyServiceBehavior(Func<Service> serviceCreator)
{
this.serviceCreator = serviceCreator;
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.InstanceProvider = new MyInstanceProvider(this.serviceCreator);
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
int currentIncrement = 1;
host.Description.Behaviors.Add(new MyServiceBehavior(delegate()
{
return new Service(currentIncrement++);
}));
host.Open();
Console.WriteLine("Host opened");
for (int i = 0; i < 10; i++)
{
WebClient c = new WebClient();
Console.WriteLine(c.DownloadString(baseAddress + "/Add?x=6&y=8"));
}
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
I have the following after doing some research on other questions:
MyServiceHost:
public class MyServiceHost : ServiceHost
{
public MyServiceHost(IUnityContainer container, Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
foreach (var cd in this.ImplementedContracts.Values)
{
cd.Behaviors.Add(new DependencyInjectionInstanceProvider(container));
}
}
}
DependencyInjectionInstanceProvider:
public class DependencyInjectionInstanceProvider : IInstanceProvider, IContractBehavior
{
private readonly IUnityContainer container;
public DependencyInjectionInstanceProvider(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}
#region IInstanceProvider Members
public object GetInstance(InstanceContext instanceContext, Message message)
{
return this.GetInstance(instanceContext);
}
public object GetInstance(InstanceContext instanceContext)
{
var serviceType = instanceContext.Host.Description.ServiceType;
return this.container.Resolve(serviceType);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
this.container.Teardown(instance);
}
#endregion
#region IContractBehavior Members
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.InstanceProvider = this;
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
#endregion
}
MyServiceHostFactory:
public class MyServiceHostFactory : ServiceHostFactory
{
private readonly IUnityContainer container;
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new MyServiceHost(this.container, serviceType, baseAddresses);
}
}
Email Service with an attempted Constructor Injection:
public class EmailValidator : IEmailValidator
{
private IFakeDAL fakeDAL;
public EmailValidator(IFakeDAL fakeDAL)
{
this.fakeDAL = fakeDAL;
}
public bool ValidateAddress(string emailAddress)
{
Console.WriteLine("Validating: {0}", emailAddress);
string pattern = #"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*#(([0-9a-zA-Z])+([-\w]*[0-9a-zA-Z])*\.)+[a-zA-Z]{2,9})$";
return Regex.IsMatch(emailAddress, pattern);
}
}
My Console Host to start the Service:
static void Main(string[] args)
{
Type serviceType = typeof(EmailValidator);
Uri serviceUri = new Uri("http://localhost:8080/");
MyServiceHostFactory shf = new MyServiceHostFactory();
ServiceHost host = shf.CreateServiceHost(serviceType, serviceUri);
//ServiceHost host = new ServiceHost(serviceType, serviceUri);
host.Open();
My problem resides in the console host logic. The CreateServiceHost call has a syntax error due to the first argument expecting a Constructor string and not a Type. Which I don't understand since it does accept a Type parameter. In addition to that I don't understand where I should be mapping IFakeDAL to a concrete class. Can I do that in an app.config file or should I register that somewhere else?
ServiceHostFactory is for hosting in IIS. In self hosting you should use your derived ServiceHost directly. Here you have whole example including Unity configuration.
I´m using the following classes in my windows service to create WCF services and inject dependencies to it using unity.
UnityInstanceProvider:
internal class UnityInstanceProvider : IInstanceProvider {
private readonly IUnityContainer container;
private readonly Type contractType;
public UnityInstanceProvider(IUnityContainer container, Type contractType) {
this.container = container;
this.contractType = contractType;
}
public object GetInstance(InstanceContext instanceContext) {
return GetInstance(instanceContext, null);
}
public object GetInstance(InstanceContext instanceContext, Message message) {
return container.Resolve(contractType);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance) {
container.Teardown(instance);
}
}
UnityServiceBehavior:
public class UnityServiceBehavior : IServiceBehavior {
private readonly IUnityContainer container;
public UnityServiceBehavior(IUnityContainer container) {
this.container = container;
}
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 (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers) {
foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) {
if (endpointDispatcher.ContractName != "IMetadataExchange") {
string contractName = endpointDispatcher.ContractName;
ServiceEndpoint serviceEndpoint = serviceDescription.Endpoints.FirstOrDefault(e => e.Contract.Name == contractName);
endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(this.container, serviceEndpoint.Contract.ContractType);
}
}
}
}
}
UnityServiceHost:
public class UnityServiceHost : ServiceHost {
private IUnityContainer unityContainer;
public UnityServiceHost(IUnityContainer unityContainer, Type serviceType)
: base(serviceType) {
this.unityContainer = unityContainer;
}
protected override void OnOpening() {
base.OnOpening();
if (this.Description.Behaviors.Find<UnityServiceBehavior>() == null) {
this.Description.Behaviors.Add(new UnityServiceBehavior(this.unityContainer));
}
}
}
With this classes you can do the following (The configuration of services is done in .config):
UnityContainer container = new UnityContainer();
UnityServiceHost serviceHost = new UnityServiceHost(container, typeof("Type of Service to host"));
serviceHost.Open();
The CreateServiceHost method expects an array of Uri instances, so try this instead:
ServiceHost host = shf.CreateServiceHost(serviceType, new[] { serviceUri });
You can map interfaces to types in either XML or code, but I'd recommend code, since XML has too high a maintenance overhead.
The Main method is an excellent Composition Root, but if you want to configure the container at that level, you'll need to pass it from the Main method to MyServiceHostFactory - which is perfectly fine when you host the service in a console application, but will not work if you want to host it in IIS, where MyServiceHostFactory should be the Composition Root, since IIS requires a default constructor.