Productive Rage

Dan's techie ramblings

WCF CORS (plus JSON & REST) - Complete Example

Someone asked me the other day if I knew how to enable CORS (Cross Origin Resource Sharing for a WCF service. This is commonly used to enable AJAX requests from a web page to retrieve content from a domain outside of the domain that delivered the page that the JavaScript is executing from. For a number of reasons, this is not allowed by default by web browsers but the security measure may be relaxed in modern browsers if the data is delivered with certain headers in the response:

Access-Control-Allow-Origin: *
Access-Control-Request-Method: POST,GET,PUT,DELETE,OPTIONS
Access-Control-Allow-Headers: X-Requested-With,Content-Type

There's information about this on the "Enable CORS" website: CORS on WCF.

My friend had found this information and struggled to make it work. It looked like it should be simple enough to me, so I thought I'd give it a go.

I did not find it simple.

But I cracked it in the end! So I'm recording the necessary steps here for posterity.. or for when I might need it in the future. Truth be told, there's no one thing that's mind-blowingly difficult, it's just a case of trying to remember how WCF ties things together when you've not dealt with it for a little while.

Modern WCF configuration - so easy to get started!

When Visual Studio 2010 and .net 4 were released, one of the things they introduced was cleaner web.config files that used nice defaults to prevent the bloat that had been added over time. (ScottGu talked about it at Clean Web.Config Files - there he uses a web application rather than a WCF service as an example, but works was done on the WCF side too).

The initial web.config you get when you go to New Project / WCF / WCF Service Application in VS 2013 is:

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5"/>
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
      <add binding="basicHttpsBinding" scheme="https"/>
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>

(I removed a couple of XML comments to make it more succint but didn't change anything else).

This is great when you want to get cracking with the default settings but when you want to apply customisations it's not always clear where to start. The Enable CORS documentation says that you have to

  1. Create a couple of classes
  2. "Register new behavior in web.config"
  3. "Add new behavior to endpoint behavior configuration"
  4. "Configure endpoint"

I'm going to take an example project and apply all of these steps, explaining what each one means (largely because when I first read them, I couldn't remember what each one meant in the context of WCF configuration!).

If you want to follow along at home, create a new WCF Service Application project and call it "CORSExample". This will create the files IService1.cs, Service1.svc and Web.config. Change IService1.cs's content to

using System.ServiceModel;
using System.ServiceModel.Web;

namespace CORSExample
{
  [ServiceContract]
  public interface IService1
  {
    [OperationContract]
    ServiceResponse GetData(string value);
  }
}

and change Service1.svc to

using System;
using System.ServiceModel.Activation;

namespace CORSExample
{
  public class Service1 : IService1
  {
    public ServiceResponse GetData(string value)
    {
      return new ServiceResponse
      {
        ReceivedAt = DateTime.Now,
        Value = string.Format("You entered: {0}", value)
      };
    }
  }
}

Then add a new file "ServiceResponse.cs" and set its content to

using System;
using System.Runtime.Serialization;

namespace CORSExample
{
  [DataContract]
  public class ServiceResponse
  {
    [DataMember]
    public DateTime ReceivedAt { get; set; }

    [DataMember]
    public string Value { get; set; }
  }
}

This gives us the outline of a very basic service. You could start this project up and then create a WCF client to communicate with it. It's the most basic example you can likely imagine that takes any form of input and returns a non-primitive-type response. I wanted a "complex response type" to show how responses may be JSON-serialised very easily.. but that comes later, we're not returning JSON at the moment!

Laying the groundwork

Starting with the code on the CORS on WCF page, I took the two classes and combined them into one, removing some potentially-customisable code and replacing it with something that does just the job at hand. This results in a smaller surface area of exposed "new code" and means that I have less to confuse myself with!

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace CORSExample
{
  public class CORSEnablingBehavior : BehaviorExtensionElement, IEndpointBehavior
  {
    public void AddBindingParameters(
      ServiceEndpoint endpoint,
      BindingParameterCollection bindingParameters) { }

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

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
      endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
        new CORSHeaderInjectingMessageInspector()
      );
    }

    public void Validate(ServiceEndpoint endpoint) { }

    public override Type BehaviorType { get { return typeof(CORSEnablingBehavior); } }

    protected override object CreateBehavior() { return new CORSEnablingBehavior(); }

    private  class CORSHeaderInjectingMessageInspector : IDispatchMessageInspector
    {
      public object AfterReceiveRequest(
        ref Message request,
        IClientChannel channel,
        InstanceContext instanceContext)
      {
        return null;
      }

      private static IDictionary<string, string> _headersToInject = new Dictionary<string, string>
      {
        { "Access-Control-Allow-Origin", "*" },
        { "Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS" },
        { "Access-Control-Allow-Headers", "X-Requested-With,Content-Type" }
      };

      public void BeforeSendReply(ref Message reply, object correlationState)
      {
        var httpHeader = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
        foreach (var item in _headersToInject)
          httpHeader.Headers.Add(item.Key, item.Value);
      }
    }
  }
}

So add a file "CORSEnablingBehavior.cs" to the project and populate it with the above content.

This will, if we can attach it to the right thing in the right place at the right time, inject the response headers that we require.

To do so, we're going to have to add a "services" section to the web.config (within "system.serviceModel"). This config section is optional and so is not present in the bare bones config that Visual Studio created for us. We need to add it because we need to override some options -

<services>
  <service name="CORSExample.Service1">
    <endpoint address="" binding="webHttpBinding" contract="CORSExample.IService1" />
  </service>
</services>

It's important that we specify the "webHttpBinding" since the CORSEnablingBehavior implementation will fail without it.

Having created this section, we must then fully populate it. The address attribute can stay blank (changing this alters the URLs that we make requests through - changing it to "something" would mean that requests from a client would have to POST their xml to "/Service1.svc/something" instead of just "/Service1.svc"). The contract attribute must match the type name (including namespace) of the interface precisely and the service name attribute must match the implementation class' type name (including namespace) precisely. If either of these are incorrect then Visual Studio is nice enough to draw your attention to this fact with a blue wobbly underline (the warning message "The Enumeration Constraint failed" could be friendlier, but this is basically what it means).

Now we need to configure the "webHttpBinding". The default is "basicHttpBinding" and that works out of the box. But if we tried to call the service having only made the change above, we'd be presented with a ProtocolException stating

The content type application/xml; charset=utf-8 of the response message does not match the content type of the binding (text/xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly.

At least, that's what you'd get if you had a debugger attached to the process making the request. If you were making a request from a web application you would get a yellow screen of death showing something like

ProtocolException Yellow Screen Of Death

So we need to add another section (this time within "behaviors")

<endpointBehaviors>
  <behavior>
    <webHttp />
  </behavior>
</endpointBehaviors>

If we go back to this fictional WCF client that I'm assuming you're testing the service with for now, you'll need to update the service reference since there's a different binding mechanism.

Then you're in for another treat. When configuring a Service Reference to a WCF service that uses basicHttpBinding, the client's web.config will be populated with information describing how to connect. Excellent, no problem. When the service uses webHttpBinding, however, it does not. This is explained in the post Mixing Add Service Reference and WCF Web HTTP endpoint does not work.

We can work around it for now by manually adding some content into the client's web.config (we'll be changing this service to work with JSON soon, and so probably not be consuming it through a generated WCF client - at that point we won't have to worry about this client web.config issue).

<?xml version="1.0"?>
<!-- This is the CLIENT web.config (required to consume a WCF service that uses webHttpBinding) -->
<configuration>

   <!-- This is default web.config content -->
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>

  <!-- This is the content that needs adding to consume the service-->
  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="webhttp">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <bindings>
      <webHttpBinding>
        <binding name="WebHttpBinding_IService1" />
      </webHttpBinding>
    </bindings>
    <client>
      <endpoint name="WebHttpBinding_IService1" contract="CORSExample.IService1"
        binding="webHttpBinding" bindingConfiguration="WebHttpBinding_IService1"
        address="http://localhost:51192/Service1.svc"
        behaviorConfiguration="webhttp" />
    </client>
  </system.serviceModel>

</configuration>

Note that the 51192 port in the endpoint address may vary for your test project. If you go to the project's properties page you should be able to find the port there.

Enables the CORS!

Right, we're really getting there now! Now we need to introduce the CORSEnablingBehavior class. In the service's web.config we need to add a new section (inside "system.servicemodel") -

<extensions>
  <behaviorExtensions>
    <add
      name="crossOriginResourceSharingBehavior"
      type="CORSExample.CORSEnablingBehavior, CORSExample, Version=1.0.0.0, Culture=neutral" />
  </behaviorExtensions>
</extensions>

and then change the "endpointBehaviors" content we added before to

<endpointBehaviors>
  <behavior>
    <webHttp />
    <crossOriginResourceSharingBehavior />
  </behavior>
</endpointBehaviors>

Note: Doing this results in the "crossOriginResourceSharingBehavior" node being squiggly-underlined as an "invalid child element" - this is normal and may be ignored. Also note that the "crossOriginResourceSharingBehavior" string is arbitrary and may be any value so long as it is consistent between the two places in which it is used (the "name" attribute in the "behaviorExtensions" section and the actual node name in "endpointBehaviors"). However, the "type" attribute must match the type name (including namespace) precisely of the CORSEnablingBehavior class that we added earlier.

So now if you make a request from your WCF client the response will contain the required headers!

Success!!

Right now you only have my word for it, since Visual Studio doesn't show the raw messages that are passed in a WCF request. At times like this, I always turn to the trusty Fiddler. If you're not familiar with it, get familiar - it's fantastic! :) If you are familiar with it then you may well know that it doesn't register web requests made to "localhost". The easiest thing to do is to change the URL of the request so that "localhost" is replaced with "localhost.fiddler" (do this in the web.config of the client). Then Fiddler will show the details of the exchange. Just be aware that when Fiddler isn't connected that "localhost.fiddler" won't work.

As a recap, here's the complete WCF service web.config we've built for the "CORSExample" project:

<?xml version="1.0"?>
<configuration>

  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>

  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5"/>
  </system.web>

  <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add
          name="crossOriginResourceSharingBehavior"
          type="CORSExample.CORSEnablingBehavior, CORSExample, Version=1.0.0.0, Culture=neutral" />
      </behaviorExtensions>
    </extensions>

    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior>
          <webHttp />
          <crossOriginResourceSharingBehavior />
        </behavior>
      </endpointBehaviors>
    </behaviors>

    <services>
      <service name="CORSExample.Service1">
        <endpoint address="" binding="webHttpBinding" contract="CORSExample.IService1" />
      </service>
    </services>

  </system.serviceModel>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>

</configuration>

A JSON endpoint

As I said at the start, I think that the most common use case for CORS-enabled services such as this is to make AJAX requests from JavaScript on a web page. To this end, it would be great if we didn't have to rely upon XML messages. It would be much better to be able to make the requests in some sort of RESTful manner (where the request is essentially represented by the URL, rather than a POST'd XML-serialised message) and to have the response expressed as JSON.

I'm going to leave thinking any deeper about the nitty gritty of what it means to be RESTful for another day (it can be a contentious issue!) and just make the current example communicate through a GET request that passes the "value" parameter as part of the URL.

This is actually extraordinarily easy at this point. The only change required is to the IService1 interface, it should now read

using System.ServiceModel;
using System.ServiceModel.Web;

namespace CORSExample
{
  [ServiceContract]
  public interface IService1
  {
    [WebGet(UriTemplate = "GetData/{value}", ResponseFormat = WebMessageFormat.Json)]
    [OperationContract]
    ServiceResponse GetData(string value);
  }
}

This allows requests to be made such as

http://localhost:51192/Service1.svc/GetData/123

from the browser and for the response to be visible as serialised JSON

{"ReceivedAt":"\/Date(1403736100464+0100)\/","Value":"You entered: 123"}

Being in the browser, you don't even need Fiddler to see the response headers, you can use built-in developer tools and see that the headers are present.

Note that there was no change required to the service implementation nor to the response class - in the same way that it can be serialised to XML, the service can serialise the response to JSON.

There is one thing I maybe cheated on a little bit, though. My GetData method's argument is conveniently a string. If this is changed to anything else (an int, for example) then the service will not start up and throw an exception

Operation 'GetData' in contract 'IService1' has a path variable named 'value' which does not have type 'string'. Variables for UriTemplate path segments must have type 'string'.

But getting into all of the ins and outs of configuring requests for JSON are outside the scope of this post, I think. The aim was to enable CORS - and explain every part of what was required in doing so - and I think I've done that well enough for today. Enjoy!

Posted at 21:35

Comments

Why do you hate my (WCF) types, PHP??

Last November, I was helping someone consume a WCF Web Service with PHP (in the imaginatively named Consuming a WCF Web Service from PHP). After jumping through some hoops (and reading a lot of unhelpful and/or misleading information on the web) it was working; requests that relied on type names being specified were being accepted, gzip support was being enabled, even some useful debug information was being made available for when problems were encountered. All was well. But there was something that was bugging me for a long time that I only quite recently was able to address -

Why does the PHP SoapClient so belligerently throw away the type names of response objects?

It has knowledge of the type name since it must process the response data to populate the associative arrays that represent this data. But the names of the response types are apparently then cast forever into the ether, never to exposed to me. After all, I'm using PHP - I don't want no stinkin' types!

Is it just me?

I feel I should probably explain why I care so much. To be fair, I imagine that in a large number of cases the type name of the returned data really isn't important. If, for example, I'm querying the Twitter API for a set of Statuses then I know the form of the returned data (and since it returns JSON, there are no type names in the responses!). And for a lot of services, I imagine the form of the returned data will be identical from one result to another and, in many of the cases where the forms vary, a form of "property sniffing" will deal with it; does this result have this particular property along with all of the common ones? If so, save it or use it or do whatever with it.

But there are cases where this isn't enough. In that earlier post, the example was a web method "GetHotels" which returned hotel data for results that matched a particular set of filters (in that case, the type names were important for the request since an array of filters was specified, each filter was a particular WCF class - without the type names, the service couldn't deserialise the request).

Each of the returned hotels has data such as Categories, Awards, and Facilities but only the keys of these Categories, Awards and Facilities are returned. There is a separate web method "GetMetaData" that maps these keys onto names. A language can be specified as part of the meta data request so that the names are provided in the correct language.

Some of the meta data types may have additional data, such as an optional ImageUrl for Awards. Categories can be grouped together, so Categories such "Budget Hotel", "Boutique Hotel" and "Garden Hotel" are all considered to be part of the Category Group "Hotel" whilst "Guest House", "Farmhouse" and "Inn" are all considered part of the "Bed & Breakfast" Category Group.

The natural way to express this in a WCF Web Service (making use of wsdl-supported complex types) is something like the following -

[ServiceContract]
public class HotelService
{
  [OperationContract]
  public MetaDataEntry[] GetMetaData(MetaDataRequest request)
  {
    ..
  }
}

[DataContact]
public class MetaDataRequest
{
  [DataMember]
  public string APIKey { get; set; }

  [DataMember]
  public string LanguageCode { get; set; }

  [DataMember]
  public MetaDataType[] MetaDataTypes { get; set; }
}

public enum MetaDataType
{
  Award,
  Category,
  CategoryGroup,
  Facility
}

[KnownType(AwardMetaDataEntry)]
[KnownType(CategoryMetaDataEntry)]
[KnownType(CategoryGroupMetaDataEntry)]
[KnownType(FacilityMetaDataEntry)]
[DataContract]
public abstract class MetaDataEntry
{
  [DataMember(IsRequired = true)]
  public int Key { get; set; }

  [DataMember]
  public string Name { get; set; }
}

[DataContract]
public class AwardMetaDataEntry : MetaDataEntry
{
  [DataMember]
  public string ImageUrl { get; set; }
}

[DataContract]
public class CategoryMetaDataEntry : MetaDataEntry
{
  [DataMember(IsRequired = true)]
  public int CategoryGroup { get; set; }
}

[DataContract]
public class CategoryGroupMetaDataEntry : MetaDataEntry { }

[DataContract]
public class FacilityMetaDataEntry : MetaDataEntry { }

The MetaDataRequest allows me to specify which types of meta data that I'm interested in.

So, feasibly, if I wanted to build up a set of Categories to map the keys from the Hotels onto, I could make a request for just the meta data for the Categories. If I then want to map those Categories onto Category Groups, I could make a request for the Category Group meta data.

But why shouldn't I be able to request all of the meta data types, loop through them and stash them all away for future reference all in one go? I could do this easily enough with a .net client. Or a Java client. But, by default, PHP refuses to allow a distinction to be made between a CategoryGroupMetaDataEntry and a FacilityMetaDataEntry since they have the same structure and PHP won't tell me type names.

Well.. that's not strictly true. PHP does have some means to interrogate type names; the methods "gettype" and "get_class". If you define a class in your PHP code and pass an instance of it to the "get_class" method, you will indeed get back the name of that class. "get_class" may only be given an argument that is an object, as reported by the "gettype" method (see the get_class and gettype PHP documentation).

But if we try this with the web service call -

$client = new SoapClient(
  "http://webservice.example.com/hotelservice.svc?wsdl",
  array(
    "compression" => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
    "trace" => 1
  )
);
$metaDataTypes = $client->GetMetaData(
  array(
    "request" => array(
      "ApiKey" => "TestKey",
      "Language" => 1,
      "MetaDataTypes" => array(
        "MetaDataTypeOptions" => array(
          "Award",
          "Category",
          "CategoryGroup",
          "Facility"
        )
      )
    )
  )
);

we can loop through the returned data and use get_class to find out that.. they are all apparently "StdObject".

This is what I meant by the type names being "thrown away".

Duck-typing (doesn't work if everything quacks and waddles)

In some cases we can work around this.

For example, to guess that a result is an AwardMetaDataEntry we could try

if (property_exists($metaDataValue, "ImageUrl")) {

and work on the basis that if it exposes an "ImageUrl" property that it is AwardMetaDataEntry.

But this won't work for differentiating between a CategoryGroupMetaDataEntry and a FacilityGroupMetaDataEntry since those response types have no structural differences.

Class Mappings

It turns out that the SoapClient does offer a way to get what we want, so long as we don't mind declaring PHP classes for every response type that we're interested in.

class MetaDataEntry
{
  public $Key;
  public $Name;
}

class AwardMetaDataEntry extends MetaDataEntry
{
  public $ImageUrl;
}

class CategoryMetaDataEntry extends MetaDataEntry
{
  public $CategoryGroup;
}

class CategoryGroupMetaDataEntry extends MetaDataEntry { }

class FacilityMetaDataEntry extends MetaDataEntry { }

As we can see in the PHP SoapClient documentation, one of the options that can be specified is a "classmap" -

This option must be an array with WSDL types as keys and names of PHP classes as values

It's a way to say that particular response types should be mapped to particular PHP classes - eg.

$client = new SoapClient(
  "http://webservice.example.com/hotelservice.svc?wsdl",
  array(
    "compression" => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
    "trace" => 1,
    "classmap" => array(
      "AwardMetaDataEntry" => "AwardMetaDataEntry",
      "CategoryMetaDataEntry" => "CategoryMetaDataEntry",
      "CategoryGroupMetaDataEntry" => "CategoryGroupMetaDataEntry",
      "FacilityMetaDataEntry" => "FacilityMetaDataEntry"
    )
  )
);

Now when we loop through the response values and call get_class we get the correct names. Success!

(In the above code I've named the PHP classes the same as the WSDL types but, since the mappings all have to be individually specified, the class names don't have to be the same. The properties, on the other hand, do have to match since there is no facility for custom-mapping them. Any classes that don't have a mapping will continue to be translated into objects of type StdObject).

It may well be that this is far from news for many seasoned PHP Developers but when I described the situation (before finding out about the "classmap" option) to someone I was told was experienced and competent they had no suggestion in this direction.

To be honest, I'm not sure how I came across this in the end. If you know that there exists an option to map classes with the SoapClient then it's easy to find; but with only a vague idea that I wanted it to stop throwing away type names, it took me lots of reading and clutching at straws with search terms. Interestingly, even with this knowledge, I'm still unable to find an article that describes the specific problem I've talked about here.. so maybe it really is just me that has encountered it or cares about it!

Posted at 00:03

Comments

WCF with JSON (and nullable types)

I wanted to try putting together a WCF Service that would return JSON. With its configurable nature, I've heard before that it's not that big of a deal to do this.. and truth be told, it's not that awkward to do but it took me a while to find all the right hoops to jump through! (Another time, I might consider trying to put together a RESTful API using MVC and the JsonResult type unless there was some need to support multiple response types, such as XML and JSON).

The best way to go through it is probably with an example, so here's a simple Service Contract interface, note the "WebGet" attribute and the BodyStyle and ResponseFormat values.

using System;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace WCFJSONExample
{
    [ServiceContract]
    public interface IPostService
    {
        [OperationContract]
        [WebGet(BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
        PostResponseDetails Search(int id, DateTime postedAfter, DateTime postedBefore);
    }
}

When communicating through SOAP, the standard request method is to POST the data but for the purposes here I want to enable GET requests that return JSON. I also want the search parameters to be simple types, rather than having to support a complex SearchRequestDetails type, for example, that could specify the various criteria. There's nothing special about the PostResponseDetails type, though:

using System;
using System.Runtime.Serialization;

namespace WCFJSONExample
{
    [DataContract]
    public class PostResponseDetails
    {
        [DataMember]
        public StatusDetails Status { get; set; }

        [DataMember]
        public PostDetails[] Posts { get; set; }
    }

    [DataContract]
    public class StatusDetails
    {
        [DataMember]
        public bool Success { get; set; }

        [DataMember]
        public string AdditionalStatusInformation { get; set; }
    }

    [DataContract]
    public class PostDetails
    {
        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public DateTime Posted { get; set; }

        [DataMember]
        public string Title { get; set; }

        [DataMember]
        public string Content { get; set; }
    }
}

We're going to get the framework to serialise the response data for us, so nothing unusual was required there.

Where things do need changing from the defaults, though, are some of the settings in the web.config. Within system.serviceModel / services, we need to add the following (the "services" node won't exist if you're working with a clean web.config file - ie. a web.config as Visual Studio will generate for a new "WCF Service Application" project):

<service name="WCFJSONExample.PostService">
    <endpoint
        name="jsonEndPoint"
        address=""
        binding="webHttpBinding"
        behaviorConfiguration="json"
        contract="WCFJSONExample.IPostService"
    />
</service>

And within system.serviceModel / behaviours / endpointBehaviors we need to add (again, the "behaviours" node won't exist in the clean / default web.config, just add it and the child "endpointBehaviors" node in, if required):

<behavior name="json">
    <webHttp />
</behavior>

And that's it! Web Service calls can be made by specifying the method name as part of the url - eg.

http://localhost:62277/PostService.svc/Search?id=1

Well..

When making requests, if any of the parameters are not included then they will be given default values - eg.

http://localhost:62277/PostService.svc/Search?id=1

will result in a method call with id specified as 1 and both postedAfter and postedBefore with the value default(DateTime). I wanted to be able to specify nullable types for the method arguments so that if I needed a method where I could differentiate between integer values being specified as zero and not being specified (and so appearing to be zero as that is default(int)).

But changing the Operation Contract to

[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
PostResponseDetails Search(int? id, DateTime? postedAfter, DateTime? postedBefore);

results in the unfriendly error:

Operation 'GetLogData' in contract 'IPostService' has a query variable named 'fromDate' of type 'System.Nullable1[System.DateTime]', but type 'System.Nullable1[System.DateTime]' is not convertible by 'QueryStringConverter'. Variables for UriTemplate query values must have types that can be converted by 'QueryStringConverter'.

It turns out that the WebHttp behaviour uses an internal class "QueryStringConverter" that will only translate particular types. But we can use a different end point behaviour that uses a different query string converter. Most of the behaviour of the webHttp behaviour (which corresponds to the System.ServiceModel.Description.WebHttpBehavior class) is correct and we just want to extend it, so we'll create a new class that inherits from it and tweak it slightly in that manner.

The new class looks like this:

using System;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace WCFJSONExample
{
    public class NullableSupportingWebHttpBehaviourExtension : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get
            {
                return typeof(NullableSupportingWebHttpBehaviour);
            }
        }

        protected override object CreateBehavior()
        {
            return new NullableSupportingWebHttpBehaviour();
        }

        private class NullableSupportingWebHttpBehaviour : WebHttpBehavior
        {
            protected override QueryStringConverter GetQueryStringConverter(
                OperationDescription operationDescription)
            {
                return new NullableSupportingQueryStringConverter();
            }

            private class NullableSupportingQueryStringConverter : QueryStringConverter
            {
                public override bool CanConvert(Type type)
                {
                    if (base.CanConvert(type))
                        return true;

                    Type nullableInnerType;
                    return TryToGetNullableTypeInformation(type, out nullableInnerType)
                        && base.CanConvert(nullableInnerType);
                }

                public override object ConvertStringToValue(
                    string parameter,
                    Type parameterType)
                {
                    Type nullableInnerType;
                    if (TryToGetNullableTypeInformation(parameterType, out nullableInnerType))
                    {
                        if (parameter == null)
                            return null;
                        return ConvertStringToValue(parameter, nullableInnerType);
                    }

                    return base.ConvertStringToValue(parameter, parameterType);
                }

                public override string ConvertValueToString(
                    object parameter,
                    Type parameterType)
                {
                    Type nullableInnerType;
                    if (TryToGetNullableTypeInformation(parameterType, out nullableInnerType))
                    {
                        if (parameter == null)
                            return null;
                        return ConvertValueToString(parameter, nullableInnerType);
                    }

                    return base.ConvertValueToString(parameter, parameterType);
                }

                private bool TryToGetNullableTypeInformation(Type type, out Type innerType)
                {
                    if (type == null)
                        throw new ArgumentNullException("type");

                    if (!type.IsGenericType
                    || (type.GetGenericTypeDefinition() != typeof(Nullable<>)))
                    {
                        innerType = null;
                        return false;
                    }

                    innerType = type.GetGenericArguments()[0];
                    return true;
                }
            }
        }
    }
}

And we plumb it in by adding this to system.serviceModel / extensions / behaviorExtensions (again, the "extensions" node and its "behaviorExtensions" child node need adding in to a vanilla web.config):

<add
    name="postServiceWebHttp"
    type="WCFJSONExample.NullableSupportingWebHttpBehaviourExtension, WCFJSONExample"
/>

And then replace the "webHttp" node of the behaviour we added above with a "postServiceWebHttp" node, thus:

<behavior name="json">
    <postServiceWebHttp />
</behavior>

The QueryStringConverter's CanConvert, ConvertStringToValue and ConvertValueToString methods are overridden so that we can pick up on parameters that are of the type Nullable<T> and deal with them appropriately - returning null if a null value is stored in the type and dealing with the wrapped value if not.

This could easily be changed to perform different translation actions, if required (it could feasibly be integrated with a JSON serialiser to deal with complex data types, for example).

Posted at 22:46

Comments

Consuming a WCF Web Service from PHP

For the Web Service that I've been developing at work, I was asked to support a Developer who was trying to consume it for a project. The problem being that he was developing in PHP and had never connected to a .Net Web Service before - at least not knowingly; the APIs he'd integrated with before would all communicate in JSON. The other half of the problem is that I've never developed anything in PHP! In fact I'd not written a line in my life before last week.

From what I'm led to understand of PHP it's somewhat of a mess of 1000s of functions with inconsistent names, signatures and approaches. It's dynamically typed and may or may not have namespaces in any usable manner. But it's got an enormous set of well-established libraries available for everything under the sun. So this should be a walk in the park, right??

Below is a simplified version of what we're working with; the idea that you could search for Hotels that meet various criteria, where the criteria can be built up with the application of multiple Filters - at least one must be specified but multiple may be, in which case Hotels must meet the criteria in all Filters in order to be returned.

[ServiceContract]
public class HotelService
{
    [OperationContract]
    public Hotel[] GetHotels(HotelSearchRequest request)
    {
        ..
    }
}

[DataContact]
public class HotelSearchRequest
{
    [DataMember]
    public string APIKey { get; set; }

    [DataMember]
    public Filter[] Filters { get; set; }
}

[KnownType(CategoryFilter)]
[KnownType(ProximityFilter)]
[DataContract]
public abstract class Filter { }

[DataContract]
public class CategoryFilter : Filter
{
    [DataMember]
    public int[] CategoryKeys { get; set; }
}

[DataContract]
public class ProximityFilter : Filter
{
    [DataMember]
    public CoordinateDetails Centre { get; set; }

    [DataMember(IsRequired = true)]
    public int MaxDistanceInMetres { get; set; }
}

We have to mess about a bit with KnownType declarations in the right place in the service contract but once that's all sorted the service is easy to consume from a .Net WCF Client, all of the inheritance is easily understood (as they're documented in the xsd that the service exposes) and queries are easy to construct.

Getting things working in PHP is another matter, however. For someone who doesn't know what he's doing, at least! All of the basic examples suggest something along the lines of:

$client = new SoapClient(
    "http://testhotelservice.com/HotelService.svc?wsdl",
    array("trace" => 1)
);
$data = $client->GetHotels(
    array(
        "request" => array(
            "ApiKey" => "{KeyGoesHere}",
            "Filters" => array(
                // TODO: What to write here?!
            )
        )
    )
);

It seems like the SoapClient can do some sort of magic with what are presumably associative arrays to build up the web request. All looks good initially for declaring data for the "request" argument of the GetHotels method; we set the "ApiKey" property of the request to an appropriate string.. the SoapClient must be doing something clever with the wsdl to determine the xml to generate, which must include the type name of the request to specify. But if the type names are intended to be hidden, how am I going to specify them when I build the "Filters" array?? I can't just carry on with this type-name-less associated-array approach because there will be no way for the request to know that I want the CategoryFilter or the ProximityFilter (or any other Filter that might be available).

Hmmm...

More googling brings me to discover the SoapVar class for use with the SoapClient. If we do the following:

$client = new SoapClient(
    "http://testhotelservice.com/HotelService.svc?wsdl",
    array("trace" => 1)
);
$data = $client->GetHotels(
    array(
        "request" => array(
            "ApiKey" => "{KeyGoesHere}",
            "Filters" => array(
                new SoapVar(
                    array(
                        "CategoryKeys" => array(1, 2, 3)
                    ),
                    SOAP_ENC_OBJECT,
                    "CategoryFilter",
                    "http://schemas.datacontract.org/2004/07/DemoService.Messages.Requests"
                )
            )
        )
    )
);

Then we are able to include information about the type. Progress! The namespace string specified references the C# namespace of the CategoryFilter class.

As with so many things, it looks all so easy. But I didn't know what exactly I should be searching for, getting this far took me quite a while - and the resource out there explaining this are thin on the ground! With the maturity of both PHP and WCF I would have thought that information about calling into WCF Services from PHP in this manner would be much readily available!

But wait; there's more

While I was at it, I thought I'd dig a little further. When I first communicated with this other Developer, I asked him to send me a trace of the requests that were being generated by his PHP code, using Fiddler or something. This information was not forthcoming and when I was running test PHP scripts on my local machine the requests weren't being captured by Fiddler anyway. But I found these handy methods:

$client->__getLastRequest()
$client->__getLastRequestHeaders()

which will retrieve the sent content, ideal for looking into exactly what messages were being generated! These only work if the "trace" => "1" argument is specified when the SoapClient is instantiated. Which explained to me what that was for, which was nice :)

Enabling gzip support

The next issue I had was another one that I thought would be beyond easy to solve, with easy-to-follow and accurate information all over the place. I was wrong again! At least, my first searches didn't bring me immediately to the answer :(

A lot of resources suggest the following:

$client = new SoapClient(
    "http://testhotelservice.com/HotelService.svc?wsdl",
    array(
        "compression" => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP | 9,
        "trace" => 1
    )
);

and some suggest this variation (quoting the compression value):

$client = new SoapClient(
    "http://testhotelservice.com/HotelService.svc?wsdl",
    array(
        "compression" => "SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP | 9",
        "trace" => 1
    )
);

These do not work. The first results in a "can't uncompress compressed response" after a delay which makes me think that it's doing work. The latter does cause any error but also doesn't include the "Accept-encoding: gzip" HTTP header that I'm looking for.

They both feel wrong, anyway; presumably the 9 relates to gzip compression level 9. The compression level should surely be set on the server only, not referenced by the client?? And what are these SOAP_COMPRESS_ACCEPT and SOAP_COMPRESSION_GZIP values? These values are just numeric constants, it turns out, which are OR'd together in the first variation. But what's the 9 for; is it supposed to be there at all; is it some other mystery constant?? And surely the quoted version is incorrect unless PHP has some mad string rules that I don't know about (totally possible with my knowledge of PHP but not the case here! :)

The correct version is simply:

$client = new SoapClient(
    "http://testhotelservice.com/HotelService.svc?wsdl",
    array(
        "compression" => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
        "trace" => 1
    )
);

Again, oh so simple yet so much harder to come to in the end than it should have been.

One more thing

A final note, that actually was commonly documented and pointed out, was that if you are developing against a service that is still in flux that you should disable wsdl caching in PHP. This will affect performance as the wsdl will be retrieved on each request (I presume), but it may prevent some headaches if the contract changes. I changed a value in the php.ini file but apparently it can also be done in the PHP script with:

ini_set("soap.wsdl_cache_enabled", 0);

(Courtesy of a StackOverflow answer).

Conclusion

This may well be simple stuff to the real PHP developers out there but since I struggled, maybe this post will help others in the future with this particular problem!

Posted at 23:03

Comments

Ramping up WCF Web Service Request Handling.. on IIS 6 with .Net 4.0

It's got to the point with a Web Service I'm developing at work to run it through some stress tests and try to work out what kind of load we can handle with the hardware we have in mind for its hosting environment. We also have an arbitrary concept of the load we'd like it to be able to handle, so we need to see if the hardware solution we have in mind is even in the right ballpark.

It's a WCF Web Service running against .Net 4.0 but it's going to be hosted in IIS 6; the Web Servers our Infrastructure Team provide us with are all Server 2003 and although there are mid-term plans to start upgrading to 2008 and IIS 7 this isn't going to happen before this service needs to be live.

The Web Service effectively backs on to a Data Service we already have for our own private use that handles querying and caching of data and metadata, so this Web Service's primary job is to expose this data in a saner manner than the internal interface (which was built some years back and has a lot of legacy features, exposes too much craziness from the backing database which evolved over the best part of a decade and the internal service is communicates over .Net Remoting as it was originally written against .Net 2.0!). So all we're really doing is translating requests from the Web Service model into the internal format and then performing a similar act on the responses, there won't be too many caching concerns, any direct db access or much file IO; the bulk of its time I expect will be waiting on the internal service.

Gotcha Number One

To kick things off, I wanted to get an idea of how much load the internal service could handle on a single server and then see how things changed when I put a Web Server in front of it, hosting the new Service. To cut a long story short, I was able to reproduce requests to the internal service from the busiest few hours the night before and serialise them all to disk so that they could be replayed at any time against the stress-testing environment. The optimistic plan being to ramp up the speed at which the requests are replayed and see where the bottlenecks start appearing and at what sort of loads!

The first problem I encountered was when replaying these from a test app on my computer to the data service in the load test environment. As the Web Service requests were ramping up the request times were getting longer and longer. I looked on the Web Server and found that it was doing very little; CPU and memory use was low and looking at the "Web Service"* performance counters it was not only dealing with very few requests but there weren't any queuing up which had been my initial thought.

* (The "Web Service" in this context relates to the service in IIS, it's not a Web Service in the context of an ASP.Net Web Service, for example).

Some googling revealed that outgoing http requests are limited by default in .Net applications, so the requests were getting queued up locally and weren't even getting to the server! It seemed like only two concurrent requests were being handled though I'm sure documentation suggested that it should have been 16. Not that it matters, really, that would have way been too low as well!

Adding this to the app.config sorted it out:

<system.net>
    <connectionManagement>
        <add address="*" maxconnection="65535" />
    </connectionManagement>
</system.net>

The "*" means that this will be applied to any endpoint. The 65535 was what I found in the example and I've been struggling to find a definitive answer as to whether this is the maximum value that it can be or not! :S

Server Settings: Thread Counts

So now I was able to overload the server with more concurrent requests than it could handle at once and they were getting queued up. The performance counters are just invaluable here; there are some available for the "Web Service" - which tells us about what's happening at the IIS level - and for "ASP.Net" - which tells us about what's getting into .Net framework. So there could theoretically be limits in IIS that need to be overcome and/or limits in ASP.Net, keeping an eye on these counters gives an insight into where the bottlenecks might be.

I ramped up the request replay rate and it seemed like the maximum concurrent connections that I could achieve was around 250. The CPU load was low, around 15%, and the memory usage was fairly meagre. Most of the time in the requests was waiting for the data service to respond, so threads were just hanging around. Not enough threads for my liking! I wanted to use more of the resources on the box and be able to deal with more requests.

I spent aaaages trying to find information about whether this 250 was a limit set in IIS somewhere or if it was somehow related to the default maximum of 250 threads per CPU in a .Net ThreadPool or just where to find the knob in IIS that I can turn up to eleven! And my conclusion is that it's very difficult to find a definitive resource; so much of the information is for different versions of IIS (5 or 7, usually) and they usually do not make clear which settings are applicable to which versions and which changes I could expect to help me.

So I'm just going to cut to the chase here. For IIS 6 (six!) I needed to do two things.

Firstly, edit the machine.config for applicable ASP.Net version (I'm running against 4.0 and IIS is currently set up to run as 32-bit since to enable 64-bit I believe that all of the applications must be 64-bit and where we're going to host this Web Service I can't be sure that this will be the case):

<system.web>
    <processModel maxWorkerThreads="100" maxIoThreads="100" minWorkerThreads="50"/>

"100" is the maximum value that maxWorkerThreads and maxIoThreads can take. "50" was the value I found in the examples I encountered, presumably this lower bound is set so t hat bursty behaviour is more easily handled as a base number of threads are always spun up (starting up a load of threads is expensive and if a patch of high traffic hits the service then having to do this can result in requests being queued). These were the settings for which there is so much apparently contradictory information out there; some people will tell you the default values are 50 or 12 x the number of CPUs or 5000 depending upon the version of IIS (but infrequently saying which version they were talking about) or the version of ASP.Net (but infrequently saying which version they were talking about!) or.. I don't know.. what day of the week it is!

The second was to increase the number of processes that were used by the Web Service's App Pool. This is done by going into the Performance tab on the App Pool's properties page and increasing the "Maximum number of worker processes" upwards from 1.

There are considerations to bear in mind with increasing the number of worker processes; the first that often jumps out in documentation is issues with Session - any Session data must be maintained in a manner such that multiple processes can access it, since requests from the same Client may not always be handled by the same process, and so an in-process Session would be no good. The Service I was working with had no need for maintaining Session data, so I could happily ignore this entirely.

With these changes, I was able to ramp up the requests until I was max'ing out the CPU in a variety of circumstances; I set up different tests ranging from requests that could be completed very quickly (within, say 20ms) to requests that would artificially delay the response to see how concurrent requests would build up. Without trying too hard I was able to get around 500 requests / second with the quick responses and handle 4000 concurrent requests happily enough with the long-running ones.

This is hardly crazy internet-scale traffic, but it's well in excess of the targets that I was originally trying to see if I could handle. And since the "hardware" is a virtualised box with two cores I'm fairly happy with how it's working out!

Server Settings: Bandwidth considerations

Something we have to consider in exposing this data to external Clients is just how much data we may be passing around. The internal Data Service that is essentially be wrapped is just that; internal. This means that not only will all the data be passed around internal network connections to the requesting Web Server (that will use that data in rendering the site) but also that we can format that data however we like (ie. binary rather than xml) and take all sorts of crazy shortcuts and liberties if we need to as we have to understand the data implicitly to work efficiently with it. For the Web Service, the data's going to be broadcast as xml and we want to make the compromise of exposing a consistent and predictable data model with a simple API. This means that we'll be dealing with much larger quantities of data and we have to pass them out of our private network, out into the real world where we have to pay for transfers.

This really struck home when, during the load tests, I was max'ing out the network utilisation of my connection to the server. My connection to the office network runs at 100Mbps (100 megabits, not megabytes) - other people get 1Gb but I get 100 meg, cos I'm really lucky :) This was in tests where a large response was being returned with a high rate of handled requests. To try to push these tests closer to the limits I had to borrow some other workstations and run requests through wcat (http://www.iis.net/downloads/community/2007/05/wcat-63-(x86)).

So I figured the obvious first step was to look into whether the response were being compressed and, if not, make them.

WCF Compression with IIS 6 and .Net 4.0

I examined a request in Fiddler. No "Accept-Encoding: gzip" to be found. A flash of recollection from the past reminded me of the EnableDecompression property available on the Client proxy generated by Web References. I set it to true before making the request and looked at the request in Fiddler.

Success! The http header for gzip support was passed.

But fail! It didn't make the slightest bit of difference to the response content.

Further googling implied that I was going to be in for some hurt configuring this on IIS 6. The good news is that WCF with .Net 4.0 would support compression of the data natively unlike 3.5 and earlier which required workarounds (I got as far as looking at this Stack Overflow question before being glad I didn't have to worry about it).

This blog post was a lifesaver: IIS Compression in IIS6.0.

To summarise:

  1. In the IIS Admin tool you need to right-click on "Web Sites", click "Service" and click "Compress application files" (we don't care about static content for this particular change, and the temporary disk location is only required for static content - "application files" are always compressed on the fly).
  2. Open the IIS MetaBase file in notepad (C:\WINDOWS\system32\inetsrv\MetaBase.xml, take a backup first - getting this wrong is going to cause problems!) and search for the string "IIsCompressionScheme"; include the "svc" extension into the "HcScriptFileExtensions" section (being careful to maintain the same formatting when inserting the new extension) and then setting "HcDynamicCompressionLevel" to "9" (see that blog post for more information)
  3. Ensure that any software on the server that may try to process the requests is disabled for the "svc" extension (we have something call "IISXpress" on our Web Servers which is supposed to help with compression but which I couldn't get to work!)
  4. Restart IIS

After this, the response were being successfully compressed! As it's xml content it's fairly well compressible and I was getting the data trimmed down to approximately an 8th of the previous size. Another result!

Of course, there's always a compromise. And here it's the additional load on the CPU. For the sort of traffic we were looking to support, it seemed like it would be a good trade off; this CPU load overhead in exchange for the reduction in the size of the transmitted data.

Incidentally, Hanselman has a post here Enabling dynamic compression (gzip, deflate) for WCF Data Feeds, OData and other custom services in IIS7 illustrating just how much easier it is in IIS 7. If you've got a server hosted by that, good times! :)

Checking for gzip support

I'm actually contemplating requiring gzip support for all requests made to the service to ensure we keep public traffic bandwidth in check. Since it's just an http header that specifies that the Client supports it, we can check for it from the Service code:

var acceptsGzip =
    (WebOperationContext.Current.IncomingRequest.Headers["Accept-Encoding"] ?? "")
        .Split(',')
        .Select(v => v.Trim())
        .Contains("gzip", StringComparer.InvariantCultureIgnoreCase);

And if we decide that Client gzip support is required then an error response indicating an invalid request can be returned if this header is not present.

Bonus note on handling concurrent requests

This is probably going to sound really obvious but it caught me out when I did some testing with high numbers of concurrent requests; be careful how you deal with file locking when you have to read anything in!

I have a config file for the Service which has various settings for different API Keys. The data in this file is cached, the cache entry being invalidated when the file changes. So for the vast majority of requests, the cached data is returned. But when it's contents are changed (or the Service is restarted), the file's contents are read fresh from disk and if there are a multiple requests arriving at the same time there may well be simultaneous requests to that file.

I had previously been using the following approach to retrieve the file's content:

using (var stream = File.Open(filename, FileMode.Open, FileAccess.Read))
{
    // Do stuff
}

but the default behaviour is to lock the file while it's being read, so if concurrent read requests are to be supported, the following form should be used:

using (var stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    // Do stuff
}

This way, the file can be opened simultaneously by multiple threads or processes. Hooray! :)

Posted at 22:56

Comments

Dependency Injection with a WCF Service

Following recently discovering how easy it is to implement Dependency Injection into ASP.Net MVC Controllers by rolling my own ControllerFactory I wanted to see if the same sort of thing could be done for some Web Services I'm working on - I've never seen any production code that does it but seemed like the sort of thing that should be possible!

"Discovering" in this case only meant googling for the extension points that I'd heard were built into MVC as standard; hardly the most earth-shattering of realisations! :) In case it saves anyone time, it's as simple as having an implementation of System.Web.Mvc.IControllerFactory and specifying this as the ControllerFactory to be used in the Application_Start method (in Global.asax) -

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.SetControllerFactory(new ControllerFactory());
}

The most basic implementation runs along the lines of:

using System;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.SessionState;

namepsace Demo
{
    public class ControllerFactory : IControllerFactory
    {
        public IController CreateController(
            RequestContext requestContext,
            string controllerName)
        {
            // Return appropriate controller here, using whatever factory class or dependency
            // injection method is appropriate to the solution..
        }

        public SessionStateBehavior GetControllerSessionBehavior(
            RequestContext requestContext,
            string controllerName)
        {
            return SessionStateBehavior.Default;
        }

        public void ReleaseController(IController controller) { }
    }
}

where requestContext exposes an HttpContext property so Cache, Request, Response and Server can be accessed.

All very straight forward (which made me wish I'd gotten around to looking into this long ago!).

WCF Services

I wasn't as confident that it would be as simple to construct an equivalent for a WCF Web Service as I think there was quite a culture shift from the Microsoft that created WCF for .Net 3.5 and that which wrote the MVC framework, that tried to make use of existing standards, that included jQuery with the framework and that released the framework code under an open source license! But it couldn't hurt to try!

In the Service's ServiceHost declaration a Factory may be specified which will be used to instantiate the Service type instead of the default being taken, which is to try to find a public parameter-less constructor and to call that.

<%@ ServiceHost
    Language="C#"
    Debug="true"
    Service="Demo.ExampleWebService"
    Factory="Demo.ExampleWebServiceHostFactory"
    CodeBehind="ExampleWebService.svc.cs"
%>

The factory needs to inherit from the WebServiceHostFactory class. A given factory implementation may handle the instantiation of various Service types but for my purposes, and for the example here, I'm going to work on the basis that there is one factory per service (if different services are to be handled by a single factory then the "serviceType" argument passed around can be consulted to alter the behaviour as required).

The instantiation process follows theses steps:

  1. The CreateServiceHost method on the factory specified in the Service declaration is called (this is a method of WebServiceHostFactory that must be overridden)
  2. This returns a class that inherits from ServiceHost which registers a "Instance Provider" that will be used to instantiate the actual Service type (this Instance Provider must implement IContractBehaviour, so that it may be added the Behaviours collection, and implement IInstanceProvider to mark it as being able to instantiate the target type)
  3. The GetInstance method (which is part of the IInstanceProvider interface) is called; this must return an instance of the Service class to handle the request

Example code may make this clearer!

using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace Demo
{
    public class ExampleWebServiceHostFactory : WebServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(
            Type serviceType,
            Uri[] baseAddresses)
        {
            return new ExampleWebServiceHost(serviceType, baseAddresses);
        }

        private class ExampleWebServiceHost : ServiceHost
        {
            public ExampleWebServiceHost(
                Type serviceType,
                params Uri[] baseAddresses) : base(serviceType, baseAddresses)
            {
                foreach (var cd in this.ImplementedContracts.Values)
                {
                    cd.Behaviors.Add(new ExampleWebServiceProvider());
                }
            }
        }

        private class ExampleWebServiceProvider : IInstanceProvider, IContractBehavior
        {
            public object GetInstance(InstanceContext instanceContext)
            {
                // TODO: Return service instance here
            }

            public object GetInstance(InstanceContext instanceContext, Message message)
            {
                return this.GetInstance(instanceContext);
            }

            public void ReleaseInstance(InstanceContext instanceContext, object instance) { }

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

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

            public void ApplyDispatchBehavior(
                ContractDescription contractDescription,
                ServiceEndpoint endpoint,
                DispatchRuntime dispatchRuntime)
            {
                dispatchRuntime.InstanceProvider = this;
            }

            public void Validate(
                ContractDescription contractDescription,
                ServiceEndpoint endpoint) { }
        }
    }
}

Ta-da! The only thing to fill in is the actual instantiation of the service type; this could be handled with a Ninject or other Dependency Injection Framework call or it could be a good old-fashioned factory class which pulls in all the various dependencies and returns a ready-to-rumble Service instance.

Some workarounds for removing ASP.Net dependencies

When I've used WCF to build Web Services in the past, before using this method of dependency inversion, I've been happy enough using the ASP.Net Cache and Request references but now that I'm working with Dependency-Injected classes I've wanted to extract these dependencies so that the Service class can be tested outside of the ASP.Net environment.

Cache

The first thing to approach was that I wanted to pass a generic cache reference from the factory. The current thinking (for .Net 4.0 onwards at least) seems to favour the use of the MemoryCache found in System.Runtime.Caching (this Code Project article is a reasonable introduction). So wrote an implementation of ICache (which is internal to my project) that makes use of the MemoryCache. Job done!

Client IP Address

Something else I struggled with initially was getting the IP Address of the client making the request (information that I include in the Request logging). This was easy when accessing the ASP.Net Request directly but proved more challenging without it. As usual, some Googling and experimentation yielded an answer. This method may be added to the ExampleWebServiceProvider from the above example:

/// <summary>
/// This may return an IPv4 or IPv6 format address. It will return null if unable to retrieve
/// this information.
/// </summary>
private string TryToGetClientIpAddress()
{
    var currentOperationContext = OperationContext.Current;
    if (currentOperationContext == null)
        return null;
    object nameMessagePropertyRaw;
    if (!currentOperationContext.IncomingMessageProperties.TryGetValue(
        RemoteEndpointMessageProperty.Name,
        out nameMessagePropertyRaw)
    )
        return null;
    var nameMessageProperty = nameMessagePropertyRaw as RemoteEndpointMessageProperty;
    if (nameMessageProperty == null)
        return null;
    var ipAddress = nameMessageProperty.Address;
    if (string.IsNullOrWhiteSpace(ipAddress))
        return null;
    return ipAddress.Trim();
}

These are the two biggest things that struck me when moving over to the new Service instantiation approach, I'm sure there will be similar cases but it's reassuring to know that it seems like in most cases the .net Framework has us covered one way or another, it's just a case of finding a different place to look for the data!

Posted at 16:42

Comments