简体   繁体   中英

Castle Windsor WCF Facility is not processing one way operations

I currently have a simple use case.

1) A client app that connects to a WCF Service using Castle's AsWcfClient option.

2) WCF Service "A" that is hosted using Castle and is injecting a single dependency. This dependency is a client proxy to another WCF Service (Call it Service "B").

3) Service "B" does some work.

To visualize: Client -> Service "A" with Castle injected proxy to -> Service "B"

Simple right? Works without issue IF, and that's a big if, the Service "B" host is up and running.

The behavior I have seen and can reproduce on demand is that if Service "B" is down, the call chain completes without any hint that there is any issue. To say it another way, there is no resolution exception thrown by Castle nor any WCF exception. I have isolated this to how IsOneWay=true operations are handled.

This is a major issue because you think that everything has executed correctly but in reality none of your code has been executed!

Is this expected behavior? Is there away I can turn on some option in Castle so that it will throw and exception when a WCF Client proxy is the resolved dependency? Some other option?

One more note, the only clue that you have that is issue is occurring is when/if you do a Container.Release() on the client proxy as it throws an exception. This can't be depended upon thou for various reasons not worth getting into here.

Thank!

Additionally below is the code that recreates this issue. To run it 1) Create a new Unit Test project in Visual Studio 2) Add the Castle Windsor WCF Integration Facility via NuGet 3) Paste the code from below into a .cs file, everything is in one to make it easy. 4) Run the two unit tests, SomeOperation_With3Containers_NoException() works as the dependency service (Service "B" from above) is running. SomeOperation_With2Containers_NoException() fails are the .Release 5) Set break points and you can see that no code is hit in the implementations.

****UPDATE****: The primary way this needs to be handled is with an IErrorHandler implantation (As mentioned by Roman in the comments below). Details and an example can be found here: http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.110).aspx

Use this implementation to log any exception on the One Way operation and use that data to take the appropriate action.

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);
            }
        }
    }

}

}

One way operations exists for the purpose of "Fire and forget" scenarios. You don't care about result, whether it was successful or not. You don't have to wait for the server to respond (only the initial TCP handshake if it's HTTP binding). By using one way operations, the client only gets the confidence that the server received the message successfully on the wire, and the server makes no guarantees that it will succeed in processing the message. This is true in HTTP protocol. In other protocols, like Microsoft MSMQ, or IBM MQ, the server doesn't even need to be online at the same time as the client.

In your scenario, The client does not receive an exception, because service A is up and running. If service A was down, you would have seen an error (Again, assuming HTTP, or in your case .net pipe). The condition of service B does not matter, because service B is an implementation detail of service A, and your client doesn't care about service A return values. If your were to debug service A (by attaching to it) while service B is down, you would have seen first chance, and maybe even unhandled exceptions (depending on the implementation of service A).

Castle should not have thrown an exception anyway, because it has successfully resolved a proxy for service B in service A. The fact that service B is down is no concern of Castle, or any other DI container for that matter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM