2

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

}

}

thorphin
  • 118
  • 5

1 Answers1

4

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.

Roman
  • 2,108
  • 1
  • 18
  • 20
  • Thanks for the response. First on the One way calls, I don't believe your assertion to be correct nor the intended use of One Way calls or their design in WCF. I suggest this post for more details: http://stackoverflow.com/questions/5318192/how-to-enable-reliability-for-one-way-methods – thorphin Oct 02 '14 at 23:38
  • You are right, the client doesn’t receive an exception until I try to release the resolved WCF client proxy, which is the only indication that there is an issue. The primary question here is why doesn’t, or where does, or how to enable Castle logging in regard to the dependency of Service A being unavailable? It never hits my code so I can log there being an issue. Without this my calls will be completely lost and my code never executed. A huge issue to be sure! – thorphin Oct 02 '14 at 23:46
  • This is due to your use of net.pipe binding, instead of http, however it doesn't change the main points we discussed previously. What happens is as follows: 1. the client sends a one way request, not caring at all about the service reply, good or bad. 2. on service A side, castle attempts to create an implementation instance, which requires service B proxy, but because service B is down, it seems that a net.pipe proxy cannot be created, and that's why you can't reach any service A code. 3. Next, release proxy on the client side throws an error because that's how net.pipe works. – Roman Oct 03 '14 at 05:46
  • If you replace all your bindings with http, it will work more the way you expect it to work, because http proxies can be successfully created even though the service they address to is down, and on client side, no exceptions will be thrown on release. – Roman Oct 03 '14 at 05:48
  • As for your need to know what happened on service A, you can always use IErrorHandler behavior on Service A (not the client), which will allow you to catch any exception thrown by your service and the WCF framework. Since using castle is just an implementation detail of your service, the fact that it fails to generate dependencies will be caught by the IErrorHandler, at that point you can log it using your favorite logging framework. – Roman Oct 03 '14 at 07:33
  • Thanks, I will in deed try IErrorHandler as I've used them in the past. I must say thou I'm not sure how this will help seeing it's Castle doing the resolution of the dependency of Service A, which is failing and no user code is ever hit. I'll do this and report back. – thorphin Oct 07 '14 at 15:44
  • I'm happy to report adding the IErrorHandler implementation on the Service A implementation allowed me to catch the exception. I can't tell you how happy this makes me! Thou I must say looking at the call stack and the exception coming from Castle.Facilities.WcfIntegration.WcfClientActivator, I don't know how an error handler on the service implementation catches this exception. Must be WCF dispatched magic! – thorphin Oct 07 '14 at 16:22
  • No magic. Both of these extension points, IErrorHandler, and castle s' special instance provider, are extension points of WCF pipeline, which was designed to be extended. – Roman Oct 07 '14 at 20:31
  • Thanks again very much. The sad thing here is that I had been using a IErrorHandler implementation and pulled it off this service for some reason. I was just being silly about the magic part, just that I didn't understand. Very neat to hear Castle uses WCF extensibility, smart move on their part. You pointing that out makes it all come into focus. Again many thanks. – thorphin Oct 08 '14 at 15:22