Implementación de Servicios de Fondo Multiplataforma en .NET Core

Arquitectura del Generic Host

La incorporación del Generic Host en el ecosistema .NET ha estandarizado la creación de procesos ejecutados en segundo plano. Esta herramienta nativa sustituye la necesidad de librerías de terceros al ofrecer un ciclo de vida administrado por defecto, inyección de dependencias integrada y capacidad de depuración directa mediante herramientas estándar (ejecución con F5). La arquitectura resultante garantiza consistencia operativa tanto en antornos de desarrollo como en producción.

Configuración Inicial y Estructura Base

Para generar la solución, se requiere un SDK igual o superior a la versión 3.0. Al seleccionar la plantilla Worker Service, el entorno de desarrollo crea automáticamente dos módulos centrales: el punto de entrada que configura el contenedor de servicios y la entidad que contiene la tarea cíclica.

A continuación se presenta una implementación refactorizada que mantiene la funcionalidad original pero optimiza la lectura y separación de responsabilidades:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;

namespace PlataformaServiciosFondo
{
    public class Program
    {
        public static void Main(string[] parametros)
        {
            ConstruirYLanzar(parmetros).Build().Run();
        }

        private static IHostBuilder ConstruirYLanzar(string[] argumentos) =>
            Host.CreateDefaultBuilder(argumentos)
                .ConfigureServices((context, colector) =>
                {
                    colector.AddHostedService<tareaoperativa>();
                    // Registro adicional de servicios mediante inyección compatible
                });
    }

    public class TareaOperativa : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken detencion)
        {
            while (!detencion.IsCancellationRequested)
            {
                // Ejecución de lógica de negocio programada
                await RealizarProcesamiento(detencion);
                await Task.Delay(TimeSpan.FromSeconds(15), detencion);
            }
        }

        private static async Task RealizarProcesamiento(CancellationToken token)
        {
            // Simulación de operación asíncrona
            await Task.CompletedTask;
        }
    }
}</tareaoperativa>

Adaptación Multiplataofrma

El modelo anterior es completamente agnóstico al sistema operativo, pero la interacción con el gestor de servicios requiere paquetes específicos. Para Windows se instala Microsoft.Extensions.Hosting.WindowsServices y para distribuciones Linux con init systemd se añade Microsoft.Extensions.Hosting.Systemd. Una misma compilación puede manejar ambos comportamientos utilizando reflexión de plataforma:

private static IHostBuilder DefinirContexto(string[] args)
{
    var baseConstructor = Host.CreateDefaultBuilder(args);

    return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
        ? baseConstructor.UseWindowsService()
                         .ConfigureServices((_, s) => s.AddHostedService<tareaoperativa>())
        : baseConstructor.UseSystemd()
                         .ConfigureServices((_, s) => s.AddHostedService<tareaoperativa>());
}</tareaoperativa></tareaoperativa>

Despliegue en Entornos Windows

La instalación en el registro de controladores exige privilegios elevados. Utilizando CMD o PowerShell como administrador, se registra el servicio especificando la ruta física del ejecutable:

sc.exe create NombreServicio binPath="C:\Ruta\Absoluta\bin\net6.0\Ejecutable.exe"

Un mensaje de confirmación indica éxito. Para ponerlo en marcha:

sc.exe start NombreServicio

Nota crítica: en PowerShell, la extensión .exe es obligatoria después de sc para evitar colisiones con el cmdlet Start-Service.

Despliegue en Entornos Linux

El procedimiento sigue estándares POSIX y systemd. Previo a la copia de los artefactos compilados, se recomienda crear un usuario restringido para aislar los permisos:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin operadordotnet

Se genera un descriptor de unidad en /etc/systemd/system/nombre-servicio.service:

[Unit]
Description=Daemon de Procesamiento .NET
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/dotnet /var/lib/app/Servicio.dll
WorkingDirectory=/var/lib/app
User=operadordotnet
Group=operadordotnet
Restart=on-failure
RestartSec=8s
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Una vez persistido el archivo, se actualiza el demonio, se activa el enlace simbólico de arranque y se levanta la instancia:

sudo systemctl daemon-reload
sudo systemctl enable nombre-servicio.service
sudo systemctl start nombre-servicio.service
sudo systemctl status nombre-servicio.service

Gestión de Ciclo de Vida y Limitaciones Operativas

La clase base expone hooks como StartAsync y StopAsync, permitiendo interceptar señales SIGTERM/SIGINT para finalizar operaciones pendientes o cerrar conexiones de forma limpia. Sin embargo, este enfoque no integra directamente opciones avanzadas del SCM de Windows, como pausas manuales prolongadas, políticas de error granulares o dependencias explícitas de otros servicios. Estas restricciones imponen un diseño más enfocado en la simplicidad y la portabilidad cruzada.

Diferencias frente a Alternativas Tradicionales

  • Ventajas técnicas: Adopción nativa de Cross-Platform sin emuladores ni capas de abstracción pesadas. Sincronización total con el pipeline de inyección de dependencias utilizado en APIs HTTP, reduciendo la curva de adaptación para desarrolladores Full-Stack.
  • Restricciones actuales: Ausencia de comandos auxiliares integrados (instalación/desinstalación vía CLI) que obligen al uso de utilidades del SO. Compatibilidad estricta con arquitecturas .NET Core 3.0+ y .NET 5+, excluyendo entornos Mono o frameworks heredados.

Etiquetas: DotnetCore GenericHost BackgroundService systemd WindowsServices

Publicado el 8-16 17:24