Hosting plug-in WCF Services

March 12, 2008

7 comments
Technorati Tags: ,,,,

Today, I had to show how to host many services in one place. Since I did not find many example on the Web, I decided to do share it with you (with the permission of my client…)

Visual Studio lets you easily create a WCF Library, VS 2008 also offers a default host (which after, you think it is a cool feature, you understand that it is annoying and you delete the project type GUID from the proj file)

When VS 2008 WCF Library application startup template creates the default service1 service, it also creates a default app.config file with all the needed configuration to host the service. When you crate one server (EXE or NT service) that should host many different services (for example a WCF plug-in) you start copying and merging all those config files to one. Manu has wrote a post about splitting the configuration file by sections. I’d like to show you how you can take those app.config file from all the services and without merging them to one, you can still host the services they configure in one place. Another side benefit is that by hosting each of the service assembly in a private isolated app domain you can also close and unload a specific service host.

The attached code is for example purposes; there is no one line of validation & error handling, but it can be a good starting point for more robust solution.

The concept:

In the attached VS 2008 solution, there are three services in two assemblies with two app.config. There is a post build action that copies the assembly file and the app.config to one directory (services). This is not a must, but it is an easy solution for discovering service libraries.

To let WCF configuration mechanism to have many app.config file, we create and load each app.config configure services in new WCF Host in a new AppDomain. To do that we need an object that will do the AppDomain creation, the services discovery and the WCF host creation. This is the task of the IsolatedServiceHost class that resides in its own assembly. We load this assembly in two places, in the main AppDomain and in a new WCF Host AppDomain. IsolatedServiceHost has a static function HostServices() that take a path to a WCF Service library. The app.config file must be in the same directory with the name of the XXX.dll.config

In the example all the assembly files and the config file are in the same directory.

The HostServices() function, creates the AppDomain with a new configuration file, by doing it the WCF configuration mechanism “knows” to configure the relevant assembly.

    public interface IIsolatedServiceHost
    {
        void Init(string assemblyFilePath);
        void Start();
        void Shutdown();
    }

    public class IsolatedServiceHost : MarshalByRefObject, IIsolatedServiceHost
    {
        IList<Type> _services;
        List<ServiceHost> _hosts;

        public static IIsolatedServiceHost HostServices(string assemblyFilePath)
        {
            AppDomainSetup setup = new AppDomainSetup();
            setup.ApplicationBase = Path.GetDirectoryName(assemblyFilePath);
            setup.ApplicationName = Path.GetFileNameWithoutExtension(assemblyFilePath);
            setup.ConfigurationFile = assemblyFilePath + ".config";

            EnsureServiceHostHelpperAssemblyExists(assemblyFilePath);

            AppDomain hostAppDomain = AppDomain.CreateDomain(setup.ApplicationName, AppDomain.CurrentDomain.Evidence, setup);
            IIsolatedServiceHost host = hostAppDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, "ServiceHostHelpper.IsolatedServiceHost") as IIsolatedServiceHost;
            host.Init(assemblyFilePath);

            return host;
        }
 

After creating the AppDomain, we create the IsolatedServiceHost instance in the context of the new AppDomain and we get a transparent proxy to it. There is one Quick & Dirty thing that I have done to solve a problem that can be solved in much more elegant way. I copy the ServiceHostHelpperAssembly assembly to the new ApplicationBase directory to let the CLR loader to find it. This is the purpose of the following code:
        private static void EnsureServiceHostHelpperAssemblyExists(string assemblyFilePath)
        {
            try
            {
                File.Copy(Assembly.GetExecutingAssembly().Location, Path.Combine(Path.GetDirectoryName(assemblyFilePath), Path.GetFileName(Assembly.GetExecutingAssembly().Location)), true);
            }
            catch
            {
            }
        }

 

Now we call the Init() method that does the service discovery by examine the WCF configuration and taking the service name as the service instance type. Another approach is to find all the types in the assembly that have the ServiceBehavior attribute. Since this attribute is not a must I preferred the first way.

        
        public void Init(string assemblyFilePath)
        {
            _services = new List<Type>();
            Assembly serviceAssembly = Assembly.LoadFrom(assemblyFilePath);

            System.Configuration.Configuration config =
              System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.None);

            ServiceModelSectionGroup serviceModelSectionGroup = config.GetSectionGroup("system.serviceModel") as ServiceModelSectionGroup;
            
            foreach (ServiceElement serviceElement in serviceModelSectionGroup.Services.Services)
            {
                _services.Add(serviceAssembly.GetType(serviceElement.Name));
            }
        }

       

Now, we just need to start hosting the discovered service. This is the task of the Start() method:

 public void Start()
 {
     _hosts = new List<ServiceHost>();

     foreach (Type serviceType in _services)
     {
         ServiceHost serviceHost = new ServiceHost(serviceType);
         _hosts.Add(serviceHost);
         serviceHost.Open();
         System.Diagnostics.Trace.WriteLine(string.Format("Host for {0} has been started...", serviceType.Name));
     }
 }

       

We keep the hosts to allow shutting down the hosts when it is needed.

public void Shutdown()
{
    foreach (ServiceHost serviceHost in _hosts)
    {
        serviceHost.Close();
        System.Diagnostics.Trace.WriteLine("Host for {0} has been closed...", serviceHost.Description.ServiceType.Name);
    }
}

To host many services with many app.config file, the EXE gets all the matched (dll) file in a directory and create a host to each of them:

namespace WCFHost
{
    class Program
    {
        private static List<IIsolatedServiceHost> _hosts = new List<IIsolatedServiceHost>();

        static void Main(string[] args)
        {
            HostAll();
            Console.WriteLine("Press <Enter> to exit...");
            Console.ReadLine();
        }

        private static void HostAll()
        {
            string [] assemblyFiles = Directory.GetFiles(@"C:\Users\alon\Documents\Visual Studio Codename Orcas\Projects\KLA\GenericHostExample\WCFHost\Services", "*.dll", SearchOption.TopDirectoryOnly);

            foreach (string assemblyFile in assemblyFiles)
            {
                IIsolatedServiceHost host = IsolatedServiceHost.HostServices(assemblyFile);
                _hosts.Add(host);
                host.Start();
            }
        }

        private static void ShutdownAll()
        {
            _hosts.ForEach((host) => host.Shutdown());
        }
    }
}

 

As I said, this is just for the example, you should use another configuration file that will tell you where to look for all of your services assembly (of course, without full path :)

 

You can download the solution from here.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

7 comments

  1. MoMarch 13, 2008 ב 23:42

    I was reading your topic and was wondering if this would be a good way to have the service host configurable, meaning that I don’t have to recompile my hosting service if I ever need to add a new contract with a new implementation type. I have no idea why do you have to specify your type to the service host constructor, cause that means I will have to recompile my hosting service if I add any new contracts.
    Thanks

    Reply
  2. AlonMarch 15, 2008 ב 14:39

    Hi

    You don’t. I take the type name from the dll.config file and the type itself is discovered in the Init() method. So it is exactly what you wanted.

    Alon.

    Reply
  3. MoMarch 18, 2008 ב 23:37

    Thanks for sharing this information. This was really helpful. My last question for you would be about the EnsureServiceHostHelpperAssemblyExists method. I beleive you copy the IsolatedServiceHost assembly to the folder where you have the services assemblies, so what was the more elegant way you wer mentioning? Are you talking about loading the assembly instead?
    Thanks

    Reply
  4. MoMarch 19, 2008 ב 05:51

    Hi,
    Answering myself on the last post, I GACed the assembly and that worked really well.
    Thanks again

    Reply
  5. ThomasBrabenderMay 21, 2008 ב 03:27

    Thank you for the posting this solution.

    Reply
  6. ThomasBrabenderMay 21, 2008 ב 03:27

    Thank you for the posting this solution.

    Reply
  7. BillSeptember 26, 2008 ב 13:01

    Awesome!

    Now I just need to refactor this to use a database table instead of config files and it will be complete (for me anyway).

    Can’ thank you enough.

    Reply