Archive

Archive for December, 2010

Extending WCF: Creating a logging component

December 4, 2010 4 comments

Introduction

WCF is very flexible framework. It provides various points where you can extend or replace the out of the box provided functionality. In this article I will explain you how can develop a logging component that will be invoked for every operation that gets called. To do this we will create a WCF component that is called a ParameterInspector. This type of components is called just before and after operations are invoked. Once it is created I will explain you how to inject it into the WCF pipeline.

Creating the ParameterInspector

Creating a ParameterInspector is very easy, all you need to do is implement the interface IParameterInspector:

namespace System.ServiceModel.Dispatcher
{
    public interface IParameterInspector
    {
        void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState);

        object BeforeCall(string operationName, object[] inputs);
    }
}

The method BeforeCall is executed just before each call and the AfterCall is executed directly after a call. The return value of the BeforeCall method is the correlationState; this allows you to pass values from the BeforeCall method to the AfterCall method. The correlationState does not influence the call itself.

For this proof of concept, we are only interested in the BeforeCall method. We will print information to the output trace for ease of development, but in a real application you would of course write to a database or use a logging component like the one available in the Enterprise Library of Microsoft.

The authentication information that we want to log can be found in the ServiceSecurityContext object. We can get the instance for the current call by calling ServiceSecurityContext.Current.

We will name our custom ParameterInspector “Userlogger” and the implementation of our BeforeCall method is:

public object BeforeCall(string operationName, object[] inputs)
{
	OperationContext operationContext = OperationContext.Current;
	ServiceSecurityContext securityContext = ServiceSecurityContext.Current;

	string user = null;
	bool isAnonymous = true;

	if (securityContext != null)
	{
	user = securityContext.PrimaryIdentity.Name;
	isAnonymous = securityContext.IsAnonymous;
	}

	Uri remoteAddress = operationContext.Channel.LocalAddress.Uri;
	string sessionId = operationContext.SessionId;
	MessageVersion messageVersion = operationContext.IncomingMessageVersion;

	Trace.WriteLine("Username: " + user);
	Trace.WriteLine("Is Anonymoys" + isAnonymous);
	Trace.WriteLine("Server address: " + remoteAddress);
	Trace.WriteLine("Session id: " + sessionId);
	Trace.WriteLine("Message version: " + messageVersion);
	Trace.WriteLine("Operation:" + operationName);
	Trace.WriteLine("Arguments:");
	foreach (object input in inputs)
		Trace.WriteLine(input);

	return null;
}

Writing this ParameterInspector was straightforward, but now we have to inject it into the WCF pipeline. This can be done in a couple of ways:

  • Manually injection into the WCF pipeline.
  • Injection by use of an attribute.
  • Declarative injection using the web.config of app.config configuration file.

Manually injection into the WCF pipeline

If you self host your service, you can easily add the custom ParameterInspector to each of the operations of the service that you host. When you iterate over the ChannelDispatchers, you will get access to all the EndpointDispatchers, and iterating over all the EndpointDispatchers will give you access the all the DispatchOperations. Because we want the add our custom ParameterInspector to all the operations exposed by our service, we add it to the ParameterInspectors collection of each DispatchOperation.

ServiceHost serviceHost = new ServiceHost(typeof(HelloService));

IParameterInspector parameterInspector = new UserLogger();

foreach (ChannelDispatcher dispatcher in serviceHost.ChannelDispatchers)
{
    foreach (EndpointDispatcher endpointDispatcher in dispatcher.Endpoints)
    {
        DispatchRuntime dispatchRuntime = endpointDispatcher.DispatchRuntime;
        IEnumerable<DispatchOperation> dispatchOperations = dispatchRuntime.Operations;

        foreach (DispatchOperation dispatchOperation in dispatchOperations)
        {
            dispatchOperation.ParameterInspectors.Add(parameterInspector);
        }
    }
}

serviceHost.Open();

Although this can be done, it is not a very clean approach. Programmers who whish to use the UserLogger component need to manually add it to the WCF pipeline. This is both error prone and complex to do without advanced knowledge of WCF. It is also only possible if you self host your service. It is not possible if you use IIS or WAS for hosting.

Injection by use of an attribute

By implementing IServiceBehavior and extending the Attribute class, we can create a custom ServiceBehavior. This allows us the hide all the complexity involved in adding the UserLogger component to WCF.

The interface IServiceBehavior consists of 3 methods:

public interface IServiceBehavior
{
	void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters);

	void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase);

	void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase);
}

AddBindingParameters allows to pass custom data to binding elements to support the contract implementation, ApplyDispatchBehavior allows to add custom extension objects to the service and Validate provides the ability to inspect the service host and the service description to confirm that the service can run successfully.

To inject the UserLogger, you only need the implement the ApplyDispatchBehavior method. Because this method provides access to the ServiceHostBase. The code to inject the ParameterInspector is completely the same as the manual injection method.

public class UserLoggingAttribute : Attribute, IServiceBehavior
{
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
        Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        IParameterInspector parameterInspector = new UserLogger();

        foreach(ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
        {
            foreach (EndpointDispatcher endpointDispatcher in dispatcher.Endpoints)
            {
                DispatchRuntime dispatchRuntime = endpointDispatcher.DispatchRuntime;
                IEnumerable<DispatchOperation> dispatchOperations = dispatchRuntime.Operations;

                foreach (DispatchOperation dispatchOperation in dispatchOperations)
                {
                    dispatchOperation.ParameterInspectors.Add(parameterInspector);
                }
            }
        }
    }

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

Programmers can now use the Userlogger component by decorating their service classes. They don’t need to have knowledge about the inner workings of WCF anymore. Injecting the UserLogger component is now much less error prone.

[UserLogging]
public class HelloService : IHelloService
{
	...
}

Declarative injection

We can still go one step further and make it possible to inject our UserLogger into the WCF pipeline by using the configuration file. To do this, we have to write a class that extends BehaviorExtensionElement. Our custom BehaviorExtensionElement needs to overwrite the property BehaviorType and the method CreateBehavior. The property BehaviorType needs return the type of our custom service behavior and the method CreateBehavior needs to create a new instance of it.

public class UserLoggingServiceElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get
        {
            return typeof(UserLoggingAttribute);
        }
    }

    protected override object CreateBehavior()
    {
        return new UserLoggingAttribute();
    }
}

In order to use our custom BehaviorExtensionElement, we also need to register it into the web.config or app.config:

<system.serviceModel>

	...

    <extensions>
      <behaviorExtensions>
        <add name="userLogging" type="AuditWcfService.UserLoggingServiceElement, AuditWcfService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
</system.serviceModel>

Please be aware that because of a bug in WCF, the single white spaces between the parts in the type attribute are important.

We can now inject our UserLogger component using the configuration file:

  <system.serviceModel>
    <services>
      <service name="AuditWcfService.HelloService" behaviorConfiguration="HelloServiceBehavior">

		...

        <endpoint address=""
                  binding="wsHttpBinding"
                  contract="AuditWcfService.IHelloService"/>
      </service>
    </services>

    <behaviors>
      <serviceBehaviors>

		...

        <behavior name="HelloServiceBehavior">
          <userLogging/>
        </behavior>

      </serviceBehaviors>
    </behaviors>

	...

<system.serviceModel>

Conclusion

In this article I have shown how you can hook into WCF to execute custom logic before or after each call. As proof of concept, I have shown you how to create a very simple logging component. I have also described the various ways how this component can be injected into the WCF pipeline.

Advertisements
Categories: C#, WCF