Friday, 14 December 2012

Adding webHttpBinding to WCF service

For some reason, I felt the need to create a JSON web service for my application. All nice and open-format and compatible but I eventually reached the limits of what I needed to do and decided to go WCF so I could produce a SOAP based web service. This is all great but I still need to be able to call one of the methods directly from a GET request in an image tag (to retrieve the image). This all worked fine under asmx/JSON, I simply wrote the image directly to the response. However, I learned that I needed to use a webHttpBinding in order to provide this functionality and had such a ball-ache getting it to work, partly because I am a bit out of touch with WCF services and had never used webHttpBinding.
Anyway, a few scratched heads later (well one head scratched lots) and lots of trial and error and Google searching, I managed to get it to work. Please note that since this is a product of trial-and-error, this is probably not the minimal solution but you should get the idea.
  1. Create a WCF in the usual way to product an SVC, an SVC.cs and a web config.
  2. You need to decorate the services with attributes in the standard way (which should be done by default). This includes OperationContract, ServiceContract and ServiceBehaviour
  3. For some reason which I don't understand, if you have methods that take more than one parameter, they must be passed in the get request rather than the body and if you don't do this, you will get an error when you try and display the service in the web browser (the easiest way to basically check it is correct). You can handle this by adding the WebGet or WebInvoke (nice Restful attributes!) and then specifying BodyStyle=WebMessageBodyStyle.Wrapped in the constructor.
  4. You need to have a WebGet or WebInvoke attribute on any method you wish to call via the webHttpBinding and these need to specify how to interpret the request and map onto the method. For instance, I have something like this: [WebGet(UriTemplate="GetImage?userid={userId}&tag={tag}&height={height}&width={width}")] which as you will notice is basically a URL with a query string. The names in the curly brackets map to the names of the method parameters.
  5. You then need to sort out the config. You can right-click the web config and select Edit WCF Configuration or you can do it manually. I have mixed feelings but the configuration forms do an amount of error checking for you. The config I have that works is shown below:
<system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="standardConfig" maxReceivedMessageSize="65536000">
          <readerQuotas maxDepth="32"
                        maxStringContentLength="65536000"
                        maxArrayLength="65536000"
                        maxBytesPerRead="65536000"
                        maxNameTableCharCount="65536000" />
        </binding>
      </wsHttpBinding>
      <webHttpBinding>
        <binding name="basicConfig" />
      </webHttpBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WebBehaviour">
          <webHttp />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    <services>
      <service name="FullNamespace.ServiceImplementation">
        <endpoint address="web" behaviorConfiguration="WebBehaviour" binding="webHttpBinding" bindingConfiguration="basicConfig" name="web" bindingNamespace="FullNamespaceUri" contract="FullNamespace.ServiceInterface" />
        <endpoint address="standard" binding="wsHttpBinding" bindingConfiguration="standardConfig" name="standard" bindingName="standard" contract="FullNamespace.ServiceInterface"  bindingNamespace="FullNamespaceUri" />
      </service>
      <service name="FullNamespace.OtherImplementation">
        <endpoint address="web" behaviorConfiguration="WebBehaviour" binding="webHttpBinding" bindingConfiguration="basicConfig" name="web" bindingNamespace="FullNamespaceUri" contract="FullNamespace.OtherServiceInterface" />
        <endpoint address="standard" binding="wsHttpBinding" bindingConfiguration="standardConfig" name="standard" bindingName="standard" contract="FullNamespace.OtherServiceInterface"  bindingNamespace="FullNamespaceUri" />
      </service>
    </services>
  </system.serviceModel>
Note that since webHttpBinding does not use SOAP, you will not be able to get an ASP.Net service reference to create a client binding for it but you should be able to access the method directly from a browser.
You might get all kinds of errors if you change what is written here so I suggest trying to start from what I've specified and get that working and then modifying it stage by stage to match what you want to do. You should then be able to work out what is happening.
In the case of the code above, I can access the service by http://localhost/ServiceName.svc/web/GetImage?userid=....
Post a Comment