DEV Community

Cover image for Worker Service with .NET Core and ORMi
nicoriff
nicoriff

Posted on

Worker Service with .NET Core and ORMi

In this article, we are going to write a Worker Service app using ORMi to access WMI and get some information about the system and take some decisions based on results.

Worker Service is a kind of project that started to be available from .NET Core 2.1 but it got boosted in latest .NET Core 3.0 release and now you got the template project just as a separate project and not as an ASP.NET one.
Worker Services are applications that can be run as console apps, or can be hosted as Windows Services or Linux Daemons. That is just great. I´m personally using Worker Services in all my server side and service projects and I can say that it works like a charm so I do not write .NET Framework Windows Services kind of apps any more.

For making this app, you will need Visual Studio 2019 and .NET Core 3.0 installed. Let´s create the project:

Worker Service Template

For making Worker Services run as a Windows Service you will need some minor tweaks:

  1. You got to install the Microsoft.Extensions.Hosting.WindowsServices library from NuGet:

    Install-Package Microsoft.Extensions.Hosting.WindowsServices
    
  2. Add the the following line to the CreateHostBuilder method:

    .UseWindowsService()
    

    That will tell Windows we want to host this applicacion as a Windows Service. The method should be something like this:

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Worker>();
            });
    

Now, let´s install ORMi library via NuGet:

    Install-Package ORMi

ORMi is a library I wrote to make WMI work much more easier. It is extremely easy to get WMI information by just declaring your domain model clases and optionally adding some attributes if you want to enhance the experience.

In this article, we are going to make an app to monitor the CPU usage and make some decisions based on the processes that are running on the computer. First, we´ll add a Models folder and put all classes on it. Let´s create the model classes:

    [WMIClass("Win32_PerfRawData_PerfProc_Process")]
    public class Process
    {
        public string Name { get; set; }

        [WMIProperty("IdProcess")]
        public int Pid { get; set; }

        public long PercentProcessorTime { get; set; }

        public long TimeStamp_Sys100NS { get; set; }

        [WMIIgnore]
        public double CpuUsagePercent { get; set; }
    }

Note that we are going to use Win32_PerfRawData_PerfProc_Process to get the CPU usage percent and not Win32_Process class. Then we are going to need to add some logic to finally get the % CPU usage.
There are also some things to note on this class. Some properties got the WMIProperty and some not. This is because we can add the attibute to explicitely tell ORMi which will be the name of that property that has to be mapped on the WMI Class. If there is no attribute set, the ORMi will use the property name. The second thing to note is the WMIIgnore attribute. That attribute is set when we don´t want to map that property to any WMI class property. In this case, CpuUsagePercent will be set later.

Now we are going to create the WMI service class. As all new projects in .NET Core, Worker Service are built so you can easily inject dependencies using Dependency Injection.
Let´s create the interface for the WMI service:

    public interface IWMIService
    {
        Task<List<Processor>> GetAllProcessors();
        Task<List<Process>> GetProcesses();
    }

And now we create the service where we are implementing IWMIService:

    public class WMIService : IWMIService
    {
        private WMIHelper _helper = new WMIHelper("root/CimV2");

        public async Task<List<Processor>> GetAllProcessors()
        {
            List<Processor> res = (await _helper.QueryAsync<Processor>()).ToList();

            return res;
        }

        public async Task<List<Process>> GetProcesses()
        {
            List<Process> res = (await _helper.QueryAsync<Process>()).ToList();

            return res;
        }
    }

Now, to make the dependency injection work we have to register the service and then receive it on the constructor of the Worker class.

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddSingleton<IWMIService, WMIService>();
                services.AddHostedService<Worker>();
            });

And then on the constructor:

    public Worker(ILogger<Worker> logger, IWMIService wmiService)
    {
        _logger = logger;
        _wmiService = wmiService;
    }

Now, let´s add the logic!. Worker Service got a Worker with an ExecuteAsync method that is looped and executed while the application is alive. That is where we are going to put all the logic.

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                List<Process> processes1 = (await _wmiService.GetProcesses()).Where(p => p.Pid != 0).ToList();
                await Task.Delay(2000);
                List<Process> processes2 = (await _wmiService.GetProcesses()).Where(p => p.Pid != 0).ToList();


                List<Process> highCPUConsumingProccesses = new List<Process>();

                foreach (Process p1 in processes1)
                {
                    Process p2 = processes2.Where(pr => pr.Pid == p1.Pid).SingleOrDefault();

                    int procDiff = (int)(p2.PercentProcessorTime - p1.PercentProcessorTime);
                    int timeDiff = (int)(p2.TimeStamp_Sys100NS - p1.TimeStamp_Sys100NS);

                    double percentUsage = Math.Round(((double)procDiff / (double)timeDiff) * 100, 2);

                    Console.WriteLine($"{p1.Name}: {percentUsage}%");

                    if (percentUsage > 80)
                    {
                        p1.CpuUsagePercent = percentUsage;
                        highCPUConsumingProccesses.Add(p1);
                    }
                }

                if (highCPUConsumingProccesses.Any())
                {
                    SendMail(System.Environment.GetEnvironmentVariable("COMPUTERNAME"), highCPUConsumingProccesses);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            await Task.Delay(5000, stoppingToken);
        }
    }

As you see, the code is easy to read and all the WMI work is abstracted and solved pretty easy.
There are a few things to note:

  1. The process are filtered with .Where(p => p.Pid != 0) because there were some system projects that had PID 0. No need to have those.
  2. We have to create two lists of Processes so the CPU time can be calculated.
  3. The logic implemented will send an email to the network administrator every minute if there are high CPU consuming procceses. (I´m not sure that´s such a good idea :D). This is just a conceptual example.

Here is a link to the repo.

I hope this article is useful to know a little bit more about both Worker Service and ORMi library.

Top comments (2)

Collapse
 
katnel20 profile image
Katie Nelson

Does .UseWindowsService() run a Linux daemon and how do you get it to start/stop?

Collapse
 
nicoriff profile image
nicoriff

Hi Katie!. For using Worker Service as Linux daemon is pretty simple. Instead of installing Microsoft.Extensions.Hosting.WindowsServices, you got to install Microsoft.Extensions.Hosting.Systemd and instead of .UseWindowsService() just use .UseSystemd(). That´s it. Then you got to install it as preferred on your Linux environment.