Crear un servicio con C# y .NET6 o posteriores
Es inevitable tener que ir migrando nuestro software por diferentes motivos, los nuevos Sistemas Operativos ya no lo soportan, falta de soporte, brechas de seguridad...
Esta semana he tenido que migrar un software que se ejecuta como un servicio de Windows y lo he hecho a .NET Core (antes se le llamaba .NET Core).
¿Por qué no lo he migrado a .NET Framework y así me ahorro más trabajo?
Porque probablemente el servidor donde va a correr este servicio se migre a la nube y para ahorrar costes se contrate un servidor con Linux 🐧
Al programar el servicio sobre .NET (.NET Core), mi servicio funcionará también en Linux 😎
La versión de .NET que he escogido ha sido la versión 6.
Sé que ya está disponible .NET 7, pero la compatibilidad será mayor para .NET 6, según Microsoft.
Crear un programa de tipo Worker Service
Lo primero que haremos será abrir nuestro Visual Studio y crear un nuevo proyecto de tipo Worker Service.
Elegimos un nombre, en mi caso EjemploServicioDotNET.
Elegimos el Framework en el cuál desarrollaremos. Como ya te he dicho será .NET6.
Como resultado, Visual Studio nos generará un proyecto con la siguiente estructura:
Añadiendo dependencia necesaria
Para interoperar con servicios nativos de Windows a partir de implementaciones de .NET IHostedService, deberás instalar el paquete NuGet Microsoft.Extensions.Hosting.WindowsServices .
Después de añadir dicha dependencia, tu archivo de proyecto (el mío es EjemploServicioDotNET.csproj) debería tener el siguiente contenido:
Creando nuestro programa
En este ejemplo voy a crear un servidor HTTP que responderá a una petición GET.
Nada muy sofisticado 😅
Creo la clase MyHTTPServer
y añado el siguiente código:
public MyHTTPServer() { HttpListener _listener = new(); Thread _listenerThread; _listenerThread = new(StartListener); const string url = "http://localhost:8888/"; _listener.Prefixes.Add(url); _listener.Start(); _listenerThread.Start(); Console.WriteLine("Servidor HTTP iniciado en " + url); void StartListener() { while (_listener.IsListening) { var context = _listener.GetContext(); var request = context.Request; { var response = context.Response; string miRespuesta = "Hola desde el servidor HTTP de @fjmduran"; if (request.HttpMethod == "GET") { miRespuesta += "\nHa hecho una petición GET"; } byte[] responseBytes = System.Text.Encoding.UTF8.GetBytes(miRespuesta); response.ContentLength64 = responseBytes.Length; response.OutputStream.Write(responseBytes, 0, responseBytes.Length); response.OutputStream.Close(); } } }
Editando la clase Worker
La clase Worker será la encargada de ejecutar la acción de nuestro servicio.
La parte interesante es la función ExecuteAsync
, es aquí donde programaremos lo que queremos que haga nuestro servicio.
En el await Task.Delay(1000, stoppingToken)
le indicaremos en milisegundos el tiempo que debe esperar entre ejecuciones.
Si no incluimos esta parte, el método se volverá a ejecutar instantáneamente cuando finalice.
El tiempo se indica en milisegundos.
Deja el código de tu Worker.cs así:
public class Worker : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { MyHTTPServer myHTTPServer = new(); while (!stoppingToken.IsCancellationRequested) { await Task.Delay(1000, stoppingToken); } } }
Editando la clase Program
Edita tu clase Program y déjala así:
using EjemploServicioDotNET; using Microsoft.Extensions.Logging.Configuration; HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); builder.Services.AddWindowsService(options => { options.ServiceName = "Test Servicio con .NET"; }); builder.Services.AddSingleton<MyHTTPServer>(); builder.Services.AddHostedService<Worker>(); // See: https://github.com/dotnet/runtime/issues/47303 builder.Logging.AddConfiguration( builder.Configuration.GetSection("Logging")); IHost host = builder.Build(); host.Run();
El método de extensión AddWindowsService configura la aplicación para que funcione como un servicio de Windows.
El nombre del servicio se establece como "Test Servicio con .NET".
El servicio alojado se registra para la inyección de dependencias.
Si quieres más información sobre el registro de servicios, consulta Inyección de dependencias en .NET.
Probando nuestro servidor HTTP
Si ejecutamos nuestra aplicación desde Visual Studio debería de correr perfectamente:
Si hacemos una petición GET a nuestro servidor, por ejemplo usando Postman deberías ver:
Compilando nuestra aplicación para que se comporte como un servicio
Para crear la aplicación .NET Worker Service como un servicio de Windows, se recomienda publicar la aplicación como un único archivo ejecutable.
Es menos propenso a errores tener un ejecutable autocontenido, ya que no hay archivos dependientes en el sistema de archivos.
No obstante, puedes elegir una modalidad de publicación diferente, lo cual es perfectamente aceptable, siempre y cuando crees un archivo *.exe que pueda ser apuntado por el Administrador de Control de Servicios de Windows.
Vamos al turrón, dale a publicar en carpeta y te generará todos los ficheros necesarios para que tu servicio corra en Windows.
Crear el servicio
Para crear el servicio abriremos una consolo con privilegios de administrador y ejecutaremos:
sc create "Test Servicio con .NET" binpath="D:\fjmduran\programacion\net\EjemploServicioDotNET\bin\Release\net6.0\publish\EjemploServicioDotNET.exe"
Comprobar que el servicio se ha registrado
Para comprobar que el servicio se ha registrado abriremos los servicios de Windows como administrador y buscaremos el nuestro, que le asignamos el nombre de Test Servicio con .NET
Iniciando y parando el servicio
No puede ser más fácil, con un botón de play y stop
En la siguiente imagen puedes ver que el servicio ya está corriendo porque está el botón de play deshabilitado y porque en la columna de Estado puedes leer En ejecución.
Si vuelves a hacer una petición GET al puerto 8888 verás como te responde satisfactoriamente.
Eliminar el servicio
sc delete "Test Servicio con .NET" binpath="D:\fjmduran\programacion\net\EjemploServicioDotNET\bin\Release\net6.0\publish\EjemploServicioDotNET.exe"
Repositorio de código de un servicio para Windows con .NET 6
Encontrarás el repositorio con todos los archivos de código en este enlace de mi GitHub 🔥
Hasta luego 🖖