donderdag 7 maart 2013

Building Windows Services using Autofac

In the previous article I've shown how you can build a Windows Service in .Net using Topshelf. In this article I will show how you can use Autofac in a Windows Service with Topshelf.

We ended the previous article with this little Windows Service:




Currently MyService is doing two things

1. looping till the Windows Service stops
2. Execute some work in the DoWork() method.

I don't think number two belongs in MyService. It violates the Single Responsibility Principle and as it's a private method, so we can't easily unit test it. The solution is to create a new class named Worker, and put all the work there:





Let's write the code for Worker:


public class Worker
{
   private Guid _workerId;

   public Worker()
   {
      _workerId = Guid.NewGuid();
   }

   public void Work()
   {
      Console.WriteLine("I am working. My id is {0}.", _workerId);

      Console.WriteLine("  Step 1");
     System.Threading.Thread.Sleep(1000);

     Console.WriteLine("  Step 2");
     System.Threading.Thread.Sleep(1000);

     Console.WriteLine("  Step 3");
     System.Threading.Thread.Sleep(1000);

     Console.WriteLine("  Step 4");
     System.Threading.Thread.Sleep(1000);

     Console.WriteLine("  Step 5");
     System.Threading.Thread.Sleep(1000);
   }
}


Now we can use Worker in MyService like this:


public class MyService
{
   readonly CancellationTokenSource _cancellationTokenSource;
   readonly CancellationToken _cancellationToken;
   readonly Task _task;

        public  MyService()
        {
            _cancellationTokenSource = new CancellationTokenSource();
            _cancellationToken = _cancellationTokenSource.Token;
            _task = new Task(DoWork, _cancellationToken);
        }

        private void DoWork()
        {
            while (!_cancellationTokenSource.IsCancellationRequested)
            {
                new Worker.Work();
            }
        }

        public void Start()
        {
            _task.Start();
        }

        public void Stop()
        {
            _cancellationTokenSource.Cancel();
            _task.Wait();
        }
    }


When we run the app we will get this output:


Configuration Result:
[Success] Name MyService
[Success] Description MyService using Topshelf
[Success] ServiceName MyService
Topshelf v3.1.106.0, .NET Framework v4.0.30319.18033
The MyService service is now running, press Control+C to exit.
I am working. My id is 7b60edb6-1d44-4750-81de-09468daeaecb.
  Step 1
  Step 2
  Step 3
  Step 4
  Step 5
I am working. My id is 37c0e8bb-2f46-4dc7-8568-dac6b5f5e0eb.
  Step 1
  Step 2
  Step 3
  Step 4
  Step 5
I am working. My id is e1413c84-0c18-4682-b56a-7a94365d2b57.
  Step 1
  Step 2
   ...


Notice that the Worker gets a new id everytime.

At this moment the Worker doesn't have any dependencies. In the real world the Worker maybe needs Entity Framework, Log4Net, or has to call a WCF service. We can instanciate all these dependencies by hand in the while-loop, and but I like to use autofac to do this for me.


Autofac and Windows Services

Where are we going to initialize autofac? In the Main() method of Program:




Let's install the Autofac nuget package:




Now we can initialise Autofac in the Main() method of Program. First add a new using statement:

using Autofac;

Next add the following code to the top of the Main() method::


var builder = new Autofac.ContainerBuilder();
builder.RegisterType<MyService>();
var container = builder.Build();


We can now use the Autofac container to get an instance of MyService and let it run in Topshelf. This is the old code:


HostFactory.Run(hostConfigurator =>
{
   hostConfigurator.Service<MyService>(serviceConfigurator =>
   {
      serviceConfigurator.ConstructUsing(() => new MyService());
      serviceConfigurator.WhenStarted(myService => myService.Start());
      serviceConfigurator.WhenStopped(myService => myService.Stop());
   });

   hostConfigurator.RunAsLocalSystem();
   hostConfigurator.SetDescription("MyService using Topshelf");
   hostConfigurator.SetDisplayName("MyService");
   hostConfigurator.SetServiceName("MyService");
});


Replace the marked line with this line:

serviceConfigurator.ConstructUsing(() => container.Resolve<MyService>());

If we run the app again, we will get the same results as previously, but now MyService is instanciated by Autofac.



Inject the Worker

Now we are using Autofac the create an instance of MyService. So how about an instance of Worker?
This is a but trickier. Let me start by showing the way it won't work right. Then you'll get a clue about how to do it the right way.

Register the Worker with Autofac:


var builder = new Autofac.ContainerBuilder();
builder.RegisterType<MyService>();
builder.RegisterType<Worker>();
var container = builder.Build();


We can inject Worker in the constructor of MyService:


public class MyService
{
   readonly CancellationTokenSource _cancellationTokenSource;
   readonly CancellationToken _cancellationToken;
   readonly Task _task;
   readonly Worker _worker;

   public MyService(Worker worker)
   {
      _worker = worker;

      _cancellationTokenSource = new CancellationTokenSource();
      _cancellationToken = _cancellationTokenSource.Token;

      _task = new Task(DoWork, _cancellationToken);
   }
   . . .
}


And then use _worker in the DoWork() method:


private void DoWork()
{
   while (!_cancellationTokenSource.IsCancellationRequested)
   {
      _worker.Work();
   }
}



This is what we'll see when we run the app again:

Configuration Result:
[Success] Name MyService
[Success] Description MyService using Topshelf
[Success] ServiceName MyService
Topshelf v3.1.106.0, .NET Framework v4.0.30319.18033
The MyService service is now running, press Control+C to exit.
I am working. My id is 08f6cc4a-e843-4d3d-a5c3-affeab56e0cf.
  Step 1
  Step 2
  Step 3
  Step 4
  Step 5
I am working. My id is 08f6cc4a-e843-4d3d-a5c3-affeab56e0cf.
  Step 1
  Step 2
  Step 3
  Step 4
  Step 5

I am working. My id is 08f6cc4a-e843-4d3d-a5c3-affeab56e0cf.
  Step 1


Notice that the Worker has the same id everytime. This is not the same as before, when we got a new id everytime.

The problem here is that Worker only gets injected once, and that instance is used everytime. But I want to get a new Worker everytime. How do we do that? We need to manage the lifetime of the components a bit better. (You can read more about that here and here)



LifetimeScope

We can tell Autofac that we want a new instance of Worker. We do this by creating a new Autofac LifetimeScope, and resolving a Worker from it. We have to change only a small bit of code for this to work. Let's start with the constructor of MyService:



public class MyService
{
   readonly CancellationTokenSource _cancellationTokenSource;

   readonly CancellationToken _cancellationToken;
   readonly Task _task;

   readonly ILifetimeScope _lifetimescope;

   public MyService(ILifetimeScope lifetimescope)
   {
      _lifetimescope = lifetimescope;

      _cancellationTokenSource = new CancellationTokenSource();
      _cancellationToken = _cancellationTokenSource.Token;

      _task = new Task(DoWork, _cancellationToken);
     }

   . . .
}

We can use the lifetimescope in the DoWork() method:


private void DoWork()
{
   while (!_cancellationTokenSource.IsCancellationRequested)
   {
      using(var workerScope = _lifetimescope.BeginLifetimeScope())
      {
         var worker = workerScope.Resolve<Worker>();
         worker.Work();
      }
   }
}

When we run the app now, we'll see that we get a new worker everytime again:

Configuration Result:

[Success] Name MyService
[Success] Description MyService using Topshelf
[Success] ServiceName MyService
Topshelf v3.1.106.0, .NET Framework v4.0.30319.18033
The MyService service is now running, press Control+C to exit.
I am working. My id is 4b7fb6b3-3222-4023-9452-790ff7feb49c.
  Step 1
  Step 2
  Step 3
  Step 4
  Step 5
I am working. My id is 02cb1bcd-64ca-417f-8d2b-f1afdeef75d6.
  Step 1
  Step 2
  Step 3
  Step 4
  Step 5
I am working. My id is 6da61ae3-8d50-4361-b0aa-4b77823c35b3.
  Step 1



Without ILifetimescope

We can make it even nicer by using a factory. Autofac can create factories out of the box for us.  You can read more about that here.

It works like this:

Change the constructor of MyService to accept an Autofac factory for Worker:




public class MyService
{
   readonly CancellationTokenSource _cancellationTokenSource;


   readonly CancellationToken _cancellationToken;
   readonly Task _task;


   readonly Func<Owned<Worker>> _workerFactory;

   public MyService(Func<Owned<Worker>> workerFactory)
   {
      _workerFactory = workerFactory;

      _cancellationTokenSource = new CancellationTokenSource();

      _cancellationToken = _cancellationTokenSource.Token;

      _task = new Task(DoWork, _cancellationToken);

   }
   . . .
}


We can use the _workerFactory in the DoWork() method:


private void DoWork()
{
   while (!_cancellationTokenSource.IsCancellationRequested)
   {
      using(var workerFactory = _workerFactory())
      {
         var worker = workerFactory.Value;
         worker.Work();
      }
   }
}





As you can see we never have to change anything in the registration with Autofac. We registered Worker once, and we can get an instance of Worker or a factory of Worker from Autofac if we want.
Also the lifetime and disposing is being managed by Autofac, which really makes it easy for us to focus in implementing the logic and not focus on the plumbing code.


Geen opmerkingen:

Een reactie posten