• Feeds

    Subscribe in a reader

  • Ads

A brief tour of service activation

I wrote this up in response to a question on an internal list about what the service activation path looks like in hosted environments such as IIS or WAS. Thought it might be useful to a wider audience. The story starts just after the request has been dispatched to the worker process, but before pretty much anything else has run. We now join the following thought process already in progress:

 

...We have a piece of infrastructure (an HttpModule for the HTTP case, a new thing called a ProtocolHandler for the non-HTTP case in WAS) that receives Indigo requests and stuffs them into a holding area while the transport stack comes up. The magic API is ServiceHostingEnvironment.EnsureServiceAvailable( string virtualPath ), which is what eventually calls ServiceHost.Open(). The logic for ESA does the following:

 

1) First, look up the virtual path in a cache to see if we already have an active ServiceHost for it. If so, great -- the service is active, so we're done.

 

2) If we don't find an entry in the cache, it means the service hasn't been activated yet. So the first thing we do is call ASP.NET's static BuildProvider.GetCompiledCustomString() method.

 

3) The ASP.NET compilation system ends up calling our build provider, because we mapped the *.svc extension to our build provider in the root web.config. Our build provider parses contents of the .svc file (including the @ServiceHost directive), dynamically compiles any inline code present therein, and then returns several bits of interesting data back to the caller. This interesting data contains things like the value of the Service/Factory attributes from the @ServiceHost directive and a list of referenced assemblies we used during compilation. Our build provider returns more than just a type, which is why we call BuildProvider.GetCompiledCustomString() instead of the more conventional BuildProvider.GetCompiledType().

 

4) Popping a few stack frames back to ESA(), the next thing we do is cons up an instance of IServiceHostFactory (ServiceHostFactoryBase in recent bits). If the user specified a specific factory by providing a CLR type name in the .svc file's Factory attribute, we'll Activator.CreateInstance() on that type. Otherwise, we new up the default factory (System.ServiceModel.Activation.ServiceHostFactory).

 

5) Now that we have host factory, we take the string contents of the Service attribute and pass it to ServiceHostFactoryBase.CreateServiceHost( string constructorString, Uri[] baseAddresses). The base addresses came from the IIS/WAS configuration store, and the constructor string just came from the Service="blah" attribute in the .svc. ESA() makes no assumptions about the format or contents of the string; how that string gets interpreted is entirely up to the factory/host.

 

6) We have a default implementation of ServiceHostFactoryBase that returns instances of ServiceHost. In this world, the contents of the constructor string are interpreted as the CLR type name of your service implementation type. So ServiceHostFactory does the moral equivalent of Type.GetType and passes that down to its own virtual method CreateServiceHost( Type t, Uri[] baseAddresses ). By default this implementation just does "return new ServiceHost(t, baseAddresses)". If you have your own derivative of ServiceHost, you can derive from ServiceHostFactory and override this method to return a new instance of your custom host and we'll do the work of turning that constructor string into a System.Type for you. If you're deriving directly from ServiceHostBase instead, you can derive from ServiceHostFactoryBase and get access to the unadulterated, unparsed value the user put in Service="blah" back in the .svc file. What you do with it then is entirely up to you.

 

7) Now that we have the ServiceHost back from the factory, we can stick it in the cache so we skip all this next time around. Then we call ServiceHost.Open(), which kicks off the normal process of building a description, creating a channel stack, and all the other stuff we normally do at Open() time.

 

8) Once that channel stack is fully up, it starts an accept loop. This percolates all the way down to the transport...which, for the hosted case, causes the thread to block until a message arrives in very same holding area that I mentioned at the beginning of this mail. Since the transport put one in there prior to calling ESA(), this call returns immediately and the message then flows up the channel stack where it gets processed in exactly the same way it would have in self-host.