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:
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:
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:
In this recipe, let's look at how we can change each of these behaviors of the service.
Now, let's take steps to extend the WCF service with various schemes:
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.
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.
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) { } }
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.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()); } }
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.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.
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.
NonNegativeOperationBehavior
as an attribute for an operation that returns a double value, we ensure that it does not generate a negative value.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.
3.137.216.175