1

这可能是一个小众问题,但也许有人可以帮助我。我正在将我的 Web 服务从 ASMX 移植到 WCF,但是我完全建立在 Castle ActiveRecord 上。为了确保我的配置中没有某种奇怪的问题,我使用来自 NuGet 的所有最新 Castle 和 NHibernate 库从头开始构建了一个独立的重现。

我做的第一件事是在Application_Start. 通常我会使用一个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()which 将强制执行新查询,因为该Tasks属性是惰性的。当我这样做时,我得到了异常:

Initializing[NHTest.Project#123] - 无法延迟初始化角色集合: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. 这个模块在每个 HTTP 请求之前运行,并在 ActiveRecord 中创建一个默认会话。但是,在 WCF HTTP 请求之前不会调用此模块。

ASP.NET 兼容模式呢?

您可以使用以下方法启用 ASP.NET 兼容模式:

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

这也将解决问题,并提供对 WCF 中 HTTP 请求管道的其他访问。这将是一种解决方案。但是,尽管它可以在 Visual Studio 测试网络服务器上运行,但我从来没有能够让兼容模式在 IIS7 上运行。另外,我觉得最好的设计是完全在 WCF 基础架构内工作。

我的问题:

Castle ActiveRecord 是否提供在 WCF 请求中创建会话范围的能力?如果是这样,这是如何配置的?

4

1 回答 1

2

在挖掘了 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();
      }
   }
}

for 的构造函数SessionScope将设置当前会话范围,然后可以在该线程中的任何其他位置访问该范围。这使得诸如延迟加载和传递数据读取器之类的事情变得简单。您还会注意到,我在请求期间使用correlationState状态来跟踪对 的引用SessionScope,类似于如何SessionScopeWebModule使用 HttpContext 项目集合。

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

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]

确认线程!

啊,是的,这一切在理论上可能有效,但实际上,IDispatchMessageInspector可能不会与操作在同一个线程中运行。为了解决这个问题,我们需要实现一个 ActiveRecordIWebThreadScopeInfo类。当 ActiveRecord 查找当前会话范围时使用此对象。这通常HttpContext在使用 ASP.NET 时使用 键控,但HybridThreadScopeInfo也能够按线程键控每个会话范围堆栈。两者都不适用于 WCF,因此我们需要一个超级混合实现,它支持 ASP.NET(如果HttpContext.Current存在)、WCF(如果存在)OperationContext.Current,并且当您只是在某个任意线程中运行时支持每个线程。

我的实现(未经高度测试)基于HybridThreadScopeInfo 实现,并进行了一些调整:

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 以使用此类IWebThreadScopeInfoweb.configthreadinfotype<activerecord>ThreadScopeInfoImplementationInPlaceConfigurationSource

希望这对某人有帮助!

于 2012-12-06T17:13:59.627 回答