I’ve spent the last day and a half mucking with WCF. Considering how hard Microsoft is plugging WCF as the future of SOA in .NET, I assumed this would be easy. How wrong I was.
So, I’m trying to use WCF in conjunction with WWF. The idea is that the workflow runtime will be hosted in an out-of-process NT service. But this poses a problem for a client application that has to interact with the runtime in certain circumstances, for example, suppose you define a Workflow activity that uses the HandleExternalEventActivity. How can the external event be triggered from out-of-process?
There are a lot of ways to go about this. The obvious one is to use simple .NET remoting. However, since we’re jumping into .NET 3.5 with both feet by adopting WWF, we might as well at least prototype a WCF-based communications layer between our clients and our worker services, which will host the WWF runtimes.
So, I created a simple service interface that wraps the WWF runtime’s CreateWorkflow method. Cool.
The signature of the method is this:
WorkflowInstance CreateWorkflow(Type workflowType, Dictionary<string, object> arguments);
Pretty simple, right?
Roadblock #1: WorkflowInstance does not implement [Serializable]. This becomes apparent as soon as you attempt to call the service method. You’ll get this exception:
Type ‘System.Workflow.Runtime.WorkflowInstance’ cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.
I’d love to consider marking it with the DataContractAttribute attribute, except it’s Microsoft’s class. Great.
While this is somewhat annoying, it’s not the end of the world. WorkflowInstance is a minimal class. It’s little more than a GUID, and without a reference back to the runtime, the class is useless.
So, I could either return a GUID, or I could create something a little more strongly typed. At the moment a GUID is probably enough, but I’ve worked in development long enough to know that this is likely to change as the project progresses. So I implemented an envelope to return in place of the WorkflowInstance.
You might be thinking to yourself, self, this is bad. What about more complex types? Unfortunately this is the nature of SOA. Think about SOAP Web Services. Those must be returned in XML. In general, this means you are limited to a data structure that can be serialized into strings, or objects that are easily parsed from strings. You can always do fancy things with MIME-encoded serialized objects, but that somewhat defeats the purpose of SOAP. SOAP is supposed to be interoperable. If you are going to be doing strictly .NET to .NET, use remoting.
Maybe I should have followed my own advice, but yet, I push forward with WCF as an alternative to remoting. So I implement the WorkflowInstanceEnvelope class marked as a [DataContract] with a Guid data member marked as a [DataMember]. Roadblock #1 solved.
Roadblock #2: the argument list contains an abstract class as one of its parameters.
The first time I discovered that this causes exceptions to be raised, my mind was totally blown. Are you f’ing kidding me? The exact exception you’ll get is a CommunicationException like this:
There was an error while trying to serialize parameter http://tempuri.org/:workflowType. The InnerException message was ‘Type ‘System.RuntimeType’ with data contract name ‘RuntimeType:http://schemas.datacontract.org/2004/07/System’ is not expected. Add any types not known statically to the list of known types – for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.’. Please see InnerException for more details.
Incredible. Specifically, this exception is referring to the fact that the first parameter, whose type is System.Type, is not a known type. Why not? The service contract clearly defines a method with a parameter of System.Type, and the object I pass into it is a System.Type. What’s the problem?
The real problem is the fact that System.Type never actually gets initialized – it’s abstract. This means that its runtime type is not System.Type, it’s System.RuntimeType. Ironic, isn’t it?
After doing some reading, I came to understand that if you want to do this – specify an abstract class or an interface as a parameter type – you need to create a list of concrete subtypes you intend to use in conjunction with your service and hook this list up to your DataContractSerializer. Woah.
I read a bunch of blogs from a bunch of Microsoft engineers who I’m sure are all quite intelligent and could talk me under the table about typing, serialization, and transport. After reading their explanations about why this is necessary, I understand them for about 5 minutes and then am immediately confused again. My brain simply cannot wrap itself around the fact that .NET 3.5 is taking us a step backward to the days of “instantiation files” from the C++ days, in which it wasn’t enough to create a templatized class or method – you had to spell out every combination of template type you were using before using them. Well, on some compilers, anyway. If I recall Microsoft was always good enough to do that for you automatically.
My understanding, limited as it may be, is that you must specify which types your service is expecting because otherwise the serializer won’t be able to serialize them. (Also, this gives you the opportunity, using some of their arcane serialization APIs, to do fancy things such as make certain fields transient, do type replacements, etc). If you specify an interface in your service contract, how is WCF supposed to serialize that? Interfaces don’t have data members. And a major thing to keep in mind about serialization is that the default WCF serializer is not a standard .NET serializer and it is not an XML serializer – it’s a custom serializer created specifically for WCF for some reasons I won’t get into.
Point of fact, if I wanted to use a System.RuntimeType with this method, I’d have to declare it as a KnownType. There is only one hitch: System.RuntimeType is internal.
This prevents me from referencing it in code, so I can’t use it as a KnownType. I could perhaps programmatically add it to the list of types known by the DataContractSerializer at runtime. But this is not an ideal solution.
It is very likely that we will find the need to create methods in our service interface where the types will not be known at design time, e.g., a method that would take an Activity parameter. Creating a new Workflow map and passing it to our service via this method would subsequently fail unless we provided a means via configuration to define the type on the service before using it. Deployment nightmare.
The solution, it seems, is to replace the DataContractSerializer altogether. It turns out Microsoft has already foreseen this dilemma and has provided a class that serializes its messages the good old fashioned way – .NET style.
The limitation? The resulting messages are now usable only by .NET, which means all service clients now must be .NET. This may be a problem for us in the future, but for now, it gets us around our current problem.
So, how do you hook up the .NET serializer to your WCF service? Good question.
It turns out that Microsoft went ahead and implemented a NetDataContractSerializer class to solve this problem, but didn’t bother giving you an attribute you could use to specificy that specific method calls must be serialized using the NetDataContractSerializer.
You have to write your own attribute. Fortunately a few bloggers have already done it and the code is freely copyable here.
Unbelievable that Microsoft doesn’t include this class in their libraries. It’s not an oversight, in case you were wondering. It’s intentional. Microsoft doesn’t want you to use this mechanism because they claim it violates best practices. And in a sense they are right – you lose interoperability. However, in pure .NET environments, which you’d think Microsoft would be plugging (cautiously, perhaps), you lose nothing and gain the ability to use well-designed OO frameworks across WCF.
Once you create a class file with the NetDataContractAttribute class implemented, in order to use it, you need to tack on a [NetDataContract] attribute on any service method that has interface or abstract arguments. Do this on the service interface, not the service itself.
According to the blogs, this is all you need to do. However, I continued to see the same exceptions after recompiling and regenerating my client with svcutil, and that brings us to …
Roadblock #3: svcutil.exe sucks.
If you use the handy [NetDataContract] attribute, you have to mark it as such on both the service interface and the client interface, which means you need to hand-modify the generated client code. It makes sense, but it should also raise a giant red flag for anyone who works on a team. Making a series of hand-changes to the code generated by a tool and expecting someone else not to mess this up the next time they change the service interface and regenerate the client interface using svcutil.exe is asking for a fire.
The very fact that you need to create a client proxy (aka stub) is not impressive. .NET remoting is easier, and as we’ve already seen, we’ve had to sacrifice interoperability to get this far. I am failing to see why WCF is so amazing.
This speaks nothing of the fact that if you were actually trying to offer a service, even if your clients understood they were required to use .NET because you use abstract .NET types in your interface, after they query your WSDL to generate their client code they now need to edit it. They need to know which methods of yours need this attribute (which, by the way, they have to code themselves unless you give them a DLL because it is not part of the standard Microsoft API). Since they can’t know this, you’ll have to give them an assembly with a client stub generated by you, which defeats the entire purpose of exposing the WSDL such that anyone with svcutil.exe would be able to consume the service.
Okay, so after you tack on [NetDataContract] to your client code, you will probably run into the last roadblock which is related to this one:
Roadblock #4: your client stub will reproduce your custom types.
In our case, if we look at our client proxy (which for reasons outside of common sense will be generated with the same filename as our service’s code, usually), our WorkflowInstanceEnvelope is defined a second time. This is not a bad thing, except that it has exactly the same name and it will confuse the hell out of the runtime. We’ll get strange exceptions with a message like:
“Return argument has an invalid type.”
Oh, really? That makes a lot of sense. We now have two classes with the same name, as part of the same namespace. How does this not generate a loading exception? How are they differentiated internally? I have no clue. Nor do I care. I commented out the client class and everything worked fine.
In conclusion: so far, I am not particularly impressed with what I’ve seen of WCF. Doing something as simple as I’m trying to do should a) be natural and supported without jumping through at least 4 hoops, and b) not incur penalties that defeat the purpose of using WCF in the first place since the resulting functionality set has already been implemented by .NET remoting.