Sometimes programming in .NET just drives me up a wall. Not that I would prefer to go back to the Win32 API and writing my own message loops, but when a framework is as all-encompassing and opaque as .NET is, it’s doubly important to make the internals work right and sensibly, and doubly likely that somewhere they will do neither. Case in point: I probably spent four hours today trying to inject some custom behavior into a WCF channel only to find out that the problem was the lack of a space in front of a comma. Or, more accurately, four spaces in front of four commas.
My goal was to use message inspectors to sniff out an authentication cookie from an incoming WCF service reply, and inject it into subsequent requests, as part of a piggyback authentication scheme with an ASP.NET website. I won’t say much about message inspectors. They let you get your hands on various parts of an incoming or outgoing message before it is handed to the client or proxy, however the case may be. They work as advertised and there are a lot of good posts on how to use them. Microsoft has one too… it’s just not good. If you want to read specifically about managing cookies using inspectors this is the post that got me started.
If you read any of those posts you’ll notice that to implement a message inspector requires also creating a custom behavior that implements IEndpointBehavior. It’s a very simple class that just creates an instance of your inspector class on demand. If you’re willing to deal with creating the behavior for every service and every channel manually you can stop there, since there are imperative ways to do this. What I wanted was to be able to specify the behavior declaratively, in the .config file where the endpoints are defined, so that it is added transparently to all channels when they are used. In order to do that you need one more class: a custom extension of BehaviorExtensionElement that gets registered in the system.serviceModel/extensions/behaviorExtensions configuration scope. This class creates the behavior, the behavior creates the inspector, and so on and so forth.
And that’s where the fun began. The syntax for declaring the extension element and behavior looks like this:
<system.serviceModel> <extensions> <behaviorExtensions> <add name="extensionName" type="extensionType" /> </behaviorExtensions> </extensions> <behaviors> <endpointBehaviors> <behavior name="behaviorName"> <extensionName /> </behavior> </endpointBehaviors> </behaviors> </system.serviceModel>
Looking at the behaviorExtensions/add element, the name attribute value is arbitrary, and is used later to declare the behavior itself. The type attribute value must be the fully qualified typename of the class that implements the behavior extension. When I first wrote the code I assumed that “fully qualified” meant: include the namespace. My declaration looked something like this:
<behaviorExtensions> <add name="myExtension" type="MyNamespace.MyExtension" /> </behaviorExtensions>
When I got everything written and fired it up I got a configuration error from the parser, indicating that it could not find the named assembly. Hmm… /facepalm. I had implemented the extension class in a separate assembly, so of course it couldn’t find it. I tried adding the assembly name:
<behaviorExtensions> <add name="myExtension" type="MyNamespace.MyExtension, MyAssemblyName" /> </behaviorExtensions>
That didn’t work either. A little more poking around on the web and I discovered that the parser absolutely required the complete strong name of the assembly. So I added the missing fields, and my declaration looked like this:
<behaviorExtensions> <add name="myExtension" type="MyNamespace.MyExtension, MyAssemblyName, version=220.127.116.11, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions>
Still no go, but now the error changed. Instead of complaining that the assembly could not be found the parser now squeaked about the manifest data not matching the reference. Aha! Progress! I googled a bit more and discovered another post describing problems with breaking the string and having spaces after the commas. So I removed the spaces. I won’t bore you with another listing. Just look at the one above and imagine taking out the spaces after the commas in the type attribute string. It still did not farking work.
Damn, /facepalm, the sequel. My assembly is signed. Just because Microsoft’s example used null for the PublicKeyToken attribute doesn’t mean I can. I used sn.exe to generate a public key token and replaced the null with a string of letters and digits. Again, no listing, just use your imagination. It still didn’t work. But the error changed again! Instead of complaining about the mismatched manifest data the error now simply stated that the element couldn’t be loaded. I changed the key back to null – mismatched manifest data. Put back the token – can’t load element. What the hell? It was pretty obvious that I was getting past the assembly part… but what now?
Google provided no additional help, except for one or two posts that mentioned the unusual pickiness of the config parser with respect to this element. And it was while ruminating over one of these that I remembered taking those damn spaces out earlier in the process. I put them back in.
And it worked. Just why the presence of spaces in a comma-separated list of substrings should be important to the config parser I don’t know. I don’t even care. All I know is that it worked with spaces, and it didn’t work without them. Microsoft must have its reasons because the issue has been brought up a number of times and they have refused to acknowlege it as a bug. To me it looks like a bug, or at least a massively anal design decision, but what do I know? All I can do is kick the damn thing until it works.