|
WcfIntegration
Autofac can host services in a WCF server, and can increase the reliability of WCF clients.
Autofac2 Getting StartedAutofac.Integration.Wcf.dll is included in the Autofac binary downloads. To use the integration you need to reference it as well as Autofac.dll in your project. ClientsThe proxy objects returned from ChannelFactory.CreateChannel<T>() need to be disposed before client and server resources assigned to the channel are freed. This is where Autofac's deterministic disposal really shines. To take advantage of this, register and use your channel factories through a container client-side. RegistrationSomething like the following needs to run during application startup: var builder = new ContainerBuilder();
builder.Register(c => new ChannelFactory<ITrackListing>(
new BasicHttpBinding(),
new EndpointAddress("http://localhost/TrackListingService")))
.SingleInstance();
builder.Register(c => c.Resolve<ChannelFactory<ITrackListing>>().CreateChannel())
.UseWcfSafeRelease();
// Another application class not using WCF
builder.RegisterType<AlbumPrinter>();
var container = builder.Build();The Register() method infers the ITrackListing service type from the return value of the expression, which is the result of channelFactory.CreateChannel(). The call to CreateChannel() isn't executed until ITrackListing is requested from the container. The UseWcfSafeRelease() configuration option to ensures that exception messages are not lost when disposing client channels. Using the ServiceOur application prints a track listing to the console using the remote ITrackListing service. It does this via the AlbumPrinter class: class AlbumPrinter
{
readonly ITrackListing _trackListing;
public AlbumPrinter(ITrackListing trackListing)
{
_trackListing = trackListing;
}
public void PrintTracks(string artist, string album)
{
foreach (var track in _trackListing.GetTracks(artist, album))
Console.WriteLine("{0} - {1}", track.Position, track.Title);
}
}If you haven't read up on nested containers, you might want to check out the documentation now. using(var lifetime = container.BeginLifetimeScope())
{
var albumPrinter = inner.Resolve<AlbumPrinter>();
albumPrinter.PrintTracks("The Shins", "Wincing the Night Away");
}The important thing to note in this small example is that neither the client code of the album printer, nor the album printer itself, has to be aware that the ITrackListing service is in fact a WCF service and needs to be disposed. The container takes care of this housekeeping automatically when inner is disposed by the using block. Servers Hosted in IISTo use the integration from an IIS-hosted application, reference both Autofac.dll and Autofac.Integration.Wcf.dll. After that, the pattern is the same:
The choice you have is in how you want to register your services and specify the target in your .svc files. An example is included in the /trunk/examples folder. Implementation Type RegistrationYour first option is to simply register the service implementation type in the container and specify that implementation type in the .svc file. This is the most common usage. In your application startup, you'd have code like this: var builder = new ContainerBuilder(); builder.RegisterType<TestService.Service1>(); AutofacHostFactory.Container = builder.Build(); And your .svc file would specify the appropriate service implementation type and host factory, like this: <%@ ServiceHost
Service="TestService.Service1, TestService"
Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf" %>(Note that you need to use the fully-qualified name of your service in the .svc file, i.e Service="Namespace.ServiceType, AssemblyName") Contract Type RegistrationYour second option is to register the contract type in the container and specify the contract in the .svc file. This is handy if you don't want to change the .svc file but do want to change the implementation type that will handle requests. In your application startup, you'd have code like this: var builder = new ContainerBuilder(); builder.RegisterType<TestService.Service1>().As<TestService.IService1>(); AutofacHostFactory.Container = builder.Build(); And your .svc file would specify the service contract type and host factory, like this: <%@ ServiceHost
Service="TestService.IService1, TestService"
Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf" %>(Note that you need to use the fully-qualified name of your contract in the .svc file, i.e Service="Namespace.!IContractType, AssemblyName") Named Service RegistrationThe final option you have is to register a named service implementation in the container and specify that service name in the .svc file. This is handy if you want even further abstraction away from the .svc file. In your application startup, you'd have code like this: var builder = new ContainerBuilder();
builder.RegisterType<TestService.Service1>().Named<object>("my-service");
AutofacHostFactory.Container = builder.Build();Note that the service implementation type is registered as an object - this is important. Your service implementation won't be found if it's a named service and it's not registered as an object. Your .svc file specifies the service name you registered and host factory, like this: <%@ ServiceHost
Service="my-service"
Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf" %>Self-HostingTo use the integration when self-hosting your WCF Service, reference both Autofac.dll and Autofac.Integration.Wcf.dll. Below is a sample of a web service that has a constructor dependency on an ILogger instance. The interface and implementation for the logger that will be the dependency. public interface ILogger
{
void Write(string message);
}
public class Logger : ILogger
{
public void Write(string message)
{
Console.WriteLine(message);
}
}The contract and implementation for the web service. [ServiceContract]
public interface IEchoService
{
[OperationContract]
string Echo(string message);
}
public class EchoService : IEchoService
{
private readonly ILogger _logger;
public EchoService(ILogger logger)
{
_logger = logger;
}
public string Echo(string message)
{
_logger.Write(message);
return message;
}
}The Console Application code for building the container and hosting the web service. ContainerBuilder builder = new ContainerBuilder();
builder.Register(c => new Logger()).As<ILogger>();
builder.Register(c => new EchoService(c.Resolve<ILogger>())).As<IEchoService>();
using (IContainer container = builder.Build())
{
Uri address = new Uri("http://localhost:8080/EchoService");
ServiceHost host = new ServiceHost(typeof(EchoService), address);
host.AddServiceEndpoint(typeof(IEchoService), new BasicHttpBinding(), string.Empty);
host.AddDependencyInjectionBehavior<IEchoService>(container);
host.Description.Behaviors.Add(new ServiceMetadataBehavior {HttpGetEnabled = true, HttpGetUrl = address});
host.Open();
Console.WriteLine("The host has been opened.");
Console.ReadLine();
host.Close();
Environment.Exit(0);
}WAS Hosting and Non-HTTP ActivationWhen hosting WCF Services in WAS (Windows Activation Service), you are not given an opportunity to build your container in the Application_Start event defined in your Global.asax because WAS doesn't use the standard ASP.NET pipeline. The alternative approach is to place a code file in your App_Code folder that contains a type with a public static void AppInitialize() method. namespace MyNamespace
{
public static class AppStart
{
public static void AppInitialize()
{
// Put your container initialization here.
}
}
}You can read more about AppInitialize() in "How to Initialize Hosted WCF Services". Handling InstanceContextMode.Single ServicesUsing InstanceContextMode.Single is not a good idea from a scalability point of view, and allowing multiple callers to access the single instance using ConcurrencyMode.Multiple means that you also need to be careful about multiple threads accessing any shared state. If possible you should create services with InstanceContextMode.PerCall. IIS HostedThe AutofacServiceHostFactory identifies WCF services that are marked with InstanceContextMode.Single and will ensure that the ServiceHost can be provided with a singleton instance from the container. An exception will be thrown if the service in the container was not registered with the SingleInstance lifetime scope. It is also invalid to register a SingleInstance service in the container for a WCF service that is not marked as InstanceContextMode.Single. Self HostedIt is possible to manually perform constructor injection for service marked with InstanceContextMode.Single when self-hosting. This is achieved by resolving a SingleInstance service from the container and then passing that into the constructor of a manually created ServiceHost. // Get the SingleInstance from the container.
IEchoService echoService = container.Resolve<IEchoService>();
// Pass it into the ServiceHost preventing it from creating an instance with the default constructor.
ServiceHost host = new ServiceHost(echoService, new Uri("http://localhost:8080/EchoService")); ResourcesSee the original discussion thread. | |
Hi Nicholas, I have a problem with the binding configuration. This config above has no effect, do you know why?
// AssinadorService builder.Register(c => new ChannelFactory<IAssinadorService>( new BasicHttpBinding() { CloseTimeout = new TimeSpan(0, 5, 0), OpenTimeout = new TimeSpan(0, 5, 0), ReceiveTimeout = new TimeSpan(0, 10, 0), SendTimeout = new TimeSpan(0, 10, 0), AllowCookies = false, BypassProxyOnLocal = true, HostNameComparisonMode = HostNameComparisonMode.StrongWildcard, MaxBufferSize = int.MaxValue, MaxBufferPoolSize = int.MaxValue, MaxReceivedMessageSize = int.MaxValue, MessageEncoding = WSMessageEncoding.Text, TextEncoding = System.Text.Encoding.UTF8, TransferMode = TransferMode.Buffered, UseDefaultWebProxy = true }, new EndpointAddress(ConfigurationManager.AppSettings["AssinadorServiceUrl"]))); builder.Register(c => c.Resolve<ChannelFactory<IAssinadorService>>().CreateChannel() ).FactoryScoped(); builder.Register<NfeUtilV1>().OnActivating(ActivatingHandler.InjectProperties).WithScope(Autofac.InstanceScope.Factory); Autofac.Integration.Wcf.AutofacServiceHostFactory.Container = builder.Build();Thanks,
Rodrigo Lima
I Find a solution:
// AssinadorService builder.Register(c => new ChannelFactory<IAssinadorService>("basic")); // endpointConfigurationName builder.Register(c => c.Resolve<ChannelFactory<IAssinadorService>>().CreateChannel() ).FactoryScoped(); builder.Register<NfeUtilV1>().OnActivating(ActivatingHandler.InjectProperties).WithScope(Autofac.InstanceScope.Factory); Autofac.Integration.Wcf.AutofacServiceHostFactory.Container = builder.Build();I would be very interested in something like System.ServiceModel?.Activation.WebScriptServiceHostFactory?, which automatically adds ASP.NET AJAX endpoint to a service (it serializes to JSON, not to SOAP), but is implemented with AutoFac? and supports property injection (like the one in Pages) and some transaction management (so I could add an attribute Transactional? to the method.
I would like to point out that
<%@ ServiceHost Service="TestService.Service1, TestService" Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf" %>is for basicHttpBinding and wsHttpbinding (SOAP web services).
<%@ ServiceHost Service="TestService.Service1, TestService" Factory="Autofac.Integration.Wcf.AutofacWebServiceHostFactory, Autofac.Integration.Wcf" %>Works for webHttpBinding (RESTful web services).
hi, i have a problem using autofac with wcf. If i use the example of this site all are correct. If i create a new wcf project in my visualstudio 2010 beta 2 with framework 4.0 and recreate from scratch the same example i have this error: The service 'Service1' configured for WCF is not registered with the Autofac container.
stack trace: Autofac.Integration.Wcf.AutofacHostFactory?.CreateServiceHost?(String constructorString, Uri baseAddresses) in D:\projects\Autofac-2.1.10.754-NET40\autofac\src\Source\Autofac.Integration.Wcf\AutofacHostFactory?.cs:75
Can anyone help me?
Thanks felix
Hi Felix, it looks like you need to use the fully-qualified name of your service in the .SVC file; i.e. "TestService?.Service1, TestService?" - HTH, Nick
thanks, now works! felix
The WCF example here appears to rely on the IDisposable interface of the client channel, which is generally considered broken (http://msdn.microsoft.com/en-us/library/aa355056.aspx).
When attempting to host a WCF Autofac Integrated Service in IIS (for backing a Ajax page) I get a Security Exception about partial trust.
Details here: http://stackoverflow.com/questions/2429541/autofac-wcf-integration-security-problem
Anyone else seeing this behavior?
I seem to be missing a piece in the puzzle: how does Autofac actually inject any service instances into my WCF service? Are they being passed in the constructor or are they injected as properties?
Maybe this question is obsolete once I find out why my Service is initialized by the System.ServiceModel.Dispatcher.InstanceBehavior.GetInstance() method and not the AutofacWebServiceHostFactory... (this is what I see when I debug the WCF service).
Can anybody provide me with the missing info? This is all in Autofac 1.4.5.
Thanks in advance, Oliver
P.S. Just cross-posted my question with more info here: http://stackoverflow.com/questions/3355937/using-autofac-for-di-into-wcf-service-hosted-in-asp-net-application
Is there any work around to having to include the fully-qualified name of the service in the .svc file? I want to target a different assembly based on build definition and having to specify the assembly name in the svc file makes this difficult.
I am now using configuration based activation and different config files depending upon the build definition. This gives me what I wanted.
Hi!
Earlier in the wiki I've found: WCF integrations are set up so that InstancePerLifetimeScope?() will attach a component to the current web request or service method call.
This lifetime is too short for me. I'm using WCF web services in session mode:
[ServiceBehaviorAttribute(InstanceContextMode=InstanceContextMode.PerSession)] public class MyService : IMyService { [...] }In the session context (actually the service instance) I've some state from preceding service calls. Other components and subsystems created on the fly in an unit of work context (per service method call) have dependencies on the components created (as single instance) in the session context. I would like to safe the session state in the container and inject it to underlying services.
How can I solve this problem with autofac and WCF integrations?
Hi,
I am using autofac wcf integration in our web service project. I am experiencing a problem when the same instance of the WCF service is served back to a new request. With the first web request autofacWcfServiceHostFactory comes in to provide the service instance. The service instance has a sessionId attached to it. I recorded the session id for my reference. In my next request I get the same session id with the same dependency objects. All my WCF services are per call services. Can you throw some light on why am I getting same instance or am I looking at the wrong place. How does the object gets released in this case.
Thank You!
using(var lifetime = container.BeginLifetimeScope()) { var albumPrinter = inner.Resolve<AlbumPrinter>(); albumPrinter.PrintTracks("The Shins", "Wincing the Night Away"); }should be
using(var lifetime = container.BeginLifetimeScope()) { var albumPrinter = lifetime.Resolve<AlbumPrinter>(); albumPrinter.PrintTracks("The Shins", "Wincing the Night Away"); }quote: "In your application startup, you'd have code like this"
What would exactly be the preferred method if I have a WCF Web Application? Application_Start? doesn't seem to fire with SVC only projects, any ideas?
I am really happy to know why you have to use DI for WCF? How can you benefit from it? Don't use technology for the sake of that. Too much DI can only lead to maintenance issue even DI is supposed to alleviate it but actually aggrieve.
I want to do something similar to what u did in the Self-Hosting example i.e. pass a Logger to my service and also add a custom ServiceBehavior?. But I want to pass the logger instance to the custom ServiceBehavior?, which then passes the Logger instance to my ErrorHandler? class. Also, my service is hosted in IIS so I cannot create a new ServiceHost?. I'm assuming Autofaq creates the service host. How do I add the custom ServiceBehaior? and pass a logger instance to it in my case?