Wcf NHibernate Session management

2019-07-18 02:53发布

问题:

I'm new to Castle, NHibernate and WCF.

I implemented the session management for my MVC application based on the following article because it seemd to be the most advanced implementation of all posts I've read so far : http://nhibernate.info/blog/2011/03/02/effective-nhibernate-session-management-for-web-apps.html

The only problem I got was that this uses some Asp.net specific functionality that isn't available in my WCF service like (HttpContext.Current.Items).

I started to use WcfFacility

 public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.AddFacility<WcfFacility>().Register
    (

         Component.For<IRepository>().ImplementedBy(typeof(RepositoryBase<,>)),
         Component.For<ITimeService>()
                  .ImplementedBy<myTimeMvc.Webservice.TimeService>()
                  .Named("myTimeMvc.Webservice.TimeService"));


        container.Register(
        Component.For<IServiceBehavior>()
            .ImplementedBy<WcfSessionPerRequestBehavior>()
        );
    }

My Persistance configuration:

 public class PersistenceInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {

        container.Kernel.AddFacility<TypedFactoryFacility>();

        container.Register(Component.For<ISessionFactory>().UsingFactoryMethod(k => CreateNhSessionFactory()));

        container.Register(Component.For<ISessionFactoryProvider>().AsFactory());

        container.Register(Component.For<IEnumerable<ISessionFactory>>().UsingFactoryMethod(k => k.ResolveAll<ISessionFactory>()));

        container.Register(Classes.FromAssembly(Assembly.GetAssembly(typeof(HdtRepository))).InSameNamespaceAs<HdtRepository>().WithService.DefaultInterfaces().LifestyleTransient());
    }

    /// <summary>
    /// Creates NHibernate Session Factory.
    /// </summary>
    /// <returns>NHibernate Session Factory</returns>
    private static ISessionFactory CreateNhSessionFactory()
    {
        var connStr = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
        return Fluently.Configure()


            .Database(
                        MsSqlConfiguration.MsSql2008
                        .UseOuterJoin()
                        .ConnectionString(x => x.FromConnectionStringWithKey("DefaultConnection"))
                        .ShowSql()
            )
            .Mappings(m => m.FluentMappings.AddFromAssemblyOf<TimeRecord>())
           .ExposeConfiguration(cfg =>
               cfg.Properties[Environment.CurrentSessionContextClass] = typeof(LazySessionContext).AssemblyQualifiedName

               )


            .BuildSessionFactory();
    }

}

Then i tried to solve the problem with "HttpContext.Current.Items" by adding a custom extension:

namespace MyTimeService.WcfExtension
{
///<summary>
/// This class incapsulates context information for a service instance
///</summary>
public class WcfInstanceContext : IExtension<InstanceContext>
{
    private readonly IDictionary items;

    private WcfInstanceContext()
    {
        items = new Hashtable();
    }

    ///<summary>
    /// <see cref="IDictionary"/> stored in current instance context.
    ///</summary>
    public IDictionary Items
    {
        get { return items; }
    }

    ///<summary>
    /// Gets the current instance of <see cref="WcfInstanceContext"/>
    ///</summary>
    public static WcfInstanceContext Current
    {
        get
        {
            WcfInstanceContext context =      OperationContext.Current.InstanceContext.Extensions.Find<WcfInstanceContext>();
            if (context == null)
            {
                context = new WcfInstanceContext();
                OperationContext.Current.InstanceContext.Extensions.Add(context);
            }
            return context;
        }
    }

    /// <summary>
    /// <see cref="IExtension{T}"/> Attach() method
    /// </summary>
    public void Attach(InstanceContext owner) { }

    /// <summary>
    /// <see cref="IExtension{T}"/> Detach() method
    /// </summary>
    public void Detach(InstanceContext owner) { }
}
}

registered the following way:

<extensions>
  <behaviorExtensions>
    <add name="WcfInstanceContext" type="MyTimeService.WcfExtension, MyTimeService.WcfExtension.WcfInstanceContext" />
  </behaviorExtensions>
</extensions>

Then I created a custom ServiceBehavior

 public class WcfSessionPerRequestBehavior : IServiceBehavior
{
    private ISessionFactoryProvider _sfp;


    public WcfSessionPerRequestBehavior(ISessionFactoryProvider sfp)
    {
        _sfp = sfp;
    }

    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 (var cdb in serviceHostBase.ChannelDispatchers)
        {
            var channelDispatcher = cdb as ChannelDispatcher;
            if (null != channelDispatcher)
            {
                foreach (var endpointDispatcher in channelDispatcher.Endpoints)
                {
                    foreach (var dispatchOperation in endpointDispatcher.DispatchRuntime.Operations)
                    {
                        dispatchOperation.CallContextInitializers.Add(new WcfSessionPerRequestCallContextInitializer(_sfp));
                    }
                }
            }
        }
    }

followed by a custom ICallContextInitializer:

  public class WcfSessionPerRequestCallContextInitializer : ICallContextInitializer
{
    private ILogger logger = NullLogger.Instance;

    public ILogger Logger
    {
        get { return logger; }
        set { logger = value; }
    }


    private ISessionFactoryProvider sfp;


    public WcfSessionPerRequestCallContextInitializer(ISessionFactoryProvider s)
    {
        this.sfp = s;

    }

    public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
    {
        foreach (var sf in sfp.GetSessionFactories())
        {
            var localFactory = sf;
            LazySessionContext.Bind(new Lazy<NHibernate.ISession>(() => BeginSession(localFactory)), sf);
        }
        return null;

    }

    public void AfterInvoke(object correlationState)
    {
        foreach (var sf in sfp.GetSessionFactories())
        {
            var session = LazySessionContext.UnBind(sf);
            if (session == null) continue;
            EndSession(session);
        }
    }

    private static NHibernate.ISession BeginSession(ISessionFactory sf)
    {
        var session = sf.OpenSession();
        session.BeginTransaction();
        return session;
    }

    private void ContextEndRequest(object sender, EventArgs e)
    {
        foreach (var sf in sfp.GetSessionFactories())
        {
            var session = LazySessionContext.UnBind(sf);
            if (session == null) continue;
            EndSession(session);
        }
    }

    private static void EndSession(NHibernate.ISession session)
    {
        if (session.Transaction != null && session.Transaction.IsActive)
        {
            session.Transaction.Commit();
        }
        session.Dispose();
    }
}

and at last I adjusted the ICurrentSessionContext:

  public class LazySessionContext : ICurrentSessionContext
{
    private readonly ISessionFactoryImplementor factory;
    private const string CurrentSessionContextKey = "NHibernateCurrentSession";

    public LazySessionContext(ISessionFactoryImplementor factory)
    {
        this.factory = factory;
    }

    /// <summary>
    /// Retrieve the current session for the session factory.
    /// </summary>
    /// <returns></returns>
    public NHibernate.ISession CurrentSession()
    {
        Lazy<NHibernate.ISession> initializer;
        var currentSessionFactoryMap = GetCurrentFactoryMap();
        if (currentSessionFactoryMap == null || !currentSessionFactoryMap.TryGetValue(factory, out initializer))
        {
            return null;
        }
        return initializer.Value;
    }

    /// <summary>
    /// Bind a new sessionInitializer to the context of the sessionFactory.
    /// </summary>
    /// <param name="sessionInitializer"></param>
    /// <param name="sessionFactory"></param>
    public static void Bind(Lazy<NHibernate.ISession> sessionInitializer, ISessionFactory sessionFactory)
    {
        var map = GetCurrentFactoryMap();
        map[sessionFactory] = sessionInitializer;
    }

    /// <summary>
    /// Unbind the current session of the session factory.
    /// </summary>
    /// <param name="sessionFactory"></param>
    /// <returns></returns>
    public static NHibernate.ISession UnBind(ISessionFactory sessionFactory)
    {
        var map = GetCurrentFactoryMap();
        var sessionInitializer = map[sessionFactory];
        map[sessionFactory] = null;
        if (sessionInitializer == null || !sessionInitializer.IsValueCreated) return null;
        return sessionInitializer.Value;
    }

    /// <summary>
    /// Provides the CurrentMap of SessionFactories.
    /// If there is no map create/store and return a new one.
    /// </summary>
    /// <returns></returns>
    private static IDictionary<ISessionFactory, Lazy<NHibernate.ISession>> GetCurrentFactoryMap()
    {

        //var currentFactoryMap = (IDictionary<ISessionFactory, Lazy<NHibernate.ISession>>)HttpContext.Current.Items[CurrentSessionContextKey];


        var currentFactoryMap = (IDictionary<ISessionFactory, Lazy<NHibernate.ISession>>)WcfInstanceContext.Current.Items[CurrentSessionContextKey];

        if (currentFactoryMap == null)
        {
            currentFactoryMap = new Dictionary<ISessionFactory, Lazy<NHibernate.ISession>>();
            WcfInstanceContext.Current.Items[CurrentSessionContextKey] = currentFactoryMap;
        }
        return currentFactoryMap;
    }
}

This seems to work but since I'm new to all that stuff I can't say if I did this corretly. Can anyone have a look at it and give me feedback?

Cheers, Stefan

回答1:

Your are using OperationContext.Current which is the correct way to do per request context implementations for WCF services, so it looks good to me...

Question is, why are you not simply using the default implementation which comes out of the box with nhibernate? The implementation is in NHibernate.Context.WcfOperationSessionContext and you would just have to use this one within your session factory setup

For example:

Fluently.Configure()
    ...
    .ExposeConfiguration(cfg => cfg.SetProperty(
                                    Environment.CurrentSessionContextClass,
                                    "wcf")

or Fluently.Configure()...CurrentSessionContext<WcfOperationSessionContext>()



回答2:

You could also simply setaspNetCompatibilityEnabled=true and you will get HttpContext available to both MVC and WCF.