WCF Data Services / WCF Behaviors And Server Side Processing

Server Side Processing of Data in WCF Data Services Using WCF Behaviors

Introduction

One of the concerns around the data services is their ability to control the outgoing data. Unfortunately, Entity Framework provider of WCF Data Services does not support processing data before forwarding to the clients. Many applications need such processing to cover different requirements such as logging the outgoing data, manipulating the default data before sending to the clients, hiding some special data from the clients, combining different data sources with the data of entity framework and etc.

Last month, I showed an approach that enables the developers to control the outgoing data of Data Services. That approach uses the customized version of LINQ to SQL to do so. In this paper, I will show an approach that can be applied to all types of Data Services even the ones that use the Entity Framework Provider. The new approach processes the outgoing data using the facilities provided by WCF Behaviors. The advantage of using WCF behaviors is the fact that they deploy in a plug & play manner and they don’t disturb the main developing code. The idea is attaching a behavior to the service host and listens to the outgoing data. The behavior raises an event whenever a WCF message has been sent to the clients. By capturing the event, the outgoing data can be read and even changed. Using the solution is simple and just needs two lines of code. First, you should change the default ServiceFactory of your service to use the AppServiceHostFactory as follows:

<%@ ServiceHost Language="C#" Factory="ICP.DataServices.CustomProcessing.AppServiceHostFactory"

Service="ICP.DataService.CustomProcessing.ServerUI.WcfDataService" %>

Then, you should listen to the ProcessBeforeSendMessage event of the ICP.DataServices.CustomProcessing.Inspector class.

ICP.DataServices.CustomProcessing.Inspector.ProcessBeforeSendMessage += new EventHandler<ICP.DataServices.CustomProcessing.Inspector.InspectorEventArgs>(Inspector_ProcessBeforeSendMessage);

static void Inspector_ProcessBeforeSendMessage(object sender, Inspector.InspectorEventArgs e)

{

XmlDocument document = new XmlDocument();

document.LoadXml(e.Data);

}

In the event handler, the Data property of the Inspector.InspectorEventArgs contains the outgoing message in the xml format and you can process the message and change it if you wish. The advantage of this code is one doesn’t need to be aware of Behaviors and their details. The disadvantage is, the message is in xml format and the developer should parse it himself.

The rest of the paper is as follows. In the first section I describe REST Services and Data Services briefly. In the next section, the concepts that are related to WCF Behaviors have been illustrated. In the last section I explain the code of my custom behavior.

REST Services and WCF Data Services

Nowadays, Rest services are a strong rival of SOAP-based Web services in many areas especially for applications that the focus of their services is CRUD operations. REST services are based on the facilities provided by the HTPP protocol. Although HTTP protocol is around for many years, but the idea of using it to do CRUD operations on servers becomes attractive in just recent years. The idea is quite simple. HTTP protocol has the PUT, POST, GET and DELETE methods corresponding to the Update, Create, Read and Delete respectively. Each entity in the server can be specified by a url and the http actions can be used to specify the CRUD operations in the server. In general, using http protocol makes the REST services a light weight approach compare to the SOAP services.

Microsoft makes a big stride in developing REST services by introducing ADO.net Services. Known as WCF Data Services in the Framework 4.0, they allow a very simple way to expose database tables as REST-full services. Data Services can be published on a Web Server or any other WCF Host environment. Creating them is simple. Just create an Entity Framework, map the tables to conceptual classes in the entity framework and finally, configuring the Entity Framework in the InitializeService method of the data service. Pretty simple! Isn’t it? After publishing the data service, even a simple web browser can consume the data.

Microsoft provides three main providers to create a WCF Data Service. The most flexible option is creating a custom provider by implementing the IDataServiceMetadataProvider and the IDataServiceQueryProvider interfaces. The data types that have been published by such WCF Data Services are dynamic. In case that extreme flexibility is the main requirement I suggest you to implement those interfaces, but to be honest with you, implementing them is not easy at all. The other option to create a WCF Data Service is using ReflectionProvider. The reflection provider exposes data of classes that implement IQueryable interface. WCF Data Services uses reflection to infer a data model for these classes and can translate address-based queries into language integrated query (LINQ)-based queries against the exposed IQueryable types. My previous solution about processing data in the server is based on ReflectionProvider. The last and easiest way to define a data service is using Entity Framework provider. This provider uses the ADO.NET Entity Framework to enable you to use relational data with a data service by defining a data model that maps to relational data. Your data source can be SQL Server or any other data source with third-party provider support for the Entity Framework. You should use the Entity Framework provider when you have a relational data source, such as a SQL Server database.

The other option that has worth to mention is writing customizable REST services using the WCF REST Starter Kit provider. Historically, WCF REST Starter Kit is the first utility in the .NET that covers the REST services requirements. The Starter kit contains several classes that make it easy to publish data using REST services and generally they are lower level compare to the data services. Although their support for parsing the queries is not comparable with the data services.

Quick Overview of WCF Host and Behaviors
ServiceHost


Windows Communication Foundation (WCF) services are designed to run in any Windows process that supports managed code. In order to activate them, they should be hosted by instances of ServiceHost class. Even if you host a WCF service in IIS, the IIS will create a ServiceHost behind the sense to host the service. ServiceHost loads a service, configures endpoints, applies security settings, and start listeners to handle incoming requests.


ServiceFactory


In order to simplify configuration of ServiceHost instances, .NET framework provides several ServiceHostFactory classes that their responsibility is creating and configuring the ServiceHosts. Here is the signature of the class


[TypeForwardedFrom("System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]

public class ServiceHostFactory : ServiceHostFactoryBase

{

public ServiceHostFactory();

public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses);

protected virtual ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses);

}


As can be seen from its signature, the purpose of the ServiceHostFactory is just creating a customized ServiceHost instance. There are several derivations of the ServiceHostFactory class including WorkflowServiceHostFactory, WebScriptServiceHostFactory, and DataServiceHostFactory.


ServiceDescription


Exposing a service requires a complete service description (represented by the ServiceDescription class). The ServiceHost class creates a ServiceDescription from the service type and configuration information and then uses that description to create ChannelDispatcher objects for each endpoint in the description. ServiceDescription of the ServiceHost Represents a complete, in-memory description of the service, including all the endpoints for the service and specifications for their respective addresses, bindings, contracts and behaviors.


Behaviors


One of the cool features of the ServiceDescription is its collection of Behaviors. Every class that implements the
IServiceBehavior or IEndpointBehavior interfaces can be a behavior. The signature of the IServiceBehavior is as follows:

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);

}

Behaviors can be used to extend the functionality of the WCF. Behaviors can be used to process and changes the incoming and outgoing messages. To be honest with you, many features of the WCF have been done using Behaviors. If you debug a WCF application and see the value of Behaviors property using a debugger, you can see that there are several default behaviors in the ServiceDescription including the following behaviors for data services.


Microsoft.VisualStudio.Diagnostics.ServiceModelSink.Behavior, System.ServiceModel.Description.ServiceAuthenticationBehavior,

System.ServiceModel.Description.ServiceAuthorizationBehavior,

System.ServiceModel.Description.ServiceDebugBehavior


Behaviors can attach their Inspectors to the service in their ApplyDispatchBehavior method. Inspectors are classes that implement the IDispatchMessageInspector interface. Inspectors provide a straight forward mechanism to process the WCF messages. Here is the signature of the interface.


public
interface IDispatchMessageInspector

{

object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext);

void BeforeSendReply(ref Message reply, object correlationState);

}


In the AfterReceiveRequest method, the incoming message can be changed. The request property is the incoming message and as you can see, it has been passed by reference which means the method can change it. In the BeforeSendReply method, the outgoing message can be changed. The reply property is the outgoing message and as you can see, it has been passed by reference which means the method can change it.

Custom ServiceFactory and Behavior

The whole thing that I want to do is creating my custom Behavior and applying it on the WCF Data Service. In order to make my Behavior active, it must be registered in the ServiceDescription. There are two options to do so. The first option is adding the Behavior in the configuration file. The second option is adding the Behavior to the ServiceDescription programmatically. I prefer the second option here, simply because it helps me to ride away from complex configuration file. The best place to add the behavior programmatically is in the ServiceFactory. ServiceFactory is responsible for creating the ServiceHost and we can easily attach our behavior there. Here is the code of the customizable ServiceFactory.

class AppServiceHostFactory : System.Data.Services.DataServiceHostFactory

{

protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)

{

System.Data.Services.DataServiceHost host = new System.Data.Services.DataServiceHost(serviceType, baseAddresses);

host.Description.Behaviors.Add(new ListenerServiceBehavior());

return host;

}

}

Please note that the method returns an instance of the DataServiceHost which is derived from the ServieHost and is specialized to host WCF data services. Before returning it, an instance of ListenerServiceBehavior which is my Behavior has been added to the host. Here is the code of the ListenerServiceBehavior and its Inspector.

public class ListenerServiceBehavior : IServiceBehavior

{

#region IServiceBehavior Members

public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

{

}

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

{

return;

}

public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

{

foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers)

{

foreach (EndpointDispatcher epDisp in chDisp.Endpoints)

{

epDisp.DispatchRuntime.MessageInspectors.Add(new Inspector());

}

}

}

#endregion

}

public class Inspector : IDispatchMessageInspector

{

#region Types

public class InspectorEventArgs : EventArgs

{

public string Data;

public InspectorEventArgs(string data)

{

Data = data;

}

public InspectorEventArgs()

{

}

}

#endregion

#region IDispatchMessageInspector Members

public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)

{

if (!request.IsEmpty)

{

MessageBuffer buffer = request.CreateBufferedCopy(10000);

Message message = buffer.CreateMessage();

}

return null;

}

public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)

{

if (!reply.IsEmpty)

{

MessageBuffer buffer = reply.CreateBufferedCopy(1000000);

Message message = buffer.CreateMessage();

string str = message.ToString();

if (buffer.MessageContentType == "application/soap+msbin1" && str.StartsWith("<Binary>"))

{

str = str.Replace("<Binary>", "").Replace("</Binary>", "");

str = Encoding.UTF8.GetString(Convert.FromBase64String(str));

InspectorEventArgs eventArgs = new InspectorEventArgs(str);

onProcessBeforeSendMessage(this, eventArgs);

byte[] data = Encoding.UTF8.GetBytes(eventArgs.Data);

string newStr = Convert.ToBase64String(data);

newStr = String.Format("<Binary>{0}</Binary>", newStr);

XmlReader reader = XmlReader.Create(new StringReader(newStr));

Message outgoing = Message.CreateMessage(reply.Version, message.Headers.Action, reader);

foreach (string key in message.Properties.Keys)

{

outgoing.Properties.Add(key, message.Properties[key]);

}

outgoing.Headers.Clear();

foreach (MessageHeader header in message.Headers)

{

outgoing.Headers.Add(header);

}

reply = outgoing;

}

else if (buffer.MessageContentType != "application/soap+msbin1")

{

InspectorEventArgs eventArgs = new InspectorEventArgs(str);

onProcessBeforeSendMessage(this, eventArgs);

Message outgoing = buffer.CreateMessage();

reply = outgoing;

}

else

{

Message outgoing = buffer.CreateMessage();

reply = outgoing;

}

}

}

#endregion

#region Events

public static event EventHandler<InspectorEventArgs> ProcessBeforeSendMessage;

private static void onProcessBeforeSendMessage(object sender, InspectorEventArgs e)

{

if (ProcessBeforeSendMessage != null)

{

ProcessBeforeSendMessage(sender, e);

}

}

#endregion

}

The Inspector in the above code reads the reply message, convert it to a string and raise the ProcessBeforeSendMessage event. The raised event contains the string format of the message and anyone that listens to the event can change the outgoing message by changing the string format of the event. The property of raised event has been used to create a new message and substitute the original message with it. In general, it sounds simple, but the devil is in the details.

You can download the code here. It contains the Server code, a simple client and the used DB. You need VS 2010 to run the code.

By Siyamand Ayubi   Popularity  (6525 Views)