2012-12-03 67 views
1

这可能是一个小生境问题,但也许有人可以帮助我。我将我的Web服务从ASMX移植到WCF,但是我完全基于Castle ActiveRecord构建。为了确保我的配置中没有某种奇怪的问题,我使用NuGet中所有最新的Castle和NHibernate库从零开始构建了一个独立的repro。Castle ActiveRecord没有初始化WCF会话

我做的第一件事是在Application_Start中初始化ActiveRecord。通常我会使用一个web.config实例,但是这可能不应该的问题:

protected void Application_Start(object sender, EventArgs e) 
{ 
    //NHibernate.Driver.OracleClientDriver 
    IDictionary<string, string> properties = new Dictionary<string, string>(); 

    properties.Add("connection.driver_class", "NHibernate.Driver.OracleClientDriver"); 
    properties.Add("dialect", "NHibernate.Dialect.Oracle10gDialect"); 
    properties.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider"); 
    properties.Add("connection.connection_string", "user Id=xxx;password=xxx;server=localhost;persist security info=True"); 
    properties.Add("proxyfactory.factory_class", "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"); 

    InPlaceConfigurationSource source = new InPlaceConfigurationSource(); 
    source.IsRunningInWebApp = true; 
    source.ThreadScopeInfoImplementation = typeof(Castle.ActiveRecord.Framework.Scopes.HybridWebThreadScopeInfo); 

    source.Add(typeof(ActiveRecordBase), properties); 

    ActiveRecordStarter.Initialize(source, typeof(Task), typeof(Project)); 
} 

通知我还使用了HybridWebThreadScopeInfo实现,因为HttpContext.Current将在WCF空。

接下来,我实现了我的web服务:

public class Service1 : IService1 
{ 
    public string GetData(int value) 
    { 
     Project p; 

     p = Project.Find(123M); 
     var count = p.Tasks.Count(); //Force count query 

     return p.GoldDate.ToString(); 
    } 
} 

当我打电话Project.Find(),它工作正常。接下来,我打电话p.Tasks.Count()这将强制一个新的查询,因为Tasks属性是懒惰的。当我这样做,我得到异常:

初始化[NHTest.Project#123] -failed懒洋洋地初始化角色的集合:NHTest.Project.Tasks,没有会话或会话关闭

原因这是因为没有会话范围。我猜想内部ActiveRecordBase方法将创建一个会话,如果它不存在或什么的。现在,我可以用此手动创建一个:

public string GetData(int value) 
{ 
    Project p; 

    using (new SessionScope()) 
    { 
     p = Project.Find(123M); 
     var count = p.Tasks.Count(); //Force count query 

     return p.GoldDate.ToString(); 
    } 
} 

这会工作的很好。不过,我想不必在我的所有代码中都这样做,因为它在ASP.NET Web服务中完美工作。

那么,为什么它在ASP.NET中工作?

原因是工程是因为ActiveRecord带有一个叫做Castle.ActiveRecord.Framework.SessionScopeWebModule的httpModule。此模块在每个HTTP请求之前运行,并在ActiveRecord中创建一个默认会话。但是,在WCF HTTP请求之前调用此模块是而不是

怎么样ASP.NET兼容模式?

可以使用启用ASP.NET兼容模式:

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" ... /> 

这也将解决这个问题,并提供其他访问中WCF的HTTP请求管道。这将是一个解决方案。但是,尽管它在Visual Studio测试Web服务器上工作,但我始终无法获得适用于IIS7的兼容模式。另外,我觉得最好的设计是在WCF基础架构内完全工作。

我的问题:

确实城堡的ActiveRecord提供了WCF请求中创建会话作用域的能力吗?如果是这样,这是如何配置的?

回答

1

在挖掘了Castle ActiveRecord源代码后,我发现这个答案是否定的; ActiveRecord确实有而不是有任何种类的原生WCF支持。但是,由于ActiveRecord负责处理所有凌乱的NHibernate细节,例如配置和会话工厂,因此处理起来并不复杂。实际上,为每个WCF操作自动创建会话作用域与实现自定义IDisplayMessageInspector非常直接。此消息检查只是有新的一个SessionScope对象接收到一个请求时,和处置它时,请求结束:

public class MyInspector : IDispatchMessageInspector 
{ 
    public MyInspector() 
    { 
    } 

    public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext) 
    { 
     return new SessionScope(); 
    } 

    public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState) 
    { 
     SessionScope scope = correlationState as SessionScope; 

     if (scope != null) 
     { 
     scope.Flush(); 
     scope.Dispose(); 
     } 
    } 
} 

的构造SessionScope把当前会话范围,这是再访问其他地方在那个线程中。这使得延迟加载和传递数据读取器的工作变得简单。您还会注意到,在请求期间,我使用correlationState状态跟踪对SessionScope的引用,类似于SessionScopeWebModule如何使用HttpContext项目集合。

IDispatchInspector可以在web.config中来配置,或者附连到WCF服务通过Attribute类:

public class MyServiceBehaviorAttribute : Attribute, IServiceBehavior 
{ 
    public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) 
    { 
    } 

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) 
    { 
     foreach (ChannelDispatcher cDispatcher in serviceHostBase.ChannelDispatchers) 
     foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints) 
      eDispatcher.DispatchRuntime.MessageInspectors.Add(new MyInspector()); 

    } 

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) 
    { 
    } 
} 

然后当然标签的服务类[MyServiceBehavior]

Ack Threads!

啊是的,这一切都可能理论上工作,但实际上,IDispatchMessageInspector可能不会运行在同一个线程作为操作。为了解决这个问题,我们需要实现一个ActiveRecord IWebThreadScopeInfo类。 ActiveRecord查找当前会话作用域时使用此对象。这通常在使用ASP.NET时使用HttpContext来键控,但HybridThreadScopeInfo也能够按线程键入每个会话作用域堆栈。既不适用于WCF,所以我们需要一个支持ASP.NET(当存在HttpContext.Current时),WCF(当OperationContext.Current)存在时的超级混合实现,以及当你在某个任意线程中运行时的每线程。

我的实现(不是高度测试)是基于HybridThreadScopeInfo implementation了一些调整:

public class WcfThreadScopeInfo : AbstractThreadScopeInfo, IWebThreadScopeInfo 
{ 
    const string ActiveRecordCurrentStack = "activerecord.currentstack"; 

    [ThreadStatic] 
    static Stack stack; 

    public override Stack CurrentStack 
    { 
     [MethodImpl(MethodImplOptions.Synchronized)] 
     get 
     { 
     Stack contextstack; 

     if (HttpContext.Current != null) //We're running in an ASP.NET context 
     { 
      contextstack = HttpContext.Current.Items[ActiveRecordCurrentStack] as Stack; 
      if (contextstack == null) 
      { 
       contextstack = new Stack(); 
       HttpContext.Current.Items[ActiveRecordCurrentStack] = contextstack; 
      } 

      return contextstack; 
     } 

     if (OperationContext.Current != null) //We're running in a WCF context 
     { 
      NHibernateContextManager ctxMgr = OperationContext.Current.InstanceContext.Extensions.Find<NHibernateContextManager>(); 
      if (ctxMgr == null) 
      { 
       ctxMgr = new NHibernateContextManager(); 
       OperationContext.Current.InstanceContext.Extensions.Add(ctxMgr); 
      } 

      return ctxMgr.ContextStack; 
     } 

     //Working in some random thread 
     if (stack == null) 
     { 
      stack = new Stack(); 
     } 

     return stack; 
     } 
    } 
} 

您还需要一个IExtension<InstanceContext>派生类,WCF使用一个操作中的数据存储背景:

public class NHibernateContextManager : IExtension<InstanceContext> 
{ 
    public Stack ContextStack { get; private set; } 

    public NHibernateContextManager() 
    { 
     this.ContextStack = new Stack(); 
    } 

    public void Attach(InstanceContext owner) 
    { 
    } 

    public void Detach(InstanceContext owner) 
    { 
    } 
} 

然后,您可以配置的ActiveRecord通过设置在<activerecord> conf下threadinfotype属性来使用这个IWebThreadScopeInfoweb.config如果您使用的是InPlaceConfigurationSource对象,则可以使用配置节点或ThreadScopeInfoImplementation属性。

希望这可以帮助别人!

+0

如果你想要*吨*的细节,还写了一个[博客文章](http://blog.kitchenpc.com/2012/12/06/wcf-and-activerecord-lets-be-friends/) 。 –

相关问题