Injection Dependency: Bring your own container in .NET Core console App, example with Simple Injector

Introduction of Simple Injector

Simple Injector is an easy-to-use Dependency Injection (DI) library for .NET that supports .NET Core, Xamarin, Mono and Universal apps. Simple Injector is easily integrated with frameworks such as Web API, MVC, WCF, ASP.NET Core and many others. It’s easy to implement the dependency injection pattern with loosely coupled components using Simple Injector.

Why Simple Injector? It’s simple to use, free, fast, support advanced generics types, and provide powerful diagnostics services.

If you want to know more about you can check the documentation here: https://simpleinjector.readthedocs.io/en/latest/quickstart.html

Installation of Simple Injector in a console app

Select and Install it from “Manage Nuget packages” panel

or type the following command in “Package manager console”:

PM> Install-Package SimpleInjector -Version 4.0.12

Configuration of Simple Injector in a console app

  • Import SimpleInjector and SimpleInjector.Lifestyles namespaces
  • Add a static Container property in your class Program
  • Register your service with its appropriate Interface, Simple Injector supports concrete injection (the class without its interface)
  • Optionnally add Verify method, it iterates registered service to check if something is not correct, will throw an exception before any execution of the progam
  • Then use GetInstance method to get your service

Example :

public interface IMyService
{
   string HelloWorld();
}
public class MyService: IMyService
{
   public string HelloWorld()
   {
      return "Hello world!";
   }
}
using SimpleInjector;
using System;

namespace ConsoleAppDemoWithSimpleInjector
{
   class Program
   {
      static readonly Container container;

      static Program()
      {
         container = new Container();

         container.Register<IMyService, MyService>();

         container.Verify();
      }

      static void Main(string[] args)
      {
         var service = container.GetInstance<IMyService>();
         Console.WriteLine(service.HelloWorld());
         Console.ReadLine();
      }
   }
}

Execution:

Configuration of Simple Injector in a console app thats runs undefinitely

In the absence of any framework code, you are yourself responsible to tell Simple Injector that certain code must run in isolation. This can be done with Scoping. There are two types of scoped lifestyles that can be used. ThreadScopedLifestyle allows wrapping code that runs on a single thread in a scope, where AsyncScopedLifestyle allows wrapping a block of code that flows asynchronously (using async await).

The following example demonstrates a simple Console application that runs indefinitely, and executes a request every second. The request is wrapped in a scope:

class Program
{
   static readonly Container container;

   static Program()
   {
      container = new Container();
      container.Options.DefaultScopedLifestyle = new ThreadScopedLifestyle();

      container.Register<IMyService, MyService>();

      container.Verify();
   }

   static void Main(string[] args)
   {
      while (true)
      {
         using (ThreadScopedLifestyle.BeginScope(container))
         {
            var service = container.GetInstance<IMyService>();

            Console.WriteLine(service.HelloWorld());
         }

         Thread.Sleep(TimeSpan.FromSeconds(1));
      }
   }
}

By default the lifecycle of our service is Transient, it means that a new instance will be created each we ask an instance of our service, else you can set Singleton.

Transient lifestyle

container.Register<IMyService, MyService>(Lifestyle.Transient);

or

container.Register<IMyService, MyService>();

Singleton lifestyle

container.Register<IMyService, MyService>(Lifestyle.Singleton);

Example that display Guid of the instance:

public class MyService: IMyService
{
   private Guid _guid;

   public MyService()
   {
      _guid = Guid.NewGuid();
   }

   public string HelloWorld()
   {
      return $"Hello world! instance: {_guid}";
   }
}

Execution :

Transient lifestyle

Guid are not identicals

Singleton lifestyle

Guid are identicals

 

Simple isn’it ? 🙂

How to create and configure a Windows service with Topshelf and Ninject?

This article aims to present how it is possible to easily create and configure a Windows service with Topshelf and Ninject using dependency injection.

Topshelf NuGet is a package for encapsulating a classic console application in a Windows Service.

Ninject is also a NuGet package is container IOC to implement dependency injection.

Step 1, create a console application project and download packages via the management console :

PM > Install-Package Topshelf
PM > Install-Package Topshelf.Ninject

Step 2, define / service(s) interface(s) and the implement service to what the windows service depends on :

namespace TopShelfWindowsService.Services
{ 
   public interface IMyService
   { 
       void DoWork(); 
   } 
}
public class MyService : IMyService
{ 
   public void DoWork() 
   { 
      //Do something 
   } 
}

Step 3, implement a module inheriting from “ModuleNinject” in order to register the concrete classes implementing interfaces used for injection

I recommend the lifecycle Singleton for objects, here is how to declare them when recording classes :

using Ninject.Modules; 
using TopShelfWindowsService.Services; 
namespace TopShelfWindowsService
{ 
   public class IocModule : NinjectModule 
   { 
      // Bind Interfaces to implementations for dependancy injection
      public override void Load() 
      { 
         Bind<IMyService>().To<MyService>().InSingletonScope();
      } 
   } 
}

Step 4, implement the windows service deriving from the “ServiceControl” interface in the namespace “Topshelf”

I also propose an implementation of a timer that allows to execute a service all X seconds, while ensuring executes only one service at a time, through the static class “Monitor”.

By implementing the “ServiceControl” interface, it’s mandatory to implement the methods “Start” and “Stop”

using System.Threading; 
using System.Timers; 
using Topshelf; 
using TopShelfWindowsService.Services; 
using Timer = System.Timers.Timer; 
namespace TopShelfWindowsService 
{ 
   public class MyWindowsService : ServiceControl 
   { 
      private Timer _syncTimer; 
      private static object s_lock = new object(); 
      private IMyService _myservice; 
      
      // Constructor 
      public MyWindowsService(IMyService myService) 
      { 
         _myservice = myService; 
      }

      // Starts the windows service
      public bool Start(HostControl hostControl) 
      { 
         _syncTimer = new Timer(); 
         _syncTimer.Interval = 5000; 
         _syncTimer.Enabled = true; 
         _syncTimer.Elapsed += RunJob; 
         return true; 
      }

     // Stops the windows service
     public bool Stop(HostControl hostControl) 
     { 
        _syncTimer.Enabled = false; 
        return true; 
     } 

     // Job runner event, with lock if the job still running
     private void RunJob(object state, ElapsedEventArgs elapsedEventArgs) 
     { 
        // Prevents the job firing until it finishes its job 
        if (Monitor.TryEnter(s_lock)) 
        { 
           try 
           { 
              _myservice.DoWork(); 
           } 
           finally 
           { 
              // unlock the job 
             Monitor.Exit(s_lock); 
           } 
        } 
     } 
   } 
} 

Step 5, implement the entry point at the start of the windows service in a console application through Topshelf :  Here we will set the use Ninject for dependency injection, and the name of windows service, description ….

using Topshelf; 
using Topshelf.Ninject; 
namespace TopShelfWindowsService 
{ 
   class Program 
   { 
      static void Main(string[] args) 
      { 
         HostFactory.Run(x => { 
            x.UseNinject(new IocModule()); 
            x.Service(s => 
            { 
               s.ConstructUsingNinject(); 
               s.WhenStarted((service, hostControl) => service.Start(hostControl)); 
               s.WhenStopped((service, hostControl) => service.Stop(hostControl)); 
            }); 
            x.RunAsLocalSystem(); 
            x.SetDescription("Prototype .NET TopShelf Windows Service"); 
            x.SetDisplayName("Prototype_TopShelf_and_Ninject"); 
            x.SetServiceName("Prototype_TopShelf_and_Ninject"); 
        }); 
      } 
   } 
}

Step 6, deploy the windows service : 

Once the windows service is functional, move it (the config file, dlls and exe generated at compile time) to a directory on your machine.

Then, in the command line, go to the directory that contains dlls and then run the install  as follow :

nom_de_l_executable.exe install

Start it as follow :

nom_de_l_executable.exe start

You can also stop the service and uninstall it :

nom_de_l_executable.exe stop
nom_de_l_executable.exe uninstall

Samples :

topshelf-1

topshelf-2

topshelf-3