Injecting WCF fault contract using Castle Dynamic Proxy Generation
Asked Answered
E

1

8

I am currently working on WPF application with a WCF backend. We have implemented a client logging solution and a server logging solution for exception handling, and they work great, but it is often difficult to tie the information together over the wire. If an exception occurs on the server, I wanted a way to pass an exception token back across the wire so that I could log it on the client with the exception. That way, when I am trouble shooting a client error, I can very easily correlate it with the server exception.

I would like to give a little bit more info about our architecture, and then I’ll state my problem.

Our WCF implementation is a little more robust that the out-of-the-box method for setting a service reference. We implemented dynamic proxy generation on the client. We accomplished this by creating web service interfaces that are shared by the client and the server, and used the Castle.DynamicProxy.ProxyGenerator class and the CreateInterfaceProxyWithoutTarget method to create the proxy. Additionally when we call the CreateInterfaceProxyWithoutTarget method, we specify an IInterceptor implementation. On the server there are three main classes which are used for our trace and fault behavior:

FaultOperationInvoker (Implements IOperationInvoker): Tries to call the service method using IOperationInvoker.Invoke. If it is a fault exception type it rethrows, if it is an exception, it tries to determine if there is a fault contract having a specific detail type, and if so, then wrap then raise a new fault exception with the detail info.

internal class FaultOperationInvoker : IOperationInvoker
    {
        IOperationInvoker innerOperationInvoker;
        FaultDescription[] faults;

        public FaultOperationInvoker(IOperationInvoker invoker, FaultDescription[] faults)
        {
            this.innerOperationInvoker = invoker;
            this.faults = faults;
        }

        #region IOperationInvoker Members

        object[] IOperationInvoker.AllocateInputs()
        {
            return this.innerOperationInvoker.AllocateInputs();
        }

        object IOperationInvoker.Invoke(object instance, object[] inputs, out object[] outputs)
        {
            try
            {
                return this.innerOperationInvoker.Invoke(instance, inputs, out outputs);
            }
            catch (FaultException e)
            {
                ServerLogger.GetLogger(instance.GetType().FullName).LogException(e, "Unhandled exception in service operation.");

                //allow fault exceptions to bubble out
                throw;
            }          
            catch (Exception e)
            {
                Type exceptionType = e.GetType();

                ServerLogger.GetLogger(instance.GetType().FullName).LogException(e, "Unhandled exception in service operation.");

                //if the excpetion is serializable and there operation is tagged to support fault contracts of type WcfSerivceFaultDetail
                if (faults != null && (faults.OfType<WcfServiceFaultDetail>().Any() || faults.Count(x => x.DetailType == typeof(WcfServiceFaultDetail)) > 0))
                {
                    throw new FaultException<WcfServiceFaultDetail>(new WcfServiceFaultDetail(true, e), "Unhandled exception during web service call.", WcfFaultCustomExceptionFactory.GetFaultCodeForExceptionType(exceptionType));
                }
                else
                {
                    throw new FaultException("Unhandled exception during web service call.", WcfFaultCustomExceptionFactory.GetFaultCodeForExceptionType(exceptionType));
                }

            }
        }

FaultOperationBehavior (Implements IOperationBehavior) points the dispatch operation invoker to the fault operation invoker described above.

[AttributeUsage(AttributeTargets.Method)]
    public class FaultOperationBehavior : System.Attribute, IOperationBehavior
    {
        #region IOperationBehavior Members

        void IOperationBehavior.AddBindingParameters(OperationDescription operationDescription,
            System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { }


        void IOperationBehavior.ApplyClientBehavior(OperationDescription operationDescription,
            System.ServiceModel.Dispatcher.ClientOperation clientOperation) { }

        void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription,
            System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)
        {          
            dispatchOperation.Invoker = new FaultOperationInvoker(dispatchOperation.Invoker, operationDescription.Faults.ToArray());
        }

        void IOperationBehavior.Validate(OperationDescription operationDescription) { }

        #endregion
    }

ExceptionTraceBehavior (Inherits Attribute, Implements IServiceBehavior) for handling exceptions that implements IServiceBehavior. We also have a class(FaultOperationBehavior)

[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, Inherited = true)]
public class ExceptionTraceBehavior : Attribute, IServiceBehavior
{
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (var ep in serviceDescription.Endpoints)
        {
            foreach (var operation in ep.Contract.Operations)
            {
                if (operation.Behaviors.Contains(typeof(FaultOperationBehavior)))
                    continue;

                operation.Behaviors.Add(new FaultOperationBehavior());

                //Check to see if this operation description contains a wcf service fault detail operation.  If it doesn't, add one.
                if (operation.Faults != null && (operation.Faults.Count == 0 || operation.Faults.Count > 0 && operation.Faults.Count(x => x.DetailType == typeof(WcfServiceFaultDetail)) == 0))
                {
                    FaultDescription faultDescription = new FaultDescription(operation.Name);
                    //faultDescription.Name = "WcfServiceFaultDetail";
                    //faultDescription.Namespace = "";
                    faultDescription.DetailType = typeof(WcfServiceFaultDetail);
                    operation.Faults.Add(faultDescription);
                }
            }
        }
    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }
}

Every one of our service interfaces has a concrete implementation. All of our services also inherit our base service class which is decorated with the ExceptionTrace attribute.

So, now with the background info, here is the problem. I want every service operation to have a fault contract with detail type WCFServiceFaultDetail, but I don’t want to put a FaultContract attribute on every service operation. As you can see in the ExceptionTraceBehavior, I figured out how to add the fault contract programmatically, and it works great for adding the fault to the operation. When a regular old exception is caught in the operation invoker, it finds that there is the proper fault contract and threws a new FaultExcption. However, when the exception is caught back the client, it falls into the catch (FaultExcection fe) code instead of the catch (FaultException fe) code.

However, if remove the code to programmatically add in the fault contract, I decorate every service operation with [FaultContract(typeof(WcfServiceFaultDetail))], the client catches the exception as expected.

The only thing I can figure is that since the proxy is being generated dynamically from the interface and not from a WSDL or other meta data, and there is no fault contract decoration on the interface, my programmatic fault contract isn’t being honored.

With that thought in mind, I’ve tried to figure out how to add the fault contract in the IInterceptor implementation, but have had no success.

So I am hoping that someone has done this already and can provide some details. Any help is appreciated.

Ennis answered 10/9, 2012 at 22:16 Comment(3)
For client / server error correlation, did you look at Activity ID Propagation ?Eventuate
I did not, I will take a look at that. Thanks for the tip.Ennis
Erm... running into the same issue with the client not creating exception with the DetailType. This is very old I know, but did you ever solve this?Bridgettbridgette
C
0

I am not a WCF expert but I got my hands a little dirty.

I think you are right. Fault contracts are resolved from assembly metadata when creating a ChannelFactory of some type. Since your interfaces are not decorated with the appropriate FaultContract attributes, your client uses a default fault contract with no detail.

Adding FaultContract attributes to your interface methods at runtime will not probably work either.

One solution could be generating and using types dynamically at runtime, to generete channel factories.

I've never done this but I think this could be done starting with AppDomain's DefineDynamicAssembly.

Cinderellacindi answered 20/9, 2012 at 8:33 Comment(3)
That is actually what we're doing. We create ChannelFactory of the service interface type. I've tried adding the behavior just before we call the CreateChannel method, and that doesn't seem to work.Ennis
I understand your point but I am not suggesting that. "Adding FaultContract attributes to your interface methods at runtime will not probably work either." Actually I am suggesting something different. I am saying that one way to achieve what you are trying to do could be that you build types, modules, metadata and assemblies at runtime, cache them and use them. That's why i was pointing at AppDomain's DefineDynamicAssembly.Lecce
Sorry, I misunderstood. That is a very interesting idea, but I'm not sure about the feasibility of it for our purposes.Ennis

© 2022 - 2024 — McMap. All rights reserved.