I had a request to post the information on “the hack” that I presented in my demo this past weekend.

First a little bit about why I needed the hack in the first place. Recall that the goal was to get a single service to support both REST and SOAP. Yes I could have done this another way by simply having two separate services and factoring the common code into a shared assembly, but I liked the idea of them sharing the same base address. I had no problems implementing GET methods with the same interface. The problem arose when I wanted to support upload. In WCF when uploading files of arbitrary length you should probably implement streaming. Streaming requires the body of the message to consist of a single Stream object. Of course I also need to send over the name or title of the picture. In REST the name should be sent via the URL and in SOAP it needs to be a header. As it turns out this means I have to support two separate methods. So I factored a single interface into three: a common, a SOAP, and a REST version.

[ServiceContract]
public interface IPictureServiceCommon
{
  [OperationContract]
  [WebGet(UriTemplate = "titles")]
  string[] GetPictureTitles();</code><code>[OperationContract]
  [WebGet(UriTemplate = "{title}")]
  Stream GetPicture(string title);
}

[ServiceContract]
public interface IPictureServiceRest : IPictureServiceCommon
{
  [OperationContract]
  [WebInvoke(UriTemplate = "{title}", Method = "PUT")]
  void UploadPicture(string title, Stream picture);
}

[ServiceContract]
public interface IPictureServiceSoap : IPictureServiceCommon
{
  [OperationContract]
  void UploadPicture(FileMessage fileMessage);
}

Unfortunately WCF as it stands right now (in .NET 3.5 SP1) only supports turning Metadata on and off at the service level. So when I try to access the Metadata for the REST endpoint it ignores the URI Template aspect and tells me that I have an illegal method.

System.InvalidOperationException: For request in operation UploadPicture to be a stream the operation must have a single parameter whose type is Stream.

So that is where the hack came in as I needed to be able to turn off Metadata for a single endpoint. It turns out it was quite simple (although very hacky).

var host = new ServiceHost(typeof(PictureService));
// This is SUCH a HACK, but it works...
Assembly serviceModel = typeof(ServiceHostBase).Assembly;
Type type = serviceModel.GetType("System.ServiceModel.Description.ServiceMetadataContractBehavior");
var behavior = (IContractBehavior)Activator.CreateInstance(type, new object[] { true });

foreach (ServiceEndpoint ep in   host.Description.Endpoints)
{
  // apply the hack to all REST endpoints
  if (ep.Binding is WebHttpBinding)
    ep.Contract.Behaviors.Add(behavior);
  Console.WriteLine(ep.Address);
}
host.Open();

No comments yet.

No Comments »

You must be logged in to post a comment.