【问题标题】:Castle Windsor WCF Facility is not processing one way operations温莎城堡 WCF 设施未处理单向操作
【发布时间】:2014-10-08 23:52:42
【问题描述】:

我目前有一个简单的用例。

1) 使用 Castle 的 AsWcfClient 选项连接到 WCF 服务的客户端应用程序。

2) 使用 Castle 托管并注入单个依赖项的 WCF 服务“A”。此依赖项是另一个 WCF 服务(称为服务“B”)的客户端代理。

3) 服务“B”做了一些工作。

可视化:客户端 -> 带有 Castle 注入代理的服务“A” -> 服务“B”

简单吧?如果服务“B”主机已启动并正在运行,则可以正常工作。

我看到并且可以按需重现的行为是,如果服务“B”关闭,调用链完成,没有任何提示存在任何问题。换句话说,Castle 没有抛出任何解析异常,也没有任何 WCF 异常。我已将此与 IsOneWay=true 操作的处理方式隔离开来。

这是一个主要问题,因为您认为一切都已正确执行,但实际上您的任何代码都没有执行!

这是预期的行为吗?有没有我可以在 Castle 中打开某个选项,以便当 WCF 客户端代理是已解决的依赖项时它会抛出异常?还有其他选择吗?

还有一点需要注意,您遇到的问题的唯一线索是何时/如果您在客户端代理上执行 Container.Release(),因为它会引发异常。由于各种不值得在这里讨论的原因,这不能依赖于你。

谢谢!

另外,下面是重现此问题的代码。运行它 1) 在 Visual Studio 中新建一个单元测试项目 2) 通过 NuGet 添加 Castle Windsor WCF 集成工具 3) 将下面的代码粘贴到 .cs 文件中,一切合二为一。 4) 运行两个单元测试,SomeOperation_With3Containers_NoException() 作为依赖服务(上面的服务“B”)正在运行。 SomeOperation_With2Containers_NoException() 失败是 .Release 5)设置断点,你可以看到实现中没有代码被命中。

****UPDATE****:需要处理的主要方法是使用 IErrorHandler 植入(正如 Roman 在下面的 cmets 中提到的)。详细信息和示例可以在这里找到: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);
            }
        }
    }

}

}

【问题讨论】:

    标签: c# wcf castle-windsor


    【解决方案1】:

    为了“一劳永逸”的场景而存在一种操作方式。你不关心结果,不管它是否成功。您不必等待服务器响应(如果是 HTTP 绑定,则只需初始 TCP 握手)。通过使用单向操作,客户端只能确信服务器在网络上成功接收消息,服务器不保证它会成功处理信息。这在 HTTP 协议中是正确的。在其他协议中,例如 Microsoft MSMQ 或 IBM MQ,服务器甚至不需要与客户端同时在线。

    在您的场景中,客户端没有收到异常,因为服务 A 已启动并正在运行。如果服务 A 关闭,您会看到一个错误(再次假设 HTTP,或者在您的情况下是 .net 管道)。服务 B 的条件无关紧要,因为服务 B 是服务 A 的实现细节,您的客户并不关心服务 A 的返回值。如果您在服务 B 关闭时调试服务 A(通过附加到它),您将看到第一次机会,甚至可能看到未处理的异常(取决于服务 A 的实现)。

    Castle 无论如何都不应该抛出异常,因为它已经成功地解析了服务 A 中服务 B 的代理。服务 B 关闭的事实与 Castle 或任何其他 DI 容器无关。

    【讨论】:

    • 感谢您的回复。首先关于单向调用,我不相信您的断言是正确的,也不相信单向调用的预期用途或其在 WCF 中的设计。我建议这篇文章了解更多详情:stackoverflow.com/questions/5318192/…
    • 你是对的,在我尝试释放已解析的 WCF 客户端代理之前,客户端不会收到异常,这是唯一表明存在问题的迹象。这里的主要问题是,在服务 A 的依赖不可用的情况下,为什么不启用 Castle 日志记录,或者在哪里启用,或者如何启用 Castle 日志记录?它永远不会碰到我的代码,所以我可以记录存在的问题。没有这个,我的调用将完全丢失,我的代码永远不会执行。可以肯定的是一个大问题!
    • 这是由于您使用了 net.pipe 绑定,而不是 http,但它不会改变我们之前讨论的要点。发生的情况如下: 1. 客户端发送单向请求,根本不关心服务回复的好坏。 2.在服务A端,castle尝试创建一个实现实例,需要服务B代理,但是因为服务B宕机了,好像无法创建net.pipe代理,所以无法访问任何服务一个代码。 3.接下来,在客户端发布代理会抛出错误,因为net.pipe就是这样工作的。
    • 如果你用 http 替换所有的绑定,它会按照你期望的方式工作,因为 http 代理可以成功创建,即使他们寻址的服务已经关闭,并且在客户端, 发布时不会抛出异常。
    • 至于您需要知道服务 A 上发生了什么,您始终可以在服务 A(而不是客户端)上使用 IErrorHandler 行为,这将允许您捕获服务和 WCF 引发的任何异常框架。由于使用 castle 只是你的服务的一个实现细节,它无法生成依赖项的事实将被 IErrorHandler 捕获,此时你可以使用你喜欢的日志框架记录它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多