17

I have a requirement to log each method call in a WCF service, and any exceptions thrown. This has led to a lot of redundant code, because each method needs to include boilerplate similar to this:

[OperationContract]
public ResultBase<int> Add(int x, int y)
{
    var parameters = new object[] { x, y }
    MyInfrastructure.LogStart("Add", parameters);
    try
    {
        // actual method body goes here
    }
    catch (Exception ex)
    {
        MyInfrastructure.LogError("Add", parameters, ex);
        return new ResultBase<int>("Oops, the request failed", ex);
    }
    MyInfrastructure.LogEnd("Add", parameters);
}

Is there a way I can encapsulate all this logic into an attribute MyServiceLoggingBehaviorAttribute, which I could apply to the service class (or methods) like this:

[ServiceContract]
[MyServiceLoggingBehavior]
public class MyService
{
}

Note #1

I realize that this can be done using Aspect-oriented programming, but in C# the only way to do this is to modify bytecode, which requires the use of a third-party product like PostSharp. I would like to avoid using commercial libraries.

Note #2

Note that Silverlight applications are the primary consumers of the service.

Note #3

WCF trace logging is a good option in some cases, but it doesn't work here because, as noted above, I need to inspect, and in the case of an exception change, the return value.

Community
  • 1
  • 1
McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • Have you tried WCF tracing? – John Saunders Dec 01 '12 at 02:25
  • @JohnSaunders I'm not fluent in WCF tracing -- may have fulfilled the logging requirement, but in any case I don't think it would enable wrapping each method call in a try-catch and returning a `ResultBase` object upon exception. – McGarnagle Dec 01 '12 at 02:39
  • Yeah, sorry; I hadn't noticed that requirement. Not sure how wise that is, but... – John Saunders Dec 01 '12 at 02:45
  • @JohnSaunders I'd be curious to hear what specifically troubles you about the relative wisdom of that approach. Actually, at first I had a solution that passed uncaught exceptions (`IncludeExceptionDetailInFaults`), and modified the outgoing messages using `IDispatchMessageInspector` so that Silverlight could display the message and not the dreaded "NotFound". But I scrapped it because of some weird problems adding the custom endpoint behavior, and because of some legacy client code that relied on the `ResultBase` approach. (I may post that solution here too, when I have a minute.) – McGarnagle Dec 01 '12 at 02:56
  • 1
    Mainly, did you notice that you didn't specify you were using Silverlight? I presume you're dealing with REST services? Otherwise, my first objection is that you're not using SOAP faults. I would loathe the fact that you're asking your callers to check return codes to determine whether or not there's a problem. Exceptions (and therefore faults) are much cleaner. – John Saunders Dec 01 '12 at 03:00

2 Answers2

28

Yes, it is possible to encapsulate this kind of logging, using the extensibility points built into WCF. There are actually multiple possible approaches. The one I'm describing here adds an IServiceBehavior, which uses a custom IOperationInvoker, and does not require any web.config modifications.

There are three parts to this.

  1. Create an implementation of IOperationInvoker, which wraps the method invocation in the required logging and error-handling.
  2. Create an implementation of IOperationBehavior that applies the invoker from step 1.
  3. Create an IServiceBehavior, which inherits from Attribute, and applies the behavior from step 2.

Step 1 - IOperationInvoker

The crux of IOperationInvoker is the Invoke method. My class wraps the base invoker in a try-catch block:

public class LoggingOperationInvoker : IOperationInvoker
{
    IOperationInvoker _baseInvoker;
    string _operationName;

    public LoggingOperationInvoker(IOperationInvoker baseInvoker, DispatchOperation operation)
    {
        _baseInvoker = baseInvoker;
        _operationName = operation.Name;
    }

    // (TODO stub implementations)

    public object Invoke(object instance, object[] inputs, out object[] outputs)
    {
        MyInfrastructure.LogStart(_operationName, inputs);
        try
        {
            return _baseInvoker.Invoke(instance, inputs, out outputs);
        }
        catch (Exception ex)
        {
            MyInfrastructure.LogError(_operationName, inputs, ex);
            return null;
        }
        MyInfrastructure.LogEnd("Add", parameters);
    }
}

Step 2 - IOperationBehavior

The implementation of IOperationBehavior simply applies the custom dispatcher to the operation.

public class LoggingOperationBehavior : IOperationBehavior
{
    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        dispatchOperation.Invoker = new LoggingOperationInvoker(dispatchOperation.Invoker, dispatchOperation);
    }

    // (TODO stub implementations)
}

Step 3 - IServiceBehavior

This implementation of IServiceBehavior applies the operation behavior to the service; it should inherit from Attribute so that it can be applied as an attribute to the WCF service class. The implementation for this is standard.

public class ServiceLoggingBehavior : Attribute, IServiceBehavior
{
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ServiceEndpoint endpoint in serviceDescription.Endpoints)
        {
            foreach (OperationDescription operation in endpoint.Contract.Operations)
            {
                IOperationBehavior behavior = new LoggingOperationBehavior();
                operation.Behaviors.Add(behavior);
            }
        }
    }
}
Tolga Evcimen
  • 7,112
  • 11
  • 58
  • 91
McGarnagle
  • 101,349
  • 31
  • 229
  • 260
5

You can try Audit.NET library with its Audit.WCF extension. It can log the WCF service interaction and is compatible with async calls.

All you need to do is decorate your WCF service class or methods with the AuditBehavior attribute:

[AuditBehavior()]
public class OrderService : IOrderService
{ ... }

The WCF extension uses an IOperationInvoker implementing Invoke and InvokeBegin/InvokeEnd. You can check the code here.

thepirat000
  • 12,362
  • 4
  • 46
  • 72