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();
RSS feed for comments on this post.
TrackBack URI
You must be logged in to post a comment.