Friday, September 28, 2012

WCF handles streamed content type badly

While trying to help a colleague with a perplexing deserialization problem with derived types, which the KnownTypes attribute wouldn't fix.

Take One: we tried to pass JSON data into our REST service through a Stream parameter. We were thinking that we could simply read the raw object data and perform our own deserialization. But, no, we began to get a "500 Internal Server Error" with the exception:

Incoming message for operation 'test' (contract 'RuleService' with namespace 'http://tempuri.org/') contains an unrecognized http body format value 'Json'. The expected body format value is 'Raw'. This can be because a WebContentTypeMapper has not been configured on the binding. See the documentation of WebContentTypeMapper for more details.

With .NET, it seems nothing is ever as easy as advertised.

The data was getting to our service, but it was throwing an exception before dispatching the request to our service method, so the Visual Studio debugger was useless to run down the problem.

Take Two: We created a test method to accept a Stream and return it as a string. This failed, too. WCF, WTF!

Turns out WCF is really that stupid. A Stream is supposed to accept any data regardless of format. It doesn't. WCF still tries to interpret the content according to the Content-Type header, instead of ignoring it as it should. The client must tell the service the data is unformatted before it will work without throwing an exception. So a client that isn't completely well-behaved and strictly compliant with the web service interface will blow up your program. I guess it's a security feature.

Here's the raw deal on raw data: if your method accepts a Stream, the client must set the request's Content-Type header to application/octet-stream or text/plain or some other type that indicates unformatted content. If you only switch your input parameter from a class to a Stream, a message that could have worked before will immediately fail. Using WebContentTypeMapper requires substituting your own mapper for the entire service, not just for a single operation. Apparently there is no simple workaround.

Take Three: luckily for us, the client is our own program. We can make it compliant. If it were a third-party application, we would be SOL.

The End: After we changed the client to set the message header to Content-Type: application/octet-stream, the service starting receiving data again and our program went on happily deserializing the object that WCF's stock deserializer wouldn't.

Tuesday, February 21, 2012

Random notes about getting IBM WebSphere MQ 7.1 installed and working

I had an enormous amount of trouble while starting to develop an application that had to interface to another application using IBM WebShere MQ 7.1 message queues. Installation is not simply using the setup program's defaults. These notes are vague because this procedure was painfully time-consuming and I didn't have time to hunt down the facts about what actually got it working.

1. Installation Part I. This gets the files onto the workstation. This took me a couple days because IBM's documentation is pretty obscure about what installation set is needed, server or client, or where it puts files. A lot changed between versions 7.0 and 7.1 that was undocumented. In addition to being vague, the documentation is also inaccurate, as the documented defaults are not the real defaults. In short, to develop and test on a single machine, the Server set (which includes the Client files) was required, not just the Client set. And you had to manually select the Client files from a custom installation because - contrary to the instructions - the Client files were not selected by default.

2. Installation Part II. Once the files are physically installed, a second part of the installation runs to "prepare" the message queues. Under Windows 7 at least, this portion terminates with a "there's something wrong" error. Real helpful. Fixing this had to deal with security. In addition to the domain user required to execute services, another two separate domain users had to be created. One user is apparently to create queue managers and another to manage queues. They are identical accounts except they have different names; complete waste of resources. And you may have to start the installed services manually. As an Administrator. A Domain Administrator. WTF!

3. More security problems. Attempting to create a remote queue runs afoul of more security issues. A minimum of four layers of Authorizations have to be set up (by a local Administrator this time?) before a queue can be created. Some permissions are for creating the queue manager and some are for creating the queue.

4. Sample programs. The sample programs provided as part of the installation cannot be built using the supplied bldcssamp2.bat batch file. Firstly, because the batch file must be run from the command line, the PATH environment variable must be updated to include the compiler directory; in my case, for compiling against the .NET Framework 4.0, it was setting:
PATH=%PATH%;C:\Windows\Microsoft.NET\Framework\v4.0.30319.

Secondly, the path to the libraries must be edited. One every compile statement. Original:
/lib:..\..\..\..\Bin
Correct:
/lib:..\..\..\..\Lib.

On Windows 7 (and Windows Vista, from reading other comments) the compiled executables won't run. When you launch an executable, the program crashes and the following error shows up in the Windows Application Event Log:


Activation context generation failed for "C:\Program Files (x86)\IBM\WebSphere MQ\tools\dotnet\samples\cs\base\nmqsput.exe".Error in manifest or policy file "C:\Program Files (x86)\IBM\WebSphere MQ\tools\dotnet\samples\cs\base\nmqsput.exe.Config" on line 8. The element assemblyBinding appears as a child of element configuration which is not supported by this version of Windows.


This has something to do with Windows 7 not liking the "side-by-side" Win32 manifest files that the compiler generates by default. To get around this error, the batch file had to be edited yet again. Add to every compile statement:

/nowin32manifest

So, parameterizing it a bit, in the interest of helping other IBM customers, here's the complete sample build script:


PATH=%PATH%;C:\Windows\Microsoft.NET\Framework\v4.0.30319
SET LIBPATH=..\..\..\..\Lib
SET MFTOPT=/nowin32manifest
echo building nmqsget.exe
echo.
csc /t:exe /r:System.dll /r:amqmdnet.dll /lib:%LIBPATH% /out:nmqsget.exe %MFTOPT% nmqsget.cs
echo.

echo building nmqsput.exe
echo.
csc /t:exe /r:System.dll /r:amqmdnet.dll /lib:%LIBPATH% /out:nmqsput.exe %MFTOPT% nmqsput.cs
echo.

echo building nmqswrld.exe
echo.
csc /t:exe /r:System.dll /r:amqmdnet.dll /lib:%LIBPATH% /out:nmqswrld.exe %MFTOPT% nmqswrld.cs
echo.

echo building MQPubSubSample.exe
echo.
csc /t:exe /r:System.dll /r:amqmdnet.dll /lib:%LIBPATH% /out:MQPubSubSample.exe %MFTOPT% MQPubSubSample.cs
echo.

echo building MQMessagePropertiesSample.exe
echo.
csc /t:exe /r:System.dll /r:amqmdnet.dll /lib:%LIBPATH% /out:MQMessagePropertiesSample.exe %MFTOPT% MQMessagePropertiesSample.cs
echo.


And another thing... The documentation makes no mention about how to compile against the .NET libraries. Oh, there's a sample solution for Visual Studio, but it makes web service calls instead of using the MQ library. Wonderful, another irrelevant example. Without scanning my hard drive and digging through the command-line samples, it would have been nearly impossible to find where they buried the correct DLL that had to be referenced: C:\Program Files (x86)\IBM\WebSphere MQ\tools\Lib\amqmdnet.dll.

Fortunately, there is this outdated but still useful RedBook reference document: WebSphere MQ Solutions in a Microsoft .NET Environment. It has some helpful information about developing MQ applications in .NET and Visual Studio .NET. The conceptual information in it is still relevant for current development.