Creación de un servicio de Windows mediante BackgroundService
Los desarrolladores de .NET Framework probablemente estén familiarizados con las aplicaciones de servicio de Windows. Antes de .NET Core y .NET 5 y versiones posteriores, los desarrolladores que dependían de .NET Framework podían crear servicios de Windows para realizar tareas en segundo plano o ejecutar procesos de larga duración. Esta funcionalidad sigue estando disponible; por tanto, puede crear servicios Worker que se ejecuten como un servicio de Windows.
En este tutorial, aprenderá a:
- Publicar una aplicación del servicio Worker de .NET como un archivo ejecutable único
- Crear un servicio de Windows
- Crear la aplicación
BackgroundServicecomo un servicio de Windows - Iniciar y detener el servicio de Windows
- Ver los registros de eventos
- Eliminar el servicio de Windows
Sugerencia
Todo el ejemplo de "Trabajos en .NET" está disponible en el Explorador de ejemplos para su descarga. Para obtener más información, consulte Examinación de ejemplos de código: Trabajos en .NET.
Requisitos previos
- SDK de .NET 5.0 o posterior
- Un sistema operativo Windows
- Un entorno de desarrollo integrado (IDE) de .NET
- No dude en usar Visual Studio
Crear un proyecto nuevo
Para crear un proyecto de Worker Service con Visual Studio, seleccione Archivo > Nuevo > Proyecto... . En el cuadro de diálogo Crear un proyecto, busque "Worker Service" y seleccione la plantilla Worker Service. Si prefiere usar la CLI de .NET, abra su terminal favorito en un directorio de trabajo. Ejecute el comando dotnet new y reemplace <Project.Name> por el nombre del proyecto deseado.
dotnet new worker --name <Project.Name>
Para más información sobre el comando del nuevo proyecto de Worker Service de la CLI de .NET, vea dotnet new worker.
Sugerencia
Si usa Visual Studio Code, puede ejecutar comandos de la CLI de .NET desde el terminal integrado. Para más información, vea Visual Studio Code: terminal integrado.
Instalación de paquetes NuGet
Para interoperar con servicios de Windows desde implementaciones de IHostedService de .NET, deberá instalar el paquete NuGet Microsoft.Extensions.Hosting.WindowsServices.
Para instalarlo desde Visual Studio, use el cuadro de diálogo Administrar paquetes NuGet... . Busque "Microsoft.Extensions.Hosting.WindowsServices" e instálelo. Si prefiere usar la CLI de .NET, ejecute el comando dotnet add package:
dotnet add package Microsoft.Extensions.Hosting.WindowsServices
Como parte del código fuente de ejemplo de este tutorial, también deberá instalar el paquete NuGet Microsoft.Extensions.Http.
dotnet add package Microsoft.Extensions.Http
Para obtener más información sobre el comando add package de la CLI de .NET, consulte dotnet add package.
Después de agregar correctamente los paquetes, el archivo de proyecto ahora debería contener las siguientes referencias de paquetes:
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
</ItemGroup>
Actualización del archivo del proyecto
Este proyecto de trabajo usa los tipos de referencia que aceptan valores NULL de C#. Para habilitarlos para todo el proyecto, actualice el archivo del proyecto en consecuencia:
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<RootNamespace>App.WindowsService</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
</ItemGroup>
</Project>
Los cambios del archivo del proyecto anterior agregan el nodo <Nullable>enable<Nullable>. Para obtener más información, vea Establecimiento del contexto que acepta valores NULL.
Creación del servicio
Agregue una nueva clase al proyecto denominado JokeService.cs y reemplace su contenido por el siguiente código de C#:
using System.Net.Http.Json;
using System.Text.Json;
namespace App.WindowsService;
public class JokeService
{
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _options = new()
{
PropertyNameCaseInsensitive = true
};
private const string JokeApiUrl =
"https://karljoke.herokuapp.com/jokes/programming/random";
public JokeService(HttpClient httpClient) => _httpClient = httpClient;
public async Task<string> GetJokeAsync()
{
try
{
// The API returns an array with a single entry.
Joke[]? jokes = await _httpClient.GetFromJsonAsync<Joke[]>(
JokeApiUrl, _options);
Joke? joke = jokes?[0];
return joke is not null
? $"{joke.Setup}{Environment.NewLine}{joke.Punchline}"
: "No joke here...";
}
catch (Exception ex)
{
return $"That's not funny! {ex}";
}
}
}
public record Joke(int Id, string Type, string Setup, string Punchline);
El código fuente del servicio de chistes anterior expone una única funcionalidad: el método GetJokeAsync. Se trata de un método de devolución Task<TResult>, donde T es string, y representa un chiste sobre programación aleatorio. HttpClient se inserta en el constructor y se asigna a una variable _httpClient de ámbito de clase.
Sugerencia
La API de chistes es de un proyecto de código abierto de GitHub. Esta se usa con fines de demostración y, por tanto, no garantizamos que esté disponible en el futuro. Para probar rápidamente la API, abra la siguiente dirección URL en un explorador:
https://karljoke.herokuapp.com/jokes/programming/random.
Reescritura de la clase Worker
Reemplace la clase Worker de la plantilla por el siguiente código de C# y cambie el nombre del archivo a WindowsBackgroundService.cs:
namespace App.WindowsService;
public sealed class WindowsBackgroundService : BackgroundService
{
private readonly JokeService _jokeService;
private readonly ILogger<WindowsBackgroundService> _logger;
public WindowsBackgroundService(
JokeService jokeService,
ILogger<WindowsBackgroundService> logger) =>
(_jokeService, _logger) = (jokeService, logger);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
string joke = await _jokeService.GetJokeAsync();
_logger.LogWarning(joke);
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
En el código anterior, JokeService se inserta junto con ILogger. Ambos están disponibles para la clase como campos private readonly. En el método ExecuteAsync, el servicio de chistes solicita un chiste y lo escribe en el registrador. En este caso, el registrador se implementa mediante el registro de eventos de Windows Microsoft.Extensions.Logging.EventLog.EventLogLogger. Los registros se escriben en el Visor de eventos y están disponibles en este para visualizarlos.
Nota
De forma predeterminada, la gravedad del registro de eventos es Warning. Aunque esto se puede configurar, por motivos de demostración, WindowsBackgroundService ejecuta los registros con el método de extensión LogWarning. Para dirigirse específicamente al nivel EventLog, agregue una entrada en appsettings.{Environment}.json o proporcione un valor EventLogSettings.Filter.
"Logging": {
"EventLog": {
"LogLevel": {
"Default": "Information"
}
}
}
Para obtener más información sobre cómo configurar niveles de registro, consulte Proveedores de registro en .NET: Configuración del registro de eventos de Windows.
Reescritura de la clase Program
Reemplace el contenido del archivo Program.cs de la plantilla por el siguiente código de C#:
using App.WindowsService;
using IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = ".NET Joke Service";
})
.ConfigureServices(services =>
{
services.AddHostedService<WindowsBackgroundService>();
services.AddHttpClient<JokeService>();
})
.Build();
await host.RunAsync();
El método de extensión UseWindowsService(IHostBuilder) configura la aplicación para que funcione como un servicio de Windows. El nombre del servicio se establece en ".NET Joke Service". El servicio hospedado está registrado, y HttpClient se registra en JokeService para insertar las dependencias.
Para obtener más información sobre el registro de servicios, consulte Inserción de dependencias en .NET.
Publicación de la aplicación
Para crear la aplicación del servicio Worker de .NET como servicio de Windows, se recomienda que la publique como ejecutable de archivo único. Es menos propenso a errores tener un archivo ejecutable independiente, ya que no hay ningún archivo dependiente en el sistema de archivos. No obstante, puede elegir una modalidad de publicación diferente, que es perfectamente aceptable, siempre y cuando cree un archivo *.exe que pueda ser el destino del Administrador de control de servicios de Windows.
Importante
Un enfoque de publicación alternativo consiste en compilar el archivo *.dll (en lugar de *.exe) y, al instalar la aplicación publicada mediante el Administrador de control de servicios de Windows, delegue en la CLI de .NET y pase el archivo DLL. Para obtener más información, consulte el documento sobre la CLI de .NET: comando dotnet.
sc.exe create ".NET Joke Service" binpath="C:\Path\To\dotnet.exe C:\Path\To\App.WindowsService.dll"
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<RootNamespace>App.WindowsService</RootNamespace>
<OutputType>exe</OutputType>
<PublishSingleFile>true</PublishSingleFile>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
</ItemGroup>
</Project>
Las líneas resaltadas anteriores del archivo del proyecto definen los comportamientos siguientes:
<OutputType>exe</OutputType>: crea una aplicación de consola.<PublishSingleFile>true</PublishSingleFile>: habilita la publicación de un solo archivo.<RuntimeIdentifier>win-x64</RuntimeIdentifier>: especifica el RID dewin-x64.<PlatformTarget>x64</PlatformTarget>: especifica la CPU de la plataforma de destino de 64 bits.
Para publicar la aplicación desde Visual Studio, puede crear un perfil de publicación que se conservará. El perfil de publicación está basado en XML y tiene la extensión de archivo .pubxml. Visual Studio usa este perfil para publicar la aplicación implícitamente, mientras que si usa la CLI de .NET —, debe especificar explícitamente el perfil de publicación para que se pueda usar.
Haga clic con el botón derecho en el proyecto, en el Explorador de soluciones, y seleccione Publicar... . A continuación, seleccione Agregar un perfil de publicación para crear un perfil. En el cuadro de diálogo Publicar, seleccione Carpeta como Destino.
Deje el valor predeterminado Ubicación y, a continuación, seleccione Finalizar. Una vez creado el perfil, seleccione Mostrar todas las configuraciones y compruebe la configuración del perfil.
Asegúrese de que se especifican los valores siguientes:
- Modo de implementación: Independiente
- Producir un único archivo: activado
- Habilitar la compilación ReadyToRun: activado
- Quitar los ensamblados no usados (en versión preliminar) : desactivado
Por último, seleccione Publicar. La aplicación se compila y el archivo .exe resultante se publica en el directorio de salida /publish.
Como alternativa, puede usar la CLI de .NET para publicar la aplicación:
dotnet publish --output "C:\custom\publish\directory"
Para más información, consulte dotnet publish.
Creación del servicio de Windows
Para crear el servicio de Windows, use el comando "create" nativo del Administrador de control de servicios (sc.exe) de Windows. Ejecute PowerShell como administrador.
sc.exe create ".NET Joke Service" binpath="C:\Path\To\App.WindowsService.exe"
Sugerencia
Si necesita cambiar la raíz de contenido de la configuración del host, puede pasarla como argumento de la línea de comandos al especificar el valor de binpath:
sc.exe create "Svc Name" binpath="C:\Path\To\App.exe --contentRoot C:\Other\Path"
Verá un mensaje similar al siguiente:
[SC] CreateService SUCCESS
Para obtener más información, consulte Comando create de sc.exe.
Para ver la aplicación creada como un servicio de Windows, abra Servicios. Seleccione la tecla de Windows (o Ctrl + Esc) y busque en "Servicios". Desde la aplicación Servicios, debería poder encontrar el servicio por su nombre.
Comprobación de la funcionalidad del servicio
Para comprobar que el servicio funciona según lo previsto, debe seguir estos pasos:
- Inicie el servicio
- Visualización de los registros
- Detener el servicio
Inicio del servicio de Windows
Para iniciar el servicio de Windows, use el comando sc.exe start:
sc.exe start ".NET Joke Service"
Verá un resultado similar al siguiente:
SERVICE_NAME: .NET Joke Service
TYPE : 10 WIN32_OWN_PROCESS
STATE : 2 START_PENDING
(NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x7d0
PID : 37636
FLAGS
El estado del servicio pasará de START_PENDING a En ejecución.
Ver registros
Para ver los registros, abra el Visor de eventos. Seleccione la tecla de Windows (o Ctrl + Esc) y busque "Event Viewer". Seleccione el nodo Visor de eventos (locales) > Registros de Windows > Aplicación. Debería ver una entrada del nivel Advertencia con un origen que coincide con el espacio de nombres de las aplicaciones. Haga doble clic en la entrada o haga clic con el botón derecho y seleccione Propiedades de evento para ver los detalles.
Después de ver los registros en el registro de eventos, debería detener el servicio, ya que está diseñado para registrar un chiste aleatorio una vez por minuto. Aunque se trata de un comportamiento intencionado, no resulta práctico para los servicios de producción.
Detención del servicio de Windows
Para detener el servicio de Windows, use el comando sc.exe stop:
sc.exe stop ".NET Joke Service"
Verá un resultado similar al siguiente:
SERVICE_NAME: .NET Joke Service
TYPE : 10 WIN32_OWN_PROCESS
STATE : 3 STOP_PENDING
(STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
El estado del servicio pasará de STOP_PENDING a Detenido.
Eliminación del servicio de Windows
Para eliminar el servicio de Windows, use el comando "delete" nativo del Administrador de control de servicios (sc.exe) de Windows. Ejecute PowerShell como administrador.
Importante
Si el servicio no tiene el estado Detenido, no se eliminará inmediatamente. Asegúrese de que el servicio se detenga antes de emitir el comando delete.
sc.exe delete ".NET Joke Service"
Verá un mensaje similar al siguiente:
[SC] DeleteService SUCCESS
Para obtener más información, consulte Comando delete de sc.exe.