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:
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
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.
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
In
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" ActionmessageSetter = 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, Collectionendpoints, 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
The
public interface IStatus { ActionSetStatus { 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
That means. Your WSDL generation works!
Now check if the SOAP Extension works.
When I press the Validate button, the client sends a Request to the Service. The Service will check the
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
Seems to work! ๐