WCF / Entity Framework and POCO Status Extension

By | December 7, 2010

With DataTables you have the ability to set a Error Message per Row. This is useful, when you want to display i.e. the Validation Errors in the GUI.
In a 3-Tier Application your client communicates with the Application Server via Services. Exchanging DataTables in a .NET Application works well. But if your services are used in a heterogeneous environment (i.e. Java or PHP Clients) you will not use DataTables.

.NET supports since version 4.0 POCO Entities. After adding a Entity Model (*.edmx) you can add a T4 Code Generation (*.tt) File, which defines the Template for your POCOs.

OK. We know POCO Objects are compatible with other Languages like Java. But you don’t have the possibility to set a Error Message (like in DataTable). You can extend your Entities with a additional Property… or defining a base Class and so on…
But there is a other way, when you want to keep your Entities clean.

If you need the Error Message only for Client-side work you can develop a WCF extension.
This extension extends your SOAP response with a Status Element. On the server-side you have your clean POCOs and on the client-side the POCOs will have an additional Property called “Status”.

Simple overview:
WCF Extension

How it works:
Step 1.
Most clients generate a Service Proxy with the help of the WSDL file. That means, you have to add your Status Field in the WSDL XML. Otherwise your client POCOs won’t have a Status Property.
This is possible with WCF WSDL Interception.

Implement the IWsdlExportExtension and IEndpointBehavior Interfaces. In the ExportEndpoint Method add your Status Element. (See below)

public class WsdlStatusExporter : IWsdlExportExtension, IEndpointBehavior
{
    public void ExportContract(WsdlExporter exporter, 
        WsdlContractConversionContext context)
    { }

    public void ExportEndpoint(WsdlExporter exporter, 
        WsdlEndpointConversionContext context)
    {
        // Get schemas
        IEnumerator en = exporter.GeneratedXmlSchemas.Schemas().GetEnumerator();
        while (en.MoveNext())
        {
            XmlSchema schema = (XmlSchema)en.Current;
            foreach (XmlSchemaObject obj in schema.Items)
            {
                // POCOs are ComplexTypes
                if (obj is XmlSchemaComplexType)
                {
                    // Add Status Element
                    var complex = (XmlSchemaComplexType)obj;
                    var status = new XmlSchemaElement() 
                    { 
                        Name = "Status", 
                        MinOccurs = 0, 
                        IsNillable = true 
                    };
                    // OK. This code here is ugly. I copied the SchemaType of my first Element. (Type is string)...
                    status.SchemaTypeName = ((XmlSchemaElement)
                        ((XmlSchemaSequence)complex.Particle).Items[0]).SchemaTypeName;
                    // Add the new elemnt
                    ((XmlSchemaSequence)complex.Particle).Items.Add(status);
                }
            }
        }
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, 
        BindingParameterCollection bindingParameters)
    { }

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

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
        EndpointDispatcher endpointDispatcher)
    { }

    public void Validate(ServiceEndpoint endpoint)
    { }
}

Your WSDL Exporter Class must be added to the Behaviors List of your Endpoint.
Here a example of my WSDL.
WSDL Export

Step 2.
Now your client POCOs have a Status Property. In Step 2, you have to inject the Status Message in the Soap Response.
Now you have to write and register a Message Interceptor.
Implement the IDispatchMessageInspector. In the AfterReceiveRequest reference the Service Object with the Status Message String of the Inspector.
In BeforeSendReply write the Status Message in the SOAP Response.

public class StatusInterceptor : IDispatchMessageInspector
{
    private string StatusMessage { get; set; }

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, 
        InstanceContext instanceContext)
    {
        // This Actions fills the private string "StatusMessage"
        Action messageSetter = status => { this.StatusMessage = status; };
        messageSetter(string.Empty);
        // Pass the Action to the Service Instance
        ((IStatus)instanceContext.GetServiceInstance()).SetStatus = messageSetter;
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        var doc = new XmlDocument();
        doc.Load(reply.GetReaderAtBodyContents());
        // ...Response / ...Result
        var child = doc.ChildNodes[0].ChildNodes[0];

        // I used the default schema namespace
        var statusEle = doc.CreateElement("a", "Status", "http://schemas.datacontract.org/2004/07/Service");
            
        // Add Status Element
        statusEle.InnerText = this.StatusMessage;
        doc.ChildNodes[0].ChildNodes[0].AppendChild(statusEle);

        // Write to new Message and replace the old one
        var reader = new XmlNodeReader(doc.DocumentElement);
        var msg = Message.CreateMessage(reply.Version, null, reader);
        msg.Headers.CopyHeadersFrom(reply);
        msg.Properties.CopyProperties(reply.Properties);
        reply.Close();
        reply = msg;
    }
}

Now create a Attribute in which you register the Interceptor.

[AttributeUsage(AttributeTargets.Class)]
public class RegistrationBehavior : Attribute, IServiceBehavior
{
    public void AddBindingParameters(ServiceDescription serviceDescription, 
        ServiceHostBase serviceHostBase, Collection endpoints, 
        BindingParameterCollection bindingParameters)
    { }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, 
        ServiceHostBase serviceHostBase)
    {
        for (int x = 0; x < serviceHostBase.ChannelDispatchers.Count; x++)
        {
            // Register the StatusInterceptor
            var disp = (ChannelDispatcher)serviceHostBase.ChannelDispatchers[x];
            if (disp != null)
                foreach (EndpointDispatcher end in disp.Endpoints)
                    end.DispatchRuntime.MessageInspectors.Add(new StatusInterceptor());
        }
    }

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

Step 3.
Now write your service. And don't forget to add the above written Attribute to the Service class.
The Service implements beside the IService (Contract) interface a IStatus Interface.
The IStatus.SetStatus will be used by the RegistrationBehavior class (see above).

public interface IStatus
{
    Action SetStatus { get; set; }
}

[ServiceContract]
public interface IService
{
    [OperationContract]
    person CheckPerson(person pers);
}

// Attribute!!
[RegistrationBehavior]
public class Service : IService, IStatus
{
    public person CheckPerson(person pers)
    {
        // Your business logic
        if (pers.firstname.Length == 0)
        {
            // Set the status (error)
            this.SetStatus("Firstname is empty. Sorry : )");
        }
        else
        {
            // Set the status (info)
            this.SetStatus("Firstname filled!");
        }
        return pers;
    }

    public Action SetStatus
    {
        get;
        set;
    }
}

Step 4.
GREAT! ๐Ÿ™‚
Now host your Service. Here an example of a self-hosted Service with Extension.

static void Main(string[] args)
{
    var address = new Uri("http://localhost:8811/service");
    var host = new ServiceHost(typeof(Service), address);

    var binding = new BasicHttpBinding();
    var endpoint = host.AddServiceEndpoint(typeof(IService), binding, address);
    // Ad the WSDL extension
    endpoint.Behaviors.Add(new WsdlStatusExporter());
    host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
    host.Open();

    Console.WriteLine("OK! I'm ready!");
    Console.ReadLine();
    host.Close();
}

Now test the Extension.
I created a WPF client Application and generated the Service Proxy.
As you see, your person Object has a Status Property. ๐Ÿ™‚
Status
That means. Your WSDL generation works!

Now check if the SOAP Extension works.
WPF Client
When I press the Validate button, the client sends a Request to the Service. The Service will check the Firstname Property and will set a Status Message.
After receiving the Response, the client will show the Status in a MessageBox.
(Here the code. Otherwise you won't believe me ๐Ÿ™‚ )


public MainWindow()
{
    InitializeComponent();
    this.client = new srv.ServiceClient();
    this.DataContext = new srv.person() 
    { 
        firstname = "", 
        lastname = "....." 
    };
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    var result = this.client.CheckPerson(new srv.person() 
        { 
            firstname = this.Firstname.Text, 
            lastname = this.Lastname.Text 
        });
    MessageBox.Show(result.Status, "Validation Message", 
        MessageBoxButton.OK, MessageBoxImage.Exclamation);
}

Here the result (when firstname is empty):
Status Message

Seems to work! ๐Ÿ˜‰