Considering my history using ASP
and COM – you can imagine I’ve always been a fan of interface-based design. Of
course, I’m not a religious zealot of any sort, so I’ve always been comfortable with
the idea of abstract base classes as another perfectly valid way to create pluggable,
extensible systems. This
post is about one such pluggable system which utilizes abstract base classes – and
how that kind of design can just be stupid (not based on the concept of abstract base
classes, but just based on dumb design of such a system).
Harry has
been bugging me to post about creating classes that derive from ServiceHostBase, which
I have done quite a few times in the past year or so. The
next technical post I make after this one will be about the ins and outs of creating
a class that derives from ServiceHostBase (specifically to host workflows as WCF Endpoints),
but I’ve had this post on my chest for a while and I just had to let it out (although
as I am writing the post I am finding that it is a pretty decent introduction to ServiceHostBase
– interspersed with slight vitriol).
One of the things I strive for
whenever I design an abstract base class is to make sure that it is as simple as possible
to derive from and implement that base class. One
of the great features in Visual Studio (VS) is that via reflection VS can determine
what methods *must* be implemented for a particular base class when you build a derived
class.
Let’s take a simple example. Imagine
a base class named “Base” which is marked as abstract and which has one method, named
“VirtualMethod”. (I know this is pretty simplistic, but I feel it may be the only
way to get the WCF team at MS to understand the issue).
public abstract class Base
{
public abstract string VirtualMethod(string param);
}
Now
imagine you want to derive from that base class and create a class named “Derived”
which implements all the abstract methods of this base class. Here
is an example implementation:
public class Derived : Base
{
public override string VirtualMethod(string param)
{
Console.WriteLine(“This
should work: {0}”,param);
return “Implemented
VirtualMethod”;
}
}
A
cool feature related to abstract base classes in Visual Studio 2005 is that once you
type “public class Derived : Base”, there is smart tag hovering over the Base keyword. If
you select that smart tag with the mouse (or use the Alt-Shift-F10 keyboard shortcut
as I like to do), you get a context menu which states that it will “Implement abstract
class ’Base’” for you.
>
I
personally find the Alt-Shift-F10 shortcut to be super useful (it can also be used
to implement an interface). I also think
from a *moral* point-of-view that, when I derive from an abstract base class and use
Alt-Shift-F10 (or just manually implement *all* the abstract methods), my derived
class should *work* in whatever context the base class is supposed to work (which
is really the point of abstract base classes right?). The
whole point of marking methods “virtual” is to force the derived class implementor
to provide an override of those methods. Whatever
code uses the base class methods should work with a complete, correct implementation
of the base class (of course the C# or VB.NET compiler won’t let you complie an incomplete
implementation, but it certainly will let you compile an incorrect one).
So
what would you think if you downloaded
some library from some company which was based on some OO language which supported
abstract base classes, and you did the above steps (implementing all the abstract
methods), and then when you plugged your class into the framework for that class,
your class didn’t work? What would you
think if it didn’t work – *and* the exception
you got was a blatant lie about why your nicely implemented dervied class didn’t work?
In
fact, WCF provides such a framework (and has such an exception – you’ll just have
to keep reading for that) for creating derived classes that can plug into its framework. ChannelListeners
are the channel manager used to provide service Endpoints – that is Endpoints which
listen for incoming messages in WCF. The way you load up ChannelListeners in WCF,
in the general case, is to use a class
named ServiceHost. ServiceHost is a useful
class that enables you to associate one or more endpoints with a .NET class which
implements one or more contracts, and thus hosts one or more contracts on one or more
endpoints. ServiceHost is a really useful
class in WCF (fairly essential actually).
In
the ASP.NET/WPAS hosting scenarios, you don’t actually get to create your own ServiceHost
type – the ServiceHost is created implicitly based on your .svc file (with help from
your web.config file for the binding and potential behaviors). To create the ServiceHost,
the WCF HttpHandler actually uses a class called ServiceHostFactory, which is a fairly
typical generic Factory pattern implementation (handing out ServiceHost objects to
requestors). This features is a really
cool extensibility point of WCF, one that can be used for things ranging from controlling
Behaviors on your ServiceHost (i.e. not relying on configuration for features like
ServiceMetadataBehavior or ServiceDebugBehavior) to replacing the ServiceHost/.NET
Type pairing paradigm with some other paradigm. Steve
Maine has some good info on ServiceHost/ServiceHostFactory here.
So
more than a year ago I ventured to write my first custom ServiceHost/ServiceHostFactory
pairing. Here’s my story of that adventure. Just
to make it a little more interesting, and to see how the plumbing is put together
in WCF, I chose to derive from ServiceHostBase
and ServiceHostFactoryBase (see Steve’s article above about the choices between deriving
from ServiceHost or ServiceHostBase). So
I fired up Visual Studio and created classes that derived from ServiceHostFactoryBase
and ServiceHostBase. Using my Alt-Shift-F10 trick – this is what I end up with (after
removing the throw clauses that Alt-Shift-F10 place inside of each overriding method):
public class MyServiceHostFactory : ServiceHostFactoryBase
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[]
baseAddresses)
{
}
}
public class MyServiceHost : ServiceHostBase
{
protected override ServiceDescription CreateDescription(out IDictionary<string,ContractDescription>
implementedContracts)
{
}
}
Of
course at this point the code won’t compile, since I haven’t actually provided implementations. The
implemenation of CreateServiceHost is pretty easy – all I need to do there is create
an instance of ServiceHostBase. In the
typical case, your ServiceHostBase implementation will have a constructor that takes
both of the arguments passed to CreateServiceHost. The
OOB implementation (ServiceHost) uses the first argument to figure out what .NET Type
should be used to implement the service Endpoints around, and uses the second argument
as list of base Uris that relative Uri’s can hang off of the base addresses (assuming
the Endpoint’s transport channel matches the scheme of the base Uri). So
that’s pretty easy. I’ll store those
two parameters in instance fields on the MyServiceHost Type, as we’ll need them later.
The
interesting part here is implementing ServiceHostBase::CreateDescription. CreateDescription
is really the key to making your ServiceHostBase implementation work within the WCF
infrastructure. From the look of the
return value (ServiceDescription) you might get the mistaken idea that the ServiceDescription
is only used by WCF to publish metadata (assuming metadata publishing is enabled on
your Service). This is really pretty
far from the truth. The instance of ServiceDecription
returned from your ServiceHost (as well as the out parameter – the generic dictionary
of strings and ContractDescriptions) is used for more than just metadata publishing. It
is used by the WCF dispatching layer to route messages received via the Channel layer
down to the Service layer (the piece we are trying to implement). Creating
a valid ServiceDescription and returning the list of implemented contracts is essential
for making your ServiceHostBase-derived class work.
So
let’s assume I create a valid return value and out parameter for CreateDescription
(the next post will get into the necessary detail on how exactly to accomplish that). Once
I do so, notice that there are no more virtual method to implement from ServiceHostBase.
Next,
I need to get my custom ServiceHostBase derived class loaded by WCF. In
the typical WCF hosting scenario in IIS there is a .svc file. The
.svc links to the “Service Type” – the .NET Type that WCF will use as the basis for
the endpoints exposed via the Uri ending in .svc. Here
is a typical example:
<%
@ServiceHost Language=C# Debug=”true” Service=”MyService” CodeBehind=”~/App_Code/Service.cs”
%>
In
the IIS hosting case, the value of the Service attribute from the ServiceHost directive
is passes as the parameter to the ServiceHostFactory:CreateServiceHost and to the
typical contstructor of ServiceHost.
To
load your own custom ServiceHostFactory all you need to do is add a Factory attribute
to the ServiceHost directive. Like this:
<%
@ServiceHost Service=”MyService” Factory=”MyServiceHostFactory” %>
When
I do all this and browse to the URL for the Service, I get this lovely exception in
the browser:
For search engine purposes – the exception/lie is “InitializeRuntime requires that
the Description property be intialized” (this part isn’t a lie) – “Either provider
a valid ServiceDescription in the CreateDescription method or override the InitializeRuntime
method to provide an alternate implementation”.
So
I begin to scratch my head (this is me retelling the story when I first wrote this
code oh-so-long-ago). I
generally always assume that when something like this happens, *I’ve* done something
wrong. So I assume my instance of ServiceDescription
must be invalid, since I *definitely* implemented the abstract methods from ServiceHostBase.
Here
is where things get ugly. I spent an afternoon looking through Reflector to
figure out where I had gone wrong. I
even went to the trouble to delegate to an instance of ServiceHost to get what I knew
to be a valid ServiceDescription. Once
I had done that and I was still getting the same exception, I began to think it wasn’t
*my* implementation of CreateDescription, but something else. This
of course (about 4 hours later) led me down another Reflector expedition into what
ServiceHost was doing that I wasn’t.
Finally
after another hour or so – I stumbled upon it. In
the *constructor* of ServiceHost (not ServiceHostBase mind you – but WCF’s OOB implementation
of ServiceHostBase) there is a call to InitializeDescription. InitializeDescription
calls another method, which in turn sets the ServiceHostBase.description field (which
is *private* BTW so I couldn’t set the value myself) to the return
value of the call to CreateDescription.
So
the *only* way to make a ServiceHostBase derived class work inside of this infrastructure
is to call a “magic” method inside of your ServiceHostBase constructor. Failure
to call IntializeDescription will lead to complete failure when implementing a derived
class of ServiceHostBase.
So
now that I have all my pent-up frustration out – what are the key points here?
1) This
is a stupid design – if I implement all the abstract methods of an abstract base class
my class should work. Period. Unless
there is some super compelling reason – this should also be the case with interfaces
(see ISerializable for what I would consider to be a valid exception to this rule
where you must implement a special constructor for deserialization purposes).
2) WCF
has no such special reason. In fact,
if you look at the implemenation of InitalizeRuntime, all it does is check to see
if the value of the Description property is null or not (the Description property
is a public read-only property on top of the ServiceHostBase.description field). If
it is null, the stupid exception I showed above is displayed. Are
you telling me ServiceHostBase:InitializeRuntime couldn’t reasonably add one line
of code to call CreateDescription after discovering that the property is null. And
*then* throw this exception after that call returns null? Or
the Description property itself could be responsible for calling InitializeDescription
if the field was null (this was Harry’s suggestion).
So
basically this means the exception I got is a blatant lie. I
did provide a perfectly valid ServiceDescription from my CreateDescription method. It’s
just that ServiceHostBase never calls CreateDescription. Instead
– in my derived class – in my constructor – I have to call another method (InitializeDescription)
which then in turn will cause the description field to be properly filled in with
the return value of my totally correct/valid ServiceDescription (which I have to rely
on since the description filed on ServiceHostBase is private). Which,
mind you, I had created 6 hours before
I discovered the magic InitializeDescription method.
Please
folks (at MS and elsewhere) don’t design abstract base classes that rely on magic
methods to work correctly. It makes me
testy. It’s dumb. What
else can be said about it? How about – IT’S DUMB (ahh – I feel better
already).
Alt-Shift-F10
plus actually implementing all the methods correctly should be enough (barring some
real circumstance which might make making a magic method necessary – and if that’s
the case – document the hell out of it).
Check out my BizTalk
R2 Training.