2014-10-01 40 views
2

我目前有一個簡單的用例。城堡溫莎WCF設施不處理單向操作

1),使用城堡的AsWcfClient選項連接到WCF服務的客戶端應用程序。

2)WCF服務「A」正在使用城堡託管並且被注入的單一相關性。此依賴關係是另一個WCF服務(稱爲服務「B」)的客戶端代理。

3)服務「B」做了一些工作。

形象化:客戶端 - >服務 「A」 與青山注入代理 - >服務 「B」

簡單吧?沒有問題的工作IF,這是一個很大的,如果服務「B」主機啓動並運行。

我所看到的和可以重現上需求的行爲,如果服務「B」已關閉,調用鏈沒有任何跡象表明有任何問題完成。換句話說,Castle沒有引發解析異常,也沒有任何WCF異常。我已經將它隔離爲如何處理IsOneWay = true操作。

這是一個重大的問題,因爲你覺得一切都已經正確執行,但在現實中沒有你的代碼已經被執行!

這是預期的行爲?有沒有我可以在Castle中打開一些選項,這樣當WCF客戶端代理被解析的依賴關係時它會拋出異常?其他選項?

還有一點需要注意的是,您遇到的唯一線索就是問題出現時,您是否在客戶端代理上執行Container.Release()時發生異常。這不能因爲你不值得進入的各種原因而依賴你。

謝謝!另外下面

是再現了這個問題的代碼。要運行它 1)在Visual Studio 2從下面創建一個新的單元測試項目)通過的NuGet 3添加溫莎城堡WCF集成工具)將代碼粘貼到一個cs文件,一切都在一個可以很容易。 4)運行兩個單元測試,SomeOperation_With3Containers_NoException()作爲依賴服務(上面的服務「B」)運行。 SomeOperation_With2Containers_NoException()失敗。釋放 5)設置斷點,你可以看到沒有代碼在實現中被命中。

****更新****:這需要處理的主要方式是使用IErrorHandler植入(正如在下面的評論中羅馬提到的)。可以在此處找到詳細信息和示例:http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.110).aspx

使用此實現記錄單向操作中的任何異常並使用該數據採取適當的操作。

using Castle.Facilities.WcfIntegration; 
using Castle.MicroKernel.Registration; 
using Castle.Windsor; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using System; 
using System.ServiceModel; 
using System.ServiceModel.Description; 

namespace UnitTestProject1 
{ 
    [ServiceContract] 
    public interface IServiceContractA 
    { 
     [OperationContract(IsOneWay = true)] 
     void SomeOperation(); 
    } 

[ServiceContract] 
public interface IServiceDependancy 
{ 
    [OperationContract] 
    void SomeOperation(); 
} 

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] 
public class ServiceContractAImplementation : IServiceContractA 
{ 
    private IServiceDependancy ServiceProxy; 

    public ServiceContractAImplementation() { } 
    public ServiceContractAImplementation(IServiceDependancy dep) 
    { 
     ServiceProxy = dep; 
    } 

    public void SomeOperation() 
    { 
     ServiceProxy.SomeOperation(); 
    } 
} 

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] 
public class ServiceDependancyImplementation : IServiceDependancy 
{ 
    public void SomeOperation() 
    { 
     //do nothing, just want to see if we can create an instance and hit the operation. 
     //if we need to do something, do something you can see like: System.IO.File.Create(@"d:\temp\" + Guid.NewGuid().ToString()); 
    } 
} 

public class ServiceCastleInstaller : IWindsorInstaller 
{ 
    public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store) 
    { 
     container.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero); 

     var returnFaults = new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true, HttpHelpPageEnabled = true }; 

     container.Register(Component.For<IServiceBehavior>().Instance(returnFaults)); 


     //local in-proc service hosting 
     var namedPipeBinding = new NetNamedPipeBinding(); 

     //it works using Named Pipes 
     var serviceModelPipes = new DefaultServiceModel().AddEndpoints(
      WcfEndpoint.BoundTo(namedPipeBinding).At("net.pipe://localhost/IServiceContractA") 
         ).Discoverable(); 

     container.Register(Component.For<IServiceContractA>() 
              .ImplementedBy<ServiceContractAImplementation>() 
              .LifeStyle.PerWcfOperation() 
              .AsWcfService(serviceModelPipes) 
              ); 

     //our service (IServiceContractA) has a dependancy on another service so needs a client to access it. 
     container.Register(Castle.MicroKernel.Registration.Component.For<IServiceDependancy>() 
      .AsWcfClient(WcfEndpoint.BoundTo(namedPipeBinding) 
      .At(@"net.pipe://localhost/IServiceDependancy")).LifeStyle.Transient); 

    } 
} 

public class ServiceDependancyCastleInstaller : IWindsorInstaller 
{ 
    public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store) 
    { 
     container.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero); 

     var returnFaults = new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true, HttpHelpPageEnabled = true }; 

     container.Register(Component.For<IServiceBehavior>().Instance(returnFaults)); 

     //local in-proc service hosting 
     var namedPipeBinding = new NetNamedPipeBinding(); 

     var serviceModel = new DefaultServiceModel().AddEndpoints(
      WcfEndpoint.BoundTo(namedPipeBinding).At("net.pipe://localhost/IServiceDependancy") 
         ).Discoverable(); 

     container.Register(Component.For<IServiceDependancy>() 
              .ImplementedBy<ServiceDependancyImplementation>() 
              .LifeStyle.PerWcfOperation() 
              .AsWcfService(serviceModel) 
              ); 
    } 

} 


[TestClass] 
public class UnitTest1 
{ 
    [TestMethod] 
    public void SomeOperation_With3Containers_NoException() 
    { 
     //setup the container that is going to host the service dependancy 
     using (var dependancyContainer = new WindsorContainer().Install(new ServiceDependancyCastleInstaller())) 
     { 
      //container that host the service that the client will call. 
      using (var serviceContainer = new WindsorContainer().Install(new ServiceCastleInstaller())) 
      { 
       //client container, nice and simple so doing it in the test here. 
       using (var clientContainer = new WindsorContainer()) 
       { 
        clientContainer.AddFacility<WcfFacility>(); 

        var endpoint = WcfEndpoint.BoundTo(new NetNamedPipeBinding()) 
         .At("net.pipe://localhost/IServiceContractA"); 

        clientContainer.Register(Castle.MicroKernel.Registration.Component.For<IServiceContractA>() 
         .AsWcfClient(endpoint).LifeStyle.Transient); 

        var proxy = clientContainer.Resolve<IServiceContractA>(); 

        proxy.SomeOperation(); 

        clientContainer.Release(proxy); 
       } 
      } 
     } 
    } 

    [TestMethod] 
    public void SomeOperation_With2Containers_NoException() 
    { 
     //this one fails. 
     // this test omits the dependancy that the IServiceContractA has 
     //Note that all seems to work, the only hint you have that it doesnt 
     //is the .Release call throws and exception. 

     //container that host the service that the client will call. 
     using (var serviceContainer = new WindsorContainer().Install(new ServiceCastleInstaller())) 
     { 
      //client container, nice and simple so doing it in the test here. 
      using (var clientContainer = new WindsorContainer()) 
      { 
       clientContainer.AddFacility<WcfFacility>(); 

       var endpoint = WcfEndpoint.BoundTo(new NetNamedPipeBinding()) 
        .At("net.pipe://localhost/IServiceContractA"); 

       clientContainer.Register(Castle.MicroKernel.Registration.Component.For<IServiceContractA>() 
        .AsWcfClient(endpoint).LifeStyle.Transient); 

       var proxy = clientContainer.Resolve<IServiceContractA>(); 

       //this call seems like it works but any break points 
       //in code don't get hit. 
       proxy.SomeOperation(); 

       //this throws and exception 
       clientContainer.Release(proxy); 
      } 
     } 
    } 

} 

}

回答

4

一種方式操作存在的目的 「火和忘記」 的情景。你不要保健關於結果,不管它是否成功。您不必等待供服務器響應(如果它是HTTP綁定,只有初始TCP握手)。通過使用單向操作,客戶端只能得到服務器在線上成功接收到消息的信心,並且服務器不保證它將成功處理該消息。這在HTTP協議中是正確的。在其他協議中,如Microsoft MSMQ或IBM MQ,服務器甚至不需要與客戶端同時在線。

在您的方案中,客戶端不會收到異常,因爲服務A已啓動並正在運行。如果服務A關閉,你會看到一個錯誤(再次假設HTTP,或者在你的情況下.net管道)。服務B的條件無關緊要,因爲服務B是服務A的實現細節,而客戶端並不關心服務A的返回值。如果您在服務B關閉的情況下調試服務A(通過附加到服務),您將看到第一次機會,甚至可能未經處理的異常(取決於服務A的實現)。

無論如何,城堡不應該拋出異常,因爲它已經成功地解析了服務A中的服務B的代理。服務B關閉的事實並不是城堡或任何其他DI容器的問題。

+0

感謝您的迴應。首先在單向通話中,我不相信你的斷言是正確的,也不相信單向通話或其在WCF中的設計的使用。我建議這個職位的更多細節:http://stackoverflow.com/questions/5318192/how-to-enable-reliability-for-one-way-methods – thorphin 2014-10-02 23:38:25

+0

你是對的,客戶端沒有收到異常,直到我嘗試釋放已解析的WCF客戶端代理,這是唯一表示存在問題的指示。 這裏的主要問題是爲什麼沒有,或者在什麼地方,或者如何啓用城堡日誌記錄關於服務A的依賴關係不可用?它從來沒有打我的代碼,所以我可以登錄有問題。沒有這個,我的電話將會完全丟失,我的代碼也不會執行。一個巨大的問題是肯定的! – thorphin 2014-10-02 23:46:34

+0

這是由於您使用net.pipe綁定而不是http,但它不會改變我們之前討論的要點。會發生什麼如下: 1.客戶端發送單向請求,而不關心服務回覆,無論好壞。 2.關於服務一方面,castle試圖創建一個實現實例,它需要服務B代理,但由於服務B關閉,似乎無法創建net.pipe代理,這就是爲什麼你無法訪問服務代碼。 3.接下來,客戶端發佈代理會引發錯誤,因爲這是net.pipe的工作方式。 – Roman 2014-10-03 05:46:46