WCF Extensibility – Configuring the Endpoint for WCF RIA Services

This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page .

And we’re again taking a small detour over the normal flow of the series covering the “proper” WCF extensibility points. This time, we’ll talk about how you can use (almost) all the extensibility points seen before to configure the endpoint used by a WCF RIA Service. WCF RIA Services is a framework which allows easy creation of n-tiered rich internet applications using Silverlight, by coordinating the application logic between the middle tier and the presentation tier. As the name suggests, WCF RIA Services use WCF as the communication framework underneath, by exposing endpoints which are consumed by a “transparent” proxy on the Silverlight application – unlike on “normal” WCF scenarios, if you create an application using the WCF RIA Services, there’s no need to explicitly create a proxy to the service to access it in a typed way – anytime a change is made in a domain service class in the service, that change is automatically propagated to the client so it can be used right away. Domain service is the term used to describe services a service written using the WCF RIA Services framework.

Since WCF RIA Services use WCF, it’s possible to use all of the applicable extensibility points in the server to configure the endpoint which is used by the framework. This post is about how to get to the endpoint from the WCF RIA Services framework, so that you can use all the extensions which have already been covered in this series. Notice that this is quite an advanced scenario, requiring not only that you use WCF RIA Services in the first place, but also that the existing endpoint types not being enough (which is not common), but since I’ve answered a similar question in the Silverlight forum for WCF, I decided to include it here in case other people run into this same scenario.

The “entry point” to the WCF world in the WCF RIA Service is the DomainServiceEndpointFactory class. The goal of the class is to, given a description for the domain service, return one (or more) WCF endpoints which will be used by the client to communicate with that tier. We’ll cover it as we did other “normal” extensibility points of WCF.

Public implementations in the WCF RIA Services

There are a few public domain service endpoint factories, both in the “main” framework and in the "WCF RIA Services Toolkit”, an “out-of-band” release of improvements for the framework.

The first factory is the default one used by the framework. The second one can be enabled in the Silverlight Business Application VS template, while the ones from the toolkit need to be enabled manually by editing the web.config on the web application.

Class Definition

  1. public abstract class DomainServiceEndpointFactory
  2. {
  3.     public abstract IEnumerable<ServiceEndpoint> CreateEndpoints(
  4.         DomainServiceDescription description, DomainServiceHost serviceHost);
  5. }

The DomainServiceEndpointFactory class has essentially one method which needs to be overriden: CreateEndpoints. Given the description of the domain service, and the instance of the service host (which is an instance of the DomainServiceHost class, itself a subclass of the WCF ServiceHost class). Usually only one endpoint is returned by each factory, but it’s possible to have one factory return multiple endpoints.

How to use a custom domain service endpoint factory

Custom domain service endpoint factories are added by adding a reference under the <system.serviceModel> / <domainServices> / <endpoint> element of the configuration file. The “pox binary” endpoint is added by default, any other endpoints need to be added based on the assembly-qualified name class which inherits from DomainServiceEndpointFactory.

  1. <system.serviceModel>
  2.   <domainServices>
  3.     <endpoints>
  4.       <add name="OData" type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  5.       <add name="JSON" type="Microsoft.ServiceModel.DomainServices.Hosting.JsonEndpointFactory, Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  6.       <add name="MyCustom" type="SLApp.Web.MyCustomFactory, SLApp.Web" />
  7.     </endpoints>
  8.   </domainServices>
  9. </system.serviceModel>

Real world scenario: supporting HTML forms data in WCF RIA Services

This one came from the Silverlight forums for WCF (Accessing Web Services with Silverlight). A user had a simple service which was consumed not only by the Silverlight client, but also by a third-party application which used the application/x-www-form-urlencoded content-type. I don’t know whether the 3rd party was able to change the content type or not, but the user wanted to know whether it was possible to be supported on a domain service. Well, domain services are built on top of WCF, and this scenario is listed on this post, so of course this can be done Smile.

First of all, this is really not a common scenario. In most cases, clients can use the JSON endpoint to communicate to domain service. You can call all CRUD operations, and also [Invoke] operations using that endpoint which works quite well from JavaScript. You can find a good overview of the JSON endpoint for WCF RIA Services in Joseph Connoly’s blog (part 1, part 2). But in this case apparently the client couldn’t be changed, so we can create our own endpoint factory which supports that media type as well.

First, as usual, a scenario. You can start by creating a new “Silverlight Business Application” on Visual Studio (or Visual Web Developer Express), which will give a basic template for such an application, with a login feature. For this sample we’ll ignore the authentication part, as it’s not relevant to this post. Let’s then create a contact list, which will be exposed in a domain service. I’ll create a “POCO” domain service instead of the usual, database-backed, one, to make this sample simpler to run, but the idea works for all domain services.

  1. public class Contact
  2. {
  3.     [Key]
  4.     public int Id { get; set; }
  5.  
  6.     [Required]
  7.     public string Name { get; set; }
  8.  
  9.     public int Age { get; set; }
  10.  
  11.     public string Telephone { get; set; }
  12. }

And we can now create a domain service to expose it to the client. The contact repository, used below, is a simple in-memory representation of the contacts. And that’s a good point to insert the usual disclaimer: this is a sample for illustrating the topic of this post, this is not production-ready code. I tested it for a few operations and it worked, but I cannot guarantee that it will work for all scenarios – please let me know if you find a bug. The “repository” is an in-memory list of the objects which is definitely not what a real scenario would use. Also, as usual, I’ve kept the error checking to a minimum, which needs to happen in a real application.

  1. [EnableClientAccess()]
  2. public class ContactsDomainService : DomainService
  3. {
  4.     [Query]
  5.     public IQueryable<Contact> GetAllContacts()
  6.     {
  7.         return ContactRepository.Get();
  8.     }
  9.  
  10.     [Insert]
  11.     public void CreateContact(Contact contact)
  12.     {
  13.         ContactRepository.Add(contact);
  14.     }
  15.  
  16.     [Update]
  17.     public void UpdateContact(Contact contact)
  18.     {
  19.         ContactRepository.Update(contact);
  20.     }
  21.  
  22.     [Delete]
  23.     public void RemoveContact(Contact contact)
  24.     {
  25.         ContactRepository.Delete(contact);
  26.     }
  27. }

We can also add “invoke” operations to domain services, which may or may not have any side effects in the domain context. We’ll add one of those operations, which we’ll use to invoke with both the “normal” JSON input, and with the modified forms-urlencoded data. The [CanReceiveFormsUrlEncodedInput] is a tagging attribute which we defined to let the inspector to know that it should try to handle this case.

  1. [Invoke(HasSideEffects = true)]
  2. [CanReceiveFormsUrlEncodedInput]
  3. public Contact ImportContact(Contact contact)
  4. {
  5.     ContactRepository.Add(contact);
  6.     return contact;
  7. }

Now for the actual code which does the trick. There are quite a few ways we can go about it. We can build a custom message formatter which understands the form-urlencoded data and converts it to the appropriate parameters in the operation. That could work, but I don’t know if the formatter used by the domain service does anything else – it’s possible that it does. What I ended up doing instead was to use a message inspector to do a translation between the forms-urlencoded format and the JSON which should be sent to the JSON endpoint in the first place.

So, let’s start with the endpoint factory. Since we’ll just do a translation, I’ll create a class which inherits from the JsonEndpointFactory class itself. Then, when it’s asked to create the endpoints, it first gets the endpoints from the base class, then inserts the “translator” before the other behaviors in the main endpoint. The behavior itself does nothing more than add an inspector, which will do the bulk of the work.

  1. public class FormUrlEncodedEnabledJsonEndpointFactory : JsonEndpointFactory
  2. {
  3.     public const string FormUrlEncodedInputProperty = "FormUrlEncodedInputProperty";
  4.  
  5.     public override IEnumerable<ServiceEndpoint> CreateEndpoints(System.ServiceModel.DomainServices.Server.DomainServiceDescription description, System.ServiceModel.DomainServices.Hosting.DomainServiceHost serviceHost)
  6.     {
  7.         List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>(base.CreateEndpoints(description, serviceHost));
  8.         ServiceEndpoint jsonEndpoint = endpoints[0];
  9.         jsonEndpoint.Behaviors.Insert(0, new FormUrlEncodedToJsonEndpointBehavior());
  10.         return endpoints.AsEnumerable();
  11.     }
  12. }
  13.  
  14. class FormUrlEncodedToJsonEndpointBehavior : IEndpointBehavior
  15. {
  16.     public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
  17.     {
  18.     }
  19.  
  20.     public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  21.     {
  22.     }
  23.  
  24.     public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  25.     {
  26.         endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new FormUrlEncodedToJsonInspector(endpoint));
  27.     }
  28.  
  29.     public void Validate(ServiceEndpoint endpoint)
  30.     {
  31.     }
  32. }

The inspector will look at the request headers of the message, trying to find any with the forms encoding. If the incoming message is such one, it will then look at the last part of the request URI (which represents the operation name), and then find that operation in the endpoint description. If that operation exists, and it’s marked with the attribute that allows such input to be sent, we start the conversion process.

The translation starts by getting the data in binary format (after all, no mapper is registered for the application/x-www-form-urlencoded content type, so it will be mapped to raw / binary), and using the ParseQueryString(String) method from the HttpUtility class to convert between the input and a collection of name/value pairs. Later, we start creating the equivalent JSON, and with that in hand, we create a new message, copying the headers and properties from the original one. One property which we need to change is the WebBodyFormatMessageProperty, to tell the WCF pipeline that it’s now dealing with JSON data.

  1. class FormUrlEncodedToJsonInspector : IDispatchMessageInspector
  2. {
  3.     ServiceEndpoint endpoint;
  4.     public FormUrlEncodedToJsonInspector(ServiceEndpoint endpoint)
  5.     {
  6.         this.endpoint = endpoint;
  7.     }
  8.  
  9.     public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
  10.     {
  11.         HttpRequestMessageProperty reqProp = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
  12.         if (reqProp.Headers[HttpRequestHeader.ContentType] == "application/x-www-form-urlencoded")
  13.         {
  14.             string operation = request.Headers.To.AbsolutePath;
  15.             int lastSlash = operation.LastIndexOf('/');
  16.             if (lastSlash >= 0)
  17.             {
  18.                 operation = operation.Substring(lastSlash + 1);
  19.             }
  20.  
  21.             OperationDescription operationDescription = this.endpoint.Contract.Operations.Find(operation);
  22.             if (operationDescription != null &&
  23.                 operationDescription.Behaviors.Find<CanReceiveFormsUrlEncodedInputAttribute>() != null)
  24.             {
  25.                 // Decode the forms-urlencoded input
  26.                 XmlDictionaryReader bodyReader = request.GetReaderAtBodyContents();
  27.                 byte[] input = bodyReader.ReadElementContentAsBase64();
  28.                 string inputStr = Encoding.UTF8.GetString(input);
  29.                 NameValueCollection parameters = HttpUtility.ParseQueryString(inputStr);
  30.  
  31.                 // Create an equivalent JSON
  32.                 StringBuilder json = new StringBuilder();
  33.                 json.Append('{');
  34.                 this.ConvertNVCToJson(operationDescription, parameters, json);
  35.                 json.Append('}');
  36.  
  37.                 // Recreate the message with the JSON input
  38.                 byte[] jsonBytes = Encoding.UTF8.GetBytes(json.ToString());
  39.                 XmlDictionaryReader jsonReader = JsonReaderWriterFactory.CreateJsonReader(jsonBytes, XmlDictionaryReaderQuotas.Max);
  40.                 Message newMessage = Message.CreateMessage(request.Version, null, jsonReader);
  41.                 newMessage.Headers.CopyHeadersFrom(request);
  42.                 newMessage.Properties.CopyProperties(request.Properties);
  43.  
  44.                 // Notify the application this this change happened
  45.                 OperationContext.Current.IncomingMessageProperties.Add(FormUrlEncodedEnabledJsonEndpointFactory.FormUrlEncodedInputProperty, true);
  46.                 newMessage.Properties.Add(FormUrlEncodedEnabledJsonEndpointFactory.FormUrlEncodedInputProperty, true);
  47.  
  48.                 // Change the 'raw' input to 'json'
  49.                 newMessage.Properties.Remove(WebBodyFormatMessageProperty.Name);
  50.                 newMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Json));
  51.  
  52.                 request = newMessage;
  53.             }
  54.         }
  55.  
  56.         return null;
  57.     }
  58. }

The conversion between the name/value collection and the JSON is fairly straightforward. For simple operations (i.e., those taking primitive parameter types, such as strings, bools and numbers) we can just create a JSON object mapping each name/value pair as a member of that object. We can even map those non-strings parameters as JSON strings, since the JSON deserializer used by WCF is smart enough to deserialize JSON strings representing numbers (or bools) into the appropriate type. However, I thought we could go further, that we could also support simple complex types as well, such as the Contact we showed before. For that, we need to map the name value pairs not as parameters themselves, but as members of the contact object. To implement that, I used a simple wrapping technique that if there is a single parameter in the operation, and the parameter name isn’t present in the name/value pairs, then we’ll wrap the values in another JSON object, and have that as a member whose name is the same as the operation parameter. In the Contact case, for example, it will map an input such as Name=John+Doe&Age=33&Telephone=415-555-1234 into the JSON {“contact”:{“Name”:”John Doe”,”Age”:”33”,”Telephone”:”415-555-1234”}}.

  1. private void ConvertNVCToJson(OperationDescription operationDescription, NameValueCollection parameters, StringBuilder json)
  2. {
  3.     bool wrapRequest = false;
  4.     string firstParameterName = null;
  5.     if (operationDescription.Messages[0].Body.Parts.Count == 1)
  6.     {
  7.         firstParameterName = operationDescription.Messages[0].Body.Parts[0].Name;
  8.         // special case for inputs of complex types
  9.         if (parameters[firstParameterName] == null)
  10.         {
  11.             wrapRequest = true;
  12.         }
  13.     }
  14.  
  15.     if (wrapRequest)
  16.     {
  17.         json.Append("\"" + firstParameterName + "\":{");
  18.     }
  19.  
  20.     bool first = true;
  21.     foreach (string key in parameters.Keys)
  22.     {
  23.         if (first)
  24.         {
  25.             first = false;
  26.         }
  27.         else
  28.         {
  29.             json.Append(",");
  30.         }
  31.  
  32.         json.AppendFormat("\"{0}\":\"{1}\"", key, parameters[key]);
  33.     }
  34.  
  35.     if (wrapRequest)
  36.     {
  37.         json.Append("}");
  38.     }
  39. }

And that’s it. Now we need to create a HTML page to test that. Since I haven’t worked with the JSON endpoint too much, I first tried using a simple JSON request just to make sure that everything was ok, before going with the simple HTML form (which uses the form-urlencoded content type). The form has two buttons, one which uses a JavaScript code (JSON-ifying the input prior to sending to the service), and another which is “pure” HTML. The JSON one will format the entry appropriately, then after submitting the data, will redirect to the main Silverlight page, which will display the added contact. The HTML post button will just submit the data. But the HTML post expects to receive a HTML page back, so I edited the [Invoke] operation to redirect to the page at the server side (see code in the gallery).

  1. <body>
  2.     <h1>Contact importer</h1>
  3.     <form action="SLApp-Web-ContactsDomainService.svc/JSON/ImportContact" method="post">
  4.         <p>Name: <input type="text" id="Name" name="Name" /></p>
  5.         <p>Age: <input type="text" id="Age" name="Age" /></p>
  6.         <p>Telephone: <input type="text" id="Telephone" name="Telephone" /></p>
  7.         <p><input type="button" value="Import Contact (using jQuery)" onclick="Call()" /></p>
  8.         <p><input type="submit" value="Import Contact (using plain HTML)" /></p>
  9.     </form>
  10.  
  11.     <script type="text/javascript">
  12.         function Call() {
  13.             var data = {
  14.                 Name: $("#Name").val(),
  15.                 Age: parseInt($("#Age").val()),
  16.                 Telephone: $("#Telephone").val()
  17.             };
  18.  
  19.             var json = JSON.stringify({ contact: data });
  20.  
  21.             $.ajax({
  22.                 url: "SLApp-Web-ContactsDomainService.svc/JSON/ImportContact",
  23.                 type: "POST",
  24.                 contentType: "application/json",
  25.                 data: json,
  26.                 complete: function () {
  27.                     window.location = "SLAppTestPage.html";
  28.                 }
  29.             });
  30.         }
  31.     </script>
  32. </body>

And that’s it. Granted, it’s not a very common scenario, but it just shows how to get into the world of WCF extensibility from within a WCF RIA Service.

[Code in this post]

[Back to the index]

Comments

  • Anonymous
    July 18, 2014
    I have a VS Silverlight project in WCF RIA Service and my answer is: Is it necessary to have a endpoint configured?Anyway, my project do not run in IIS, just in VS enviromment. Any help?Thanks in advance.Luis
  • Anonymous
    July 18, 2014
    In the vast majority of the cases you don't need to configure the endpoint. It's often configured just the way the client needs it to be. Only if you have some non-standard scenario is that you need to use this extensibility point.