The post is divided into two parts. The first post (this post) will be about WCF + LightInject only, so we can have basic infrastructure for WCF + IoC. We will tackle the domain model integration via NHibernate on the second part
These are the seven layers of a WCF Service Application:
* AppService * Dtos * RichDomainModels * RichDomainModelsMapping * ServiceContracts * ServiceImplementations * UnitTestFriendlyDal AppService - WCF Service Application Using LightInject for IoC/DI: Wires NHibernate and UnitTestFriendlyDal Wires IDomainAccessFactory to ServiceImplementations Generates svc based on ServiceImplementations Wires svc to its implementation using AppService.LightInjectServiceHostFactory Dtos - DTOs. References System.Runtime.Serialization only The data that travels across the wire RichDomainModels - References Dtos and UnitTestFriendlyDal only Contains the nouns and verbs of a domain Has no virtual keyword on domain models' members even NHibernate need domain models' members to be virtual. Uses Virtuosity.Fody to automatically make the domain models' members be virtual ORM-agnostic, e.g., has no access to NHibernate. Has access to UnitTestFriendlyDal only Receives and returns DTOs to WCF consumer RichDomainModelsMapping - References NHibernate and RichDomainModels only Maps relational entities to RichDomainModels ServiceContracts - References Dtos and System.ServiceModel only interface for ServiceImplementations ServiceImplementations - References Dtos, ServiceContract, RichDomainModels, UnitTestFriendlyDal and System.ServiceModel Just a thin layer for RichDomainModels, the main process are done on RichDomainModels UnitTestFriendlyDal - Domain Access Layer. References NHibernate only Just a thin layer for ORM(NHibernate), NHibernate's .Query is an extension method, it can't be mocked properly, hence this interface is created
The part 1 of this how-to has three layers only; namely AppService, ServiceContracts and ServiceImplementations
The part 2 has the seven layers detailed above
Let's begin the part 1, this will just take us eight steps
1. Create WCF Service Application, not WCF Service Library, name it AppService
Delete IService1.svc and Service1.svc, we will move the contracts and implementation to their own assemblies
2. Add Class Library project, name it ServiceContracts
Add reference to System.ServiceModel assembly
Delete Class1.cs, then add ISampleService interface, make it public:
using System.ServiceModel; namespace ServiceContracts { [ServiceContract] public interface ISampleService { [OperationContract] string GetGreet(); } }
3. Add another Class Library project, name it ServiceImplementations, add reference to System.ServiceModel assembly too
Add reference to ServiceContracts project
Delete Class1.cs from ServiceImplementations project. Then add ISampleService implementation, name it SampleService, make it public:
using System.ServiceModel; namespace ServiceImplementations { [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] public class SampleService : ServiceContracts.ISampleService { string ServiceContracts.ISampleService.GetGreet() { return "Hey " + System.Guid.NewGuid(); } } }
Here's how interface and implementation should look like:
4. On AppService project, add LightInject from NuGet Packages:
Then add reference to System.ServiceModel.Activation assembly
Let's not make an apple pie from scratch, let's pattern our LightInject WCF dependency-injection from Jimmy Bogard's template for WCF+StructureMap integration. Create a new class file, name it Ioc.cs, then overwrite its content with these:
using System; using System.ServiceModel; using System.ServiceModel.Dispatcher; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Activation; namespace AppService { public static class DependencyFactory { public static LightInject.IServiceContainer Container { get; set; } } public class LightInjectInstanceProvider : IInstanceProvider { readonly Type _serviceType; public LightInjectInstanceProvider(Type serviceType) { _serviceType = serviceType; } object IInstanceProvider.GetInstance(InstanceContext instanceContext) { return Resolve(); } object IInstanceProvider.GetInstance(InstanceContext instanceContext, Message message) { return Resolve(); } object Resolve() { var instance = DependencyFactory.Container.GetInstance(_serviceType); return instance; } void IInstanceProvider.ReleaseInstance(InstanceContext instanceContext, object instance) { var disposable = instance as IDisposable; if (disposable != null) { disposable.Dispose(); } } } public class LightInjectServiceBehavior : IServiceBehavior { void IServiceBehavior.AddBindingParameters( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } void IServiceBehavior.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 LightInjectInstanceProvider(serviceDescription.ServiceType); } } } } void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } } public class LightInjectServiceHost : ServiceHost { public LightInjectServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses) { } protected override void OnOpening() { Description.Behaviors.Add(new LightInjectServiceBehavior()); base.OnOpening(); } } public class LightInjectServiceHostFactory : ServiceHostFactory { protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) { return new LightInjectServiceHost(serviceType, baseAddresses); } } }
5. Add Text Template to the project, name it ServiceHostingEnvironment.tt, then use the following:
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ assembly name="System.ServiceModel" #> <#@ assembly name="$(SolutionDir)\ServiceContracts\bin\Debug\ServiceContracts.dll" #> <#@ assembly name="$(SolutionDir)\ServiceImplementations\bin\Debug\ServiceImplementations.dll" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".config" #> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"> <serviceActivations> <# var serviceImplementations = typeof(ServiceImplementations.SampleService).Assembly.GetTypes() .Where(t => t.GetInterfaces() .Any(i => i.GetCustomAttributes(false) .Any(a => a.GetType() == typeof(System.ServiceModel.ServiceContractAttribute)))); #> <# foreach (var item in serviceImplementations) { string fullName = item.FullName; // example: ServiceImplementations.PersonImplementation+MemberService string[] fullnameSplit = fullName.Split('+'); string schemaName; string className; string serviceFullname; if (fullnameSplit.Length == 2) // The model service is in a schema (e.g., PersonImplementation static class) { schemaName = fullnameSplit[0].Split('.').Last(); className = fullnameSplit[1]; serviceFullname = schemaName + "." + className; } else // domain model is not inside schema { serviceFullname = item.Name; } #> <add relativeAddress="<#=serviceFullname#>.svc" service="<#=item.FullName#>" factory="AppService.LightInjectServiceHostFactory"/> <# } #> </serviceActivations> </serviceHostingEnvironment>
Following is the output of Text Template code above. Filename output is ServiceHostingEnvironment.config:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"> <serviceActivations> <add relativeAddress="SampleService.svc" service="ServiceImplementations.SampleService" factory="AppService.LightInjectServiceHostFactory"/> </serviceActivations> </serviceHostingEnvironment>
On web.config, remove the highlighted line..
..and replace it with:
<serviceHostingEnvironment configSource="ServiceHostingEnvironment.config"/>
You can ignore this warning in the project:
6. On AppService, add reference ServiceContracts and ServiceImplementations
Then add Global Application Class:
Use this in Global Application Class (Global.asax.cs):
using System; using System.Linq; namespace AppService { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { DependencyFactory.Container = new LightInject.ServiceContainer(); RegisterIocs(); } static void RegisterIocs() { //// Instead of manually adding each implementation: //DependencyFactory.Container.Register<ServiceImplementations.ProductImplementation.ProductService>(new LightInject.PerRequestLifeTime()); //DependencyFactory.Container.Register<ServiceImplementations.PersonImplementation.MemberService>(new LightInject.PerRequestLifeTime()); // Just do this: var serviceImplementations = typeof(ServiceImplementations.SampleService).Assembly.GetTypes() .Where(t => t.GetInterfaces() .Any(i => i.GetCustomAttributes(false) .Any(a => a.GetType() == typeof(System.ServiceModel.ServiceContractAttribute)))); foreach (var item in serviceImplementations) { DependencyFactory.Container.Register(item, new LightInject.PerRequestLifeTime()); } } } }
7. Run the AppService
You'll notice there is no .svc file(s) in the WCF Service application, it's served dynamically
To verify it exists, visit .svc on the browser: http://localhost:50549/SampleService.svc, here's the result:
8. Last step. To test, run WcfTestCLient, path is: "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\WcfTestClient.exe"
Click GetGreet, then click Invoke. You shall see the following output:
On second part of this post, I will show how to integrate NHibernate on this WCF Service application
Complete code at: https://github.com/MichaelBuen/PlayWcfServiceApplication
Happy Coding!
No comments:
Post a Comment