Tutorials‎ > ‎

Implementing Operation Invoker and Operation Behavior in WCF

posted Oct 15, 2015, 2:58 AM by Hadi Setiawan   [ updated Aug 15, 2016, 11:31 PM by Surya Wang ]

Operation invoker is responsible for invoking the service implementation of a service contract. By writing our own operation invoker, we can determine what will happen around or intercept the actual service implementation. For example, we can cache the response or log the request. In this tutorial we'll use operation invoker along with behaviors to log invokes to our service.

We'll create a simple service, the GreeterService, which will have one operation, Greet. Greet operation will take a name, and return a greeting to that person. And we'll log each invocation and the time it was invoked to the output window.

Getting Started

The IDE used in this tutorial is Microsoft Visual Studio 2015. And to test the service, we'll be using WCF Test Client, which should be bundled with installation of Visual Studio.

Creating the service

We'll create an empty web project and name it Tutorial. Let's first add assembly reference of System.ServiceModel. After that, we'll create our Greeter service contract and its implementation. We'll create them inside the Service namespace. We'll put the Service namespace inside the Service folder of our root directory.

Service\IGreeterService.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using System.ServiceModel;

namespace Tutorial.Service
{
    [ServiceContract]
    interface IGreeterService
    {
        [OperationContract]
        string Greet(string name);
    }
}
Service\GreeterService.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace Tutorial.Service
{
    public class GreeterService : IGreeterService
    {
        public string Greet(string name)
        {
            return "Hello, " + name;
        }
    }
}

Now that the code for the service is done, we'll try to build our project. The purpose of building our project is so that we there will be autocomplete when we modify the web.config file. After building the project, we'll configure our project so it will activate the service without .svc files.

web.config
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<configuration>
  <!-- ... -->
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="Tutorial.Service.GreeterService">
        <endpoint binding="basicHttpBinding" contract="Tutorial.Service.IGreeterService" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <serviceHostingEnvironment>
      <serviceActivations>
        <add service="Tutorial.Service.GreeterService" relativeAddress="~/Greeter.svc" />
      </serviceActivations>
    </serviceHostingEnvironment>
  </system.serviceModel>
  <!-- ... -->
</configuration>

After this configuration, the service should be accessible at http://localhost:port/Greeter.svc. Make sure that the service is activated by going to the hosted address on your local computer.

Creating the Operation Invoker

To create an operation invoker, the class need to implements the System.ServiceModel.Dispatcher.IOperationInvoker interface. This interface have 5 methods to be implemented. In this tutorial, we only care about the Invoke method. In this method, we'll log the service invocation.

Keep in mind that we won't be implementing the operation invoker fully. In fact, we'll still use the default invoker. So methods in the operation invoker we create, will be delegated to the default invoker eventually.

Invoker\LogOperationInvoker.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Diagnostics;
using System.ServiceModel.Dispatcher;

namespace Tutorial.Invoker
{
    public class LogOperationInvoker : IOperationInvoker
    {
        public IOperationInvoker DefaultInvoker { get; set; }

        public string OperationName { get; set; }

        public bool IsSynchronous
        {
            get
            {
                return DefaultInvoker.IsSynchronous;
            }
        }

        public object[] AllocateInputs()
        {
            return DefaultInvoker.AllocateInputs();
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            Debug.WriteLine("[{0:yyyy-MM-dd H:mm:ss}] {1}.{2}({3})",
                DateTime.Now, instance.GetType().FullName, OperationName, String.Join(", ", inputs));

            return DefaultInvoker.Invoke(instance, inputs, out outputs);
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return DefaultInvoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            return DefaultInvoker.InvokeEnd(instance, out outputs, result);
        }
    }
}

The code for LogOperationInvoker is done now. The important thing to notice here is that we'll delegate every method invocation to the default invoker, but the Invoke method, will log the request to the output before delegating to the default invoker. Note that if you want to create other implementation instead of logging, for example caching the response, you should do it in the Invoke method.

Gluing things together

Now that we have both our service and operation invoker ready, how do we use them both? As now when we invoke the greet operation, the request won't be logged and shown on output window. The glue to these two is the behavior. By using behavior, we'll change the default operation invoker of our GreeterService to LogOperationInvoker.

There're four types of behavior if categorized by their scope:

  • Service Behavior
  • Contract Behavior
  • Endpoint Behavior
  • Operation Behavior

We'll add these behavior to our service programmatically, by using attributes. But we can't apply endpoint behavior programmatically, as endpoint is configured inside the web.config file. That's not the case for contract, operation, and service, as we can access it from our code.

Here is where you should put the each attribute when you want to apply certain behavior. Contract behavior is at ServiceContract, operation behavior is at OperationContract, and service behavior is at the class that implements ServiceContract. For more detailed explanation about behavior and other mechanism to apply a behavior, please visit this link.

Behavior\LogOperationBehavior.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using Tutorial.Invoker;

namespace Tutorial.Behavior
{
    public class LogOperationBehavior : Attribute, IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription operationDescription,
            BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(OperationDescription operationDescription,
            ClientOperation clientOperation)
        {
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription,
            DispatchOperation dispatchOperation)
        {
            dispatchOperation.Invoker = new LogOperationInvoker
            {
                DefaultInvoker = dispatchOperation.Invoker,
                OperationName = operationDescription.Name
            };
        }

        public void Validate(OperationDescription operationDescription)
        {
        }
    }
}
Behavior\LogContractBehavior.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace Tutorial.Behavior
{
    public class LogContractBehavior : Attribute, IContractBehavior
    {
        public void AddBindingParameters(ContractDescription contractDescription,
            ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ContractDescription contractDescription,
            ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ContractDescription contractDescription,
            ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
        {
            foreach (var operation in contractDescription.Operations)
            {
                if (!operation.OperationBehaviors.Contains(typeof(LogOperationBehavior)))
                {
                    operation.OperationBehaviors.Add(new LogOperationBehavior());
                }
            }
        }

        public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        {
        }
    }
}
Behavior\LogServiceBehavior.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;

namespace Tutorial.Behavior
{
    public class LogServiceBehavior : Attribute, IServiceBehavior
    {
        public void AddBindingParameters(ServiceDescription serviceDescription,
            ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
            BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
            ServiceHostBase serviceHostBase)
        {
            foreach (var endpoint in serviceDescription.Endpoints)
            {
                foreach (var operation in endpoint.Contract.Operations)
                {
                    if (!operation.OperationBehaviors.Contains(typeof(LogOperationBehavior)))
                    {
                        operation.OperationBehaviors.Add(new LogOperationBehavior());
                    }
                }
            }
        }

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

You can also create a class which will implements all three interface (IOperationBehavior, IContractBehavior, IServiceBehavior). I separated them into three different classes is for clarity reason.

All that's left to do is applying the behavior to our service. Because we have three types of different behavior, we'll also have three ways to use them.

The code below applies both the operation and contract behavior.

Service\IGreeterService.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System.ServiceModel;
using Tutorial.Behavior;

namespace Tutorial.Service
{
    [LogContractBehavior]
    [ServiceContract]
    interface IGreeterService
    {
        [LogOperationBehavior]
        [OperationContract]
        string Greet(string name);
    }
}

The code below applies the service behavior.

Service\GreeterService.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using Tutorial.Behavior;

namespace Tutorial.Service
{
    [LogServiceBehavior]
    public class GreeterService : IGreeterService
    {
        public string Greet(string name)
        {
            return "Hello, " + name;
        }
    }
}

Even though I applied all three behavior to the same service, it does not mean that you should apply them all too. You should apply the behavior according to the scope you want. For example you just want to log one operation of a certain service, then you should just use the LogOperationBehavior on the said operation.

Let's try invoking our GreeterService and see the result in the output window. I'll be using WcfTestClient mentioned earlier. Below is the screenshot of both the test client and the output window.

WCF test client
WCF test client
Output window
Logged message in output window

Conclusion

By leveraging the extensibility of WCF, invoker and behavior, we can intercept any request at the service level. For further implementation, it's better that you combine the behavior into one class, as this simplifies a lot the usage of the behavior.

You can download the source code below.

ċ
source.zip
(10k)
Hadi Setiawan,
Oct 15, 2015, 3:02 AM