Wednesday, April 11, 2012

WCF "just works", even when you may not want it to.

So my current project requires a notion of file upload/download between the client and a WCF service.

As such, I was required to implement MTOM/Streaming capabilities to accommodate large files.
The usage of this is documented fairly well on a very generic level at MSDN.

To make a long story short, there is some semi-undocumented gotchas that can cause you to search and do trial and error until you find the problem to the solution to be a small mis-configuration that's corrected with a mere couple of keystrokes.  How annoying!

So here we go...

WCF in .Net has a default service/binding context that allows your service to "just work" for typical situations.
Unfortunately, MTOM/Streaming implementation does not qualify as a typical situation.  And equally as unfortunate, you won't find out things are not going quite right until run-time when you begin to exercise the limits of the typical situation.

If you're mis-configured and step outside of the norm, you will see anywhere from helpful to vague exceptions and dialogs such as these:

The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element

or something along the lines of this:

The message cannot be processed at the receiver, due to a mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver.  Check that sender and receiver have the same contract and the same binding

or maybe this:

Content Type text/xml; charset=utf-8 was sent to a service expecting application/soap+xml; charset=utf-8.  The client and service bindings may be mismatched.

or the ever so vague:

HTTP 404: Bad Request 

Yet you've changed things appropriately on client and server in your config files and things still don't work.  Suddenly the "just works" concept has created a bunch of false impressions of what is actually happening.

If you're suddenly finding that WCF is ignoring all of your maxReceivedMessageSize or messageEncoding and other customizations in your binding settings, odds are the service is ignoring your custom binding and using the built-in "typical" one.  There are a couple of places you need to check to ensure you're telling WCF to look at the right binding.  One is obvious (and widely documented). The other is not.

There are a couple of things that can cause WCF to ignore your custom binding:
First, the obvious...

In your Web/App.config in the WCF service, you must specify a name for your custom binding (I've put it in green here for emphasis):

        <binding name="FileStreamBinding" maxReceivedMessageSize="2147483647" messageEncoding="Mtom" transferMode="Buffered">

Now, make sure your bindingConfiguration in your endpoint matches the name of the custom binding you want the service to obey:

    <service name="SomeNamespace.SomeOtherNameSpace.SomeCoolService">
      <endpoint address="basic" binding="basicHttpBinding" bindingConfiguration="FileStreamBinding"
        name="WebClientHttp" contract="SomeService.ICoolServiceContract" />
      <endpoint address="mex" binding="mexHttpBinding" name="MetadataServices"
        contract="IMetadataExchange" isSystemEndpoint="false" />

This is the most documented thing to check.  So my inclusion of it here is to keep all the trouble shooting all in one place rather than repeating the efforts of others.

Still doesn't work?

Now the not so obvious... Look at the .svc file in your WCF host.  You know, the file that you usually right click and "View in Browser" to see the instructions for consuming the service.  In our case, we'll say it's called "SomeCoolService.svc"

Typically, this file has one line in it that looks something like this:

<%@ ServiceHost Language="C#" Debug="true" Service="SomeNamespace.SomeOtherNameSpace.SomeServce"  %>

The service described here needs to correlate to the service you've defined in your config:

    <service name="SomeNamespace.SomeOtherNameSpace.SomeCoolService">
      <endpoint address="basic" binding="basicHttpBinding" bindingConfiguration="FileStreamBinding"
        name="WebClientHttp" contract="SomeService.ICoolServiceContract" />
      <endpoint address="mex" binding="mexHttpBinding" name="MetadataServices"
        contract="IMetadataExchange" isSystemEndpoint="false" /> 

See the error?  The final namespace element mentioned in the config's service name is "SomeCoolService". In the actual .svc file, the final namespace element is "SomeService"

Since .svc file defines the running service, this is the service name that the application is aware of at run time.  When the initialization processes interrogate the config file for custom service bindings, it can't find any services defined as "SomeService".  So, in an effort to make the WCF "just work", the default typical bindings are used instead.

The solution here is clear.  Make the service names match in the .svc file and the .config file:

    <service name="SomeNamespace.SomeOtherNameSpace.SomeService">
      <endpoint address="basic" binding="basicHttpBinding" bindingConfiguration="FileStreamBinding"
        name="WebClientHttp" contract="SomeService.ICoolServiceContract" />
      <endpoint address="mex" binding="mexHttpBinding" name="MetadataServices"
        contract="IMetadataExchange" isSystemEndpoint="false" /> 

(I'd probably rename the .svc file as well to keep things consistent) :)

In conclusion, I can see where the inclusion of this "default" binding is useful.  Perhaps even essential to keep the learning curve low in order to get developers with lesser knowledge and/or patience to accept and use it over the traditional web service models.

But as illustrated above, sometimes this "just works" behavior create a situation where a harsher, all-out failure would have maybe saved time hunting down the bug.

No comments:

Post a Comment