2012-12-03 64 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/) 。 –

相關問題