Tuesday, October 28, 2014

WCF + LightInject + NHibernate (part 1 of 2)

On this post I'll how you how to integrate WCF + LightInject + NHibernate

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