Search code examples
c#wcfwindows-servicesappdomainshadow-copy

Start WCF service in a new AppDomain to enable shadow copy (Windows Service hosted)


I have a WCF service library (MyWCFService), which uses MEF to load plugins and hosted by Windows services (All .NET 4.0). I am now trying to run it in a new AppDomain and to enable ShadowCopyFiles in a hope that I can update plugins in the runtime. Here is the code in the Windows service project.

Program.cs

 static class Program
    {  
        static void Main()
        {  
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[] 
            { 
               new MyService() 
            };
            ServiceBase.Run(ServicesToRun);
        }
    }

MyService.cs

  public partial class MyService: ServiceBase
    {
        internal static ServiceHost MyServiceHost = null;
        public MyService()
        {
             // this works but is deprecated..
             AppDomain.CurrentDomain.SetShadowCopyFiles();

            //this is not working.. DLLs still get locked. Require for a new AppDomain
            //AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles = "true";  

            InitializeComponent();
        }   

        protected override void OnStart(string[] args)
        {
            if(MyServiceHost  !=null)
            {
                MyServiceHost.Close();
            }
            try
            {
                MyServiceHost= new ServiceHost(typeof(MyWCFService));         
                MyServiceHost.Open();
            }
            catch(Exception)
            {
            }          
        }

        protected override void OnStop()
        {
            if (MyServiceHost!= null)
            {
                MyServiceHost.Close();
                MyServiceHost= null;
            }
        }       
    }

Is there any way of doing it? I have done a lot of search, but still don't know how to make it work with my current settings (or I just can't understand...)

I have tried to create a new AppDomain inside Main() and used domain.DoCallBack(new CrossAppDomainDelegate(() => { ServiceBase.Run(ServicesToRun); })) to start the service but I can't start it and keep getting "Error 1053: The service did not respond to the start or control request in a timely fashion".

And then I tried to just enable Shadow copy for the current appdomain by setting AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles = "true"; in MyWCFService.cs just before InitializeComponent(); I can start the service but the dlls are still locked. however, if I use AppDomain.CurrentDomain.SetShadowCopyFiles(); (a deprecated method) to enable the shadow copy, everything works. I'm more confused.


Solution

  • OK, I ended up with creating a shell/proxy class inherited from MarshalByRefObject and start the service from there, and here is the code:

    ServiceShell.cs

    public class ServiceShell:MarshalByRefObject
    {
         internal static ServiceHost MyServiceHost = null;
    
         public void Run()
         {
             if (MyServiceHost != null)
             {
                 MyServiceHost.Close();
             }
             try
             {
                 MyServiceHost = new ServiceHost(typeof(MyWCFService));
                 MyServiceHost.Open();
             }
             catch (Exception)
             {
             }          
         }
    
         public void Stop()
         {
             if (MyServiceHost!= null)
             {
                 MyServiceHost.Close();
                 MyServiceHost = null;
             }
         }
    }
    

    MyService.cs

    public partial class MyService: ServiceBase
    {        
        AppDomain domain;
        ServiceShell runner;
    
        public MyService()
        {   
            var setup = new AppDomainSetup
            {
                ShadowCopyFiles = "true"
            };
            domain = AppDomain.CreateDomain("MyServiceHostDomain", AppDomain.CurrentDomain.Evidence, setup);
    
            runner = (ServiceShell)domain.CreateInstanceAndUnwrap
                (typeof(ServiceShell).Assembly.FullName, typeof(ServiceShell).FullName);
            InitializeComponent();
    
        }
    
        protected override void OnStart(string[] args)
        {          
            runner.Run();
        }
    
        protected override void OnStop()
        {
            runner.Stop();
            AppDomain.Unload(domain);
        }       
    }