Extending a WCF service (service extensibility)

Extensibility is one of the most important concerns for any programming world. While developing a framework, a good architect always creates hooks, which lets the user explore these hooks and modify the way the framework behaves. WCF, being a powerful and simple tool to create services, also has a lot of hooks on its runtime, which will enable developers to write their own code and modify the way WCF behaves in certain scenarios. There are a great number of hooks available as interfaces in WCF, which allow you to change either the description or runtime of the WCF service.

The description of a service means all the things that are needed to start a service under the WCF environment. It includes:

  • All aspects that WCF needs to start a service
  • Endpoints (address, binding, and contract)
  • Local behaviors

After ServiceHost is opened, we cannot change the description of the service until it is closed and restarted. When the ServiceHost service is opened, the WCF runtime is initialized and all the socket listeners are started. In this situation, the ServiceDescription service is frozen and any attempt to change the service description will be ignored.

There are a number of extensibility options available in WCF, which allow hooking and changing the default operation of the service. The options are as follows:

  • Extending behaviors
  • Extending serialization
  • Extending metadata
  • Extending security
  • Extending channel system
  • Extending the service host

Getting ready

Let's create a service and host the service in a Console Application as follows:

[ServiceContract]
    public interface ISecureTestService
    {
        [OperationContract]
        string MyDummyOperation(string name, string password);
    }

    public class MySecureTestService : ISecureTestService
    {
        public string MyDummyOperation(string name, string password)
        {
            return "Doing inside dummy service";
        }
    }

In the preceding code, we just defined a ServiceContract service and an operation called MyDummyOperation. We also defined the ServiceHost service to host our service directly inside our Console Application, as shown in the following code:

static void Main(string[] args)
{
    ServiceHost host = new ServiceHost(typeof(MySecureTestService));
    host.Open();
    Console.WriteLine("Secure service is running");
    Console.ReadKey(true);
}

Also, we configured the service with address and contract as follows:

<services>
      <service name="ServiceExtensibility.MySecureTestService" behaviorConfiguration="Metadata">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8140/Service"/>
            <add baseAddress="https://localhost:8883/Service"/>
          </baseAddresses>
        </host>
      <endpoint address=""
                binding="basicHttpBinding"
                contract="ServiceExtensibility.ISecureTestService"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="Metadata">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

This is a simple service where we host our service in more than one scheme at different ports on the same machine. We also got httpGet enabled for metadata.

If we try to override the default behaviors of the WCF, we have four options to change the default behaviors of the service. They are as follows:

  • Service behavior
  • Endpoint behavior
  • Contract behavior
  • Operation behavior

In this recipe, let's look at how we can change each of these behaviors of the service.

How to do it...

Now, let's take steps to extend the WCF service with various schemes:

  1. Let's say we want our MyDummyOperation to only be called using the HTTPS scheme, and when we call the service using HTTP, the operation fails. To do this, we create a class and implement it from IServiceBehavior as follows:
    class RequireHttpsBehavior : 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)
        {
        }
    
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (var endpoint in serviceDescription.Endpoints)
            {
                if (endpoint.Binding.Scheme != Uri.UriSchemeHttps)
                    throw new InvalidOperationException("This service requires HTTPS");
            }
        }
    }

    In the preceding code, inside the Validate method, we check all the endpoints associated with the service and we throw an InvalidOperationException when we find an endpoint other than the HTTPS scheme.

  2. We then add the ServiceBehavior service either in the form of an attribute on the ServiceContract class (for that we need to inherit from the Attribute class), or we can directly add an object of this behavior in the ServiceHost object.
    host.Description.Behaviors.Add(new RequireHttpsBehavior());

    Now if I run the project, it will give an exception saying the service requires HTTPS.

  3. The EndpointBehavior service can also be tweaked using the interface IEndpointBehavior as follows:
    class LoggingEndpointBehavior : IEndpointBehavior
    {
    
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }
    
        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(new LoggingInspector());
        }
    
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
               
        }
    
        public void Validate(ServiceEndpoint endpoint)
        {
        }
    }
  4. Here, we have added EndpointBehavior such that it logs messages on the screen. We have added the LoggingInspector class, which inspects any requests and logs the data in the console.
  5. To intercept ContractBehavior, we need to implement the IContractBehavior class. Let's define a contract behavior such that we use BinaryEncoder for the service rather than the textual encoders:
    class BinaryEncoderContractBehavior : Attribute, IContractBehavior
        {
            public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
            {
                if (endpoint.Binding.CreateBindingElements().Find<MessageEncodingBindingElement>() == null)
                {
                    bindingParameters.Add(new BinaryMessageEncodingBindingElement());
                }
            }
  6. Here, we are changing the binding encoder to binary. We use BinaryMessageEncodingBindingElement as the binding for the contract such that when messages are sent or received, it uses the binary encoding rather than the text encoding. Remember, we generally use this tweak when we create CustomBinding for an operation. Before adding an encoder to the binding, it is also important to check whether there is already any binding associated. The ContractBehavior service can be added as an attribute to the ServiceContract class.
  7. Operation behaviors are used to tweak values for an application. The WebGet or WebInvoke methods are operation behaviors that specify the actual URITemplate for the operation, and tweak the request to connect to an operation. Let's define an operation that will change the negative value to a positive value:
      class NonNegativeOperationBehavior : Attribute, IOperationBehavior
        {
            public void ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)
            {
                var originalInvoker = dispatchOperation.Invoker;
                dispatchOperation.Invoker = new NoNegativeInvoker(originalInvoker);
            }
    
            public void Validate(OperationDescription operationDescription)
            {
                if (operationDescription.Messages.Count < 2 || operationDescription.Messages[1].Body.ReturnValue.Type != typeof(double))
                {
                    throw new InvalidOperationException("This behavior can only be applied on operation which returns double");
                }
            }
        }

    Here, we have checked whether the operation that was invoked returns a double value. We check ReturnValue of the second message on OperationDescription as WCF returns two messages for an operation. We also create a new OperationInvoker to manipulate the value received when the operation is invoked.

  8. To write the invoker, we pass the OriginalInvoker class as a wrapper, so that we return OriginalInvoker in places where we do not need to tweak, and we also do not need to rewrite the whole invoker as follows:
    class NoNegativeInvoker : IOperationInvoker
    {
        public NoNegativeInvoker(IOperationInvoker originalInvoker)
        {
            this.OriginalInvoker = originalInvoker;
        }
    
        public IOperationInvoker OriginalInvoker { get; set; }
    
        public object[] AllocateInputs()
        {
            return OriginalInvoker.AllocateInputs();
        }
    
        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            var result=  OriginalInvoker.Invoke(instance, inputs, out outputs);
            result = Math.Abs((double)result);
            return result;
        }
    
        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return OriginalInvoker.InvokeBegin(instance, inputs, callback, state);
        }
    
        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            var oresult= OriginalInvoker.InvokeEnd(instance, out outputs, result);
            oresult = Math.Abs((double)oresult);
            return oresult;
        }
    
        public bool IsSynchronous
        {
            get { return OriginalInvoker.IsSynchronous; }
        }
    }

    In NoNegativeInvoker, we store OriginalInvoker as a property as it is passed in its constructor, and we return the values from OriginalInvoker wherever required. To tweak the behavior, we get the results of the invoke from OriginalInvoker and change it to an absolute value. We also need to do the same thing to InvokeEnd for async operations.

  9. After adding NonNegativeOperationBehavior as an attribute for an operation that returns a double value, we ensure that it does not generate a negative value.

How it works…

The behaviors are one of the primary key elements for WCF programming. While defining a service endpoint, you can configure it so that the service behavior calls your own code rather than already predefined behaviors associated with the WCF API. In this recipe, we have covered how you can use interface hooks to define your own ServiceBehavior, EndPointBehavior, ContractBehavior, and OperationBehavior classes. As we go down the line, the WCF supports a lot of other enhancements, such as runtime extensibility, ServiceModel extensibility, WCF channel extensibility, and serialization extensibility.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.216.175