Dynamic IIS hosted WCF Service

By | November 21, 2010

For hosting WCF Services in IIS you usually need a .svc file. This File exposes your Service.
With ASP.NET virtual Paths you have the opportunity to load your service without a .svc file.
You can put the WCF Service inside a DLL without adding a reference to the ASP.NET WebApp.

Implementation:

– Create a Class derived from VirtualPathProvider and override the Methods GetFile and FileExists.
– Create a Class derived from VirtualFile and override the Method Open.
– Create a Class derived from ServiceHostFactory and override the Method CreateServiceHost.
– The VirtualPathProvider manages the calling URLs and returns a VirtualFile, if the client calls a WCF Service.
– The VirtualFile generates the .svc Content.
– The ServiceHostFactory hosts the WCF Service.
– Register the VirtualPathProvider in global.asax

Here is the code: (VirtualPathProvider)

 
public class ServicePathProvider : VirtualPathProvider
{
    public override VirtualFile GetFile(string virtualPath)
    {
        if (!this.IsServiceCall(virtualPath))
            return this.Previous.GetFile(virtualPath);
        return new ServiceFile(virtualPath);
    }
        
    private bool IsServiceCall(string virtualPath)
    {
        // Check if it is a wcf service call
        virtualPath = VirtualPathUtility.ToAppRelative(virtualPath);
        return (virtualPath.ToLower().StartsWith("~/srv_"));
    }

    public override bool FileExists(string virtualPath)
    {
        if (!this.IsServiceCall(virtualPath))
            return this.Previous.FileExists(virtualPath);
        return true;
    }
}

VirtualFile

 
public class ServiceFile : VirtualFile
{
    public ServiceFile(string virtualPath) 
        : base(virtualPath) 
    { }

    public string GetCallingServiceName
    {
        get 
        {
            // Return class name. srv_hello.svc => hello
            return base.VirtualPath.Replace("/srv_", string.Empty)
                        .Replace(".svc", string.Empty).ToLower(); 
        }
    }

    public string GetService()
    {

        string srv = this.GetCallingServiceName;
        // hello => Hello
        return srv[0].ToString().ToUpper() + srv.Substring(1);
    }

    public override Stream Open()
    {
        var serviceDef = new MemoryStream();
        var defWriter = new StreamWriter(serviceDef);
            
        // Write host definition
        defWriter.Write("<%@ ServiceHost Language=\"C#\" Debug=\"true\" Service=\"Services."+ this.GetService() + "\"  " +
                        "Factory=\"DynamicServices.DynamicHostFactory, DynamicServices\" %>");
        defWriter.Flush();
            
        serviceDef.Position = 0;
        return serviceDef;
    }
}

ServiceHostFactory

public class DynamicHostFactory : ServiceHostFactory
{
    public DynamicHostFactory() { }

    public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
    {
        // Load bin/services.dll
        var asm = Assembly.Load("Services");
        var serviceType = asm.GetType(constructorString);
        var host = new ServiceHost(serviceType, baseAddresses);

        // Add endpoints. (In this example only IHello interface)
        foreach (Type contract in serviceType.GetInterfaces())
        {
            var attribute = (ServiceContractAttribute)
                Attribute.GetCustomAttribute(contract, typeof(ServiceContractAttribute));
            if (attribute != null)
                host.AddServiceEndpoint(contract, new BasicHttpBinding(), "");
        }
        // Add metdata behavior for generating wsdl
        var metadata = new ServiceMetadataBehavior();
        metadata.HttpGetEnabled = true;
        host.Description.Behaviors.Add(metadata);
            
        return host;
    }
}

Global.asax

void Application_Start(object sender, EventArgs e)
{
    HostingEnvironment.RegisterVirtualPathProvider(new ServicePathProvider());
}

Example:
I created a DLL (Services.dll) with a WCF service. Here the Code:

[ServiceContract]
public interface IHello
{
    [OperationContract]
    string SayHello(string name);
}

public class Hello : IHello
{
    public string SayHello(string name)
    {
        return "Hello " + name;
    }
}

I compiled the DLL and copied it into the bin Folder of the ASP.NET Application.
When I start the WebApp and call the URL “http://localhost:{Port}/srv_hello.svc” the Service UI of the Hello Service (Services.dll) will be generated. Voila! ๐Ÿ˜‰
WCF Service (DLL)

EDIT:
Since WCF 4.0 there is another way to achieve the same thing. It is called “file less activation”. ๐Ÿ™‚
Example:


    
      
        
      
    
  
  • vinay

    Hi,

    I’m highly impressed with this above solution approach.
    But, I’m running into problems while implementing the above in my project.

    Can you please send me the sample project code of the above application.

    my email id is (hidden)

    Thanks in advance.
    Vinay

  • admin

    Check your mails!

  • Steven

    Hi,

    I am interested in your solution. Hope you can send me a working sample fo the solution.

    Hope to hear from you soon. Thanks.

    Best Regards

    Steven

  • admin
  • Pingback: Plug and Play Services with MEF and WCF : var Matt = new Hero();()

  • Sinan Oran

    Hi,

    When I want to use tcpbinding. What should I do in that solution.

    Hope to hear from you soon. Thanks.
    Best Regards

  • admin

    Hi Sinan

    Download my demo project from here: http://blog.micic.ch/wp-content/uploads/2011/05/DynamicServices.zip

    Turn the “Windows Communication Foundation HTTP Activation” and the “Windows Communication Foundation Non-HTTP Activation” features on. (Maybe.. only the Non-HTTP is needed.)

    Add a net.tcp Binding on your web (IIS).
    Then change the property “Enabled Protocols” under “Advanced Settings…” from “http” to “http, net.tcp”.

    In the DynamicHostFactory.CreateServiceHost() method (demo project) change “new BasicHttpBinding();” to “new NetTcpBinding();”.

    Change following things in the app.config file of the ServiceClient:
    Remove the attribute “bindingConfiguration=…” from the “endpoint” element.
    Then replace the value of the “address” attribute through the new net.tcp address.
    And last… change the attribute “binding” to “netTcpBinding”.

    I tested it. It works!

    Let me know if you need further help.

    EDIT:
    If any LoadType.. ServiceModel.. exceptions after the HTTP/non-HTTP installation occur, then execute “aspnet_regiis.exe -iru”. ๐Ÿ™‚

  • Sinan Oran

    Thank you very much

  • Sinan Oran

    Hi,again
    I changed this part. using MEF

    var asm = Assembly.Load(“Services”);
    var serviceType = asm.GetType(constructorString);

    How can I cache this part. Because every call this part run again.

  • admin

    Hi Sinan
    No. This code will be executed when you call the service the first time. (In browser… or a service method)
    (Try it.. Write something into a txt file beneath this code.)

    This blog post just shows how it works. So it’s good that you decided to extend it with MEF.

    Matthew Abbott wrote a great post about MEF/WCF: http://www.fidelitydesign.net/?p=390

  • Sinan Oran

    Hi darko,
    Do you have any idea about wcf UI cache. I want to cache wcf service result and then when client call service again. I want to not to go to service. I want to send result from cache. I inspect message with IClientMessageInspector at client side. How can I do that process.

  • admin

    Hi Sinan
    I never did this before. I’m not sure, if IClientMessageInspector is the right/easiest way. With this interface you have access to the message before it will be sent to the server and AFTER the result is arrived. But in your case you have to write the message (in a way).

    Here are a few thoughts…

    1) Writing a wrapper around your client proxy. ๐Ÿ™‚

    2) Spring.NET has a AOP extension for WCF. This could be a nice solution: http://www.springframework.net/docs/1.2.0-M1/reference/html/wcf-quickstart.html

    3) Writing a WCF routing service which runs on the client. The client calls everytime the “router”. The router decides if the data is available (cached) or if the service must be called. (There are multiple ways to inspect the service request.)
    Maybe… you will need a second implementation of your services which runs on your client. This service will return only the cached data.
    (It’s a lot of overhead… This may blow up your client.)
    WCF Router example: http://blog.micic.ch/net/creating-a-wcf-4-0-routing-service

    Let me know about your solution. ๐Ÿ™‚

  • Sinan Oran

    Hi Darko,

    I find it. this link is referenced for me. http://blogs.msdn.com/b/paolos/archive/2011/04/05/how-to-use-a-wcf-custom-channel-to-implement-client-side-caching.aspx. Thank you very much for your helps.

  • admin

    Thank you for your link. Looks interesting!!