Click here to Skip to main content
Click here to Skip to main content
Go to top

Generic WCF Host

, 13 Mar 2008
Rate this:
Please Sign up or sign in to vote.
Generic console hoster without locking up service assemblies, speeding up WCF services development with VS2005.

ConsoleHoster.GIF
Download ConsoleHoster.zip - 27.86 KB

Introduction

The main propose of this article is to develop a generic WCF host implementation, to help when developing WCF services inside Visual Studio 2005.

Background

When you need to develop a WCF service, you need to host it somewhere. So far, you have three options: IIS 6.0 (HTTP only), IIS7/WAS (Windows 2008/Vista) or self host (desktop application, windows service, etc).

A quick look at several development sites shows developers mostly ends up coding their own WCF host using a desktop application. The main reasons are habitual: it's cheap, it's easy, it's fun. Create a new console application, add a reference to WCF project, five lines of code and you are ready to go.

Inside a few other sites, you can see people writing windows services to act as host. But you can easily note some drawbacks of this approach: errors are written to Event Viewer, assemblies exclusively locked (so you can't rebuild that assembly) and some additional steps to stop/recompile/restart the host (mav wrote a tip about that).

To help with this scenarios, I wrote a generic console host which receive notifications when that assemblies are rebuild, dropping their AppDomains and starting a new one. To avoid a boring, almost static black screen, I also display some information about the messages received and sent through this host, and exceptions that can be thrown.

If you are using Visual Studio 2008, there is two utilities that do same thing: WCF Service Host and WCF Test Client. They can be found inside your %ProgramFiles%\Microsoft Visual Studio 9.0\Common7\IDE directory.

Using the code

Watching the file system

We start configuring the DelayedFileSystemWatcher object and looking for all assemblies which already reside inside configured directory.

static Dictionary<string, AppDomain> appDomains = new Dictionary<string, AppDomain>();

static void Main(string[] args)
{
    // can't find settings at *.exe.config file? look at current directory
    const string pattern = "*.dll";
    string dropPath = Path.GetFullPath(
        args.Length == 0 ?
            ConfigurationManager.AppSettings["DropPath"] ?? 
                Environment.CurrentDirectory : args[0]);

    Console.Title = dropPath + Path.DirectorySeparatorChar + pattern;
    if (!Directory.Exists(dropPath))
        throw new DirectoryNotFoundException(dropPath);

    // sets up file system monitoring
    DelayedFileSystemWatcher dfsw = new DelayedFileSystemWatcher(dropPath, pattern);
    dfsw.Created += new FileSystemEventHandler(dfsw_CreatedOrChanged);
    dfsw.Changed += new FileSystemEventHandler(dfsw_CreatedOrChanged);
    dfsw.Deleted += new FileSystemEventHandler(dfsw_Deleted);

    // before start monitoring disk, load already existing assemblies
    foreach(string assemblyFile in Directory.GetFiles(dropPath, pattern))
    {
        Create(Path.GetFullPath(assemblyFile));
    }

    dfsw.EnableRaisingEvents = true;
    Console.ReadLine(); // stay away from ENTER key
    Console.ResetColor();
}

Note I'm using DelayedFileSystemWatcher, written by Adrian Hamza. His class encapsulates regular FileSystemWatcher, adding a time pool. If multiple events triggers inside a time interval (one second in this case), it queue them, removing the duplicates ones.

The events I signed up let me mantain the AppDomains list. If a assembly was Deleted, I need to drop his AppDomain; If Created or Changed, I need to drop and recreate it, since I can't unload a single assembly from an AppDomain. Basically, I keep references to AppDomains I create inside a generic Dictionary<string, AppDomain>, using assembly full path as key, and catch all Exceptions who can arise, writing them to Console.

Since the whole point isn't there, I'll move into AppDomain creation and skip this details. You can find complete and runnable code inside this article attachment.

Creating the new AppDomain

In this step, I had to create a class who inherits from MarshalByRefObject class. I'll need to instantiate this class inside the foreign AppDomain and there to start the WCF services host. But there are a few gotchas:

  • You need to inform the correct configuration file for your service assembly prior to create the AppDomain;
  • You need to enable shadow copy assemblies (using ShadowCopyFiles property), or your DLL file will be locked up. Put it simple, the framework copies it to another location, keeping the original one free to be changed. Note it's a string property;
  • I had tried to inspect the service assembly for WCF services prior to create AppDomains, using Assembly.ReflectionOnlyLoadFrom(assemblyFile), but that approach also locks the assembly file. This technique is explained in an Kader Yildirim article, in French.
  • When you call CreateInstanceAndUnwrap with proxy class full name, the proxy instance will be created inside the new AppDomain. Here is where the magic resides.

class RemoteProxy : MarshalByRefObject
{
    static public AppDomain Start(string assemblyFile)
    {
        AppDomain domain = null;
        try
        {
            AppDomainSetup info = new AppDomainSetup();
            info.ShadowCopyFiles = "true";
            info.ConfigurationFile = assemblyFile + ".config";

            AppDomain appDomain =
                AppDomain.CreateDomain(
                    assemblyFile, null, info);

            RemoteProxy proxy = (RemoteProxy)appDomain
                .CreateInstanceAndUnwrap(
                    Assembly.GetExecutingAssembly().FullName,
                    typeof(RemoteProxy).FullName);
            if (!proxy.LoadServices(assemblyFile))
                AppDomain.Unload(appDomain);
            else
                domain = appDomain;
        }
        catch (Exception ex)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(ex.Message);
        }
        return domain;
    }
    
    // ...
}

In order to create our ServiceHost and start listening our services, we need to locate the classes whose have the [ServiceContract] attribute. That attribute can be define in class our in an inherited interface. That recursive code can also be found inside the article attachment.

Hosting WCF services

For now, let's load the service assembly and look for WCF service implementations. You need to create a AssemblyName to inform the assembly codebase, the path where other dependencies can be found. Once that service implementations are found, we will to create, finally, our ServiceHost.

bool hasServices = false;

public bool LoadServices(string assemblyFile)
{
    try
    {
        AssemblyName assemblyRef = new AssemblyName();
        assemblyRef.CodeBase = assemblyFile;
        Assembly assembly = Assembly.Load(assemblyRef);

        Type[] serviceTypes = LocateServices(assembly.GetTypes());
        foreach (Type serviceType in serviceTypes)
        {
            try
            {
                Create(serviceType);
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(ex.Message);
            }
        }
    }
    catch(Exception ex)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(ex.Message);
    }
    return hasServices;
}

So far, we're inside a new AppDomain, pointing to our service assembly configuration file, so we have all information about endpoints we need. For each loaded endpoint, we'll to add a server endpoint behavior to inspect the received messages, for debugging proposes.

private void Create(Type serviceType)
{
    Console.ForegroundColor = ConsoleColor.White;
    Console.WriteLine("Starting {0}", serviceType.FullName);
    try
    {
        ServiceHost host = new ServiceHost(serviceType, new Uri[0]);
        foreach (ServiceEndpoint endpoint in host.Description.Endpoints)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("   {0}", endpoint.Address);
            endpoint.Behaviors.Add(new MonitorBehavior());
        }
        host.Open();
        hasServices = true;
    }
    catch (Exception ex)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(ex.Message);
    }
}

"Yield the information you conceal"

To finish it, let's create a behavior to collect informations about the inbound/outbound messages. Starting with MonitorBehavior class, which implements the IEndpointBehavior interface. All methods implementations are empty in this case, except ApplyDispatchBehavior, in charge for attach our MonitorDispatcher, as follow:

class MonitorBehavior : IEndpointBehavior
{
    // ... empty methods removed ... \\

    public void ApplyDispatchBehavior(
        ServiceEndpoint endpoint,
        EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors
           .Add(new MonitorDispatcher());
    }
    
    class MonitorDispatcher : IDispatchMessageInspector
    {
        public object AfterReceiveRequest(
            ref Message request, 
            IClientChannel channel, 
            InstanceContext instanceContext)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(
                "{0:HH:mm:ss.ffff}\t{1}\n\t\t{2} ({3} bytes)\n\t\t{4}",
                DateTime.Now, request.Headers.MessageId, 
                request.Headers.Action, request.ToString().Length, 
                request.Headers.To);
            return null;
        }

        public void BeforeSendReply(
            ref Message reply,
            object correlationState)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(
                "{0:HH:mm:ss.ffff}\t{1}\n\t\t{2} ({3} bytes)",
                DateTime.Now, reply.Headers.RelatesTo,
                reply.Headers.Action, reply.ToString().Length);
        }
    }
}

The code above dumps at console the message id, timestamp, length and the action requested for inbound and outbound messages. That can be useful to look for services returning too much information (for instance, a big collection) or just to look at message XML content (calling ToString()).

Running the code

You will see a screen like this:

ConsoleHoster2.GIF

The article attachment contains two projects:

  • ConsoleHoster : The host server application. You should start it first. You can change the directory to be monitored inside the app.config file, which is displayed at window title bar. Pressing ENTER will quit this application.
  • ServiceImplementation : A sample implementation (service and client). The client part simply calls the service, which will be hosted at server part. Running it, press any key to call the service again or ESC to quit. Note the events fired at server host when you recompile this application.

Points of Interest

While I don't have an extensive experience using AppDomain, this implementation runs pretty smooth in my environment and I could successfully drop a windows service built to hold service hosts.

For further reading, I want to recommend Suzanne Cook and Bart De Smet blogs, where I could find lots of information about AppDomains and ShadowCopy.

History

Mar 16, 2008 1.0 First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

RubensFarias
Software Developer (Senior)
Brazil Brazil
MCAD, MCSD, MCDBA, MCPD, MCTS
Brazilian developer and freelancer. Currently interested in programming frameworks, code generation and development productivity.

Comments and Discussions

 
QuestionAmazing!!! [modified] PinmemberDaniel Carvalho Liedke18-Mar-14 6:09 
GeneralMy vote of 5 Pinmembermanoj kumar choubey27-Mar-12 4:08 
Questionerror in http endpoint hosting Pinmemberkkrakesh9-Feb-12 20:18 
GeneralWCF generic Windows service host PinmemberMember 386383118-Dec-08 6:22 
GeneralRe: WCF generic Windows service host PinmemberRubensFarias22-Dec-08 23:28 
GeneralProblem with hosting in same port PinmemberIgor.Laptiev10-Jun-08 19:33 
GeneralRe: Problem with hosting in same port PinmemberS. Kolic26-Nov-10 6:49 
GeneralRe: Problem with hosting in same port Pinmemberkkrakesh9-Feb-12 20:17 
Generalnice Pinmembermeetsenthilbtech6-May-08 0:52 
Generalty PinmemberRubensFarias9-May-08 14:15 
GeneralRe: nice Pinmembersmacaulay@gmail.com2-Feb-09 15:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140926.1 | Last Updated 13 Mar 2008
Article Copyright 2008 by RubensFarias
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid