Console application vs worker service

¿Host o no host? Esa es la cuestión

¿Cuándo elegir una u otra? ¿En qué se diferencian? ¿Cómo crear una aplicación de consola y no sentir que estás solo frente al peligro?

File > New > Project > Console App es algo que sale de carrerilla. Sin embargo, entre el uso de Top-level statements, Global using e Implicit using nuestro código parece un yermo.

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

Se agradece hasta el comentario que por lo menos hace bulto.

Si queremos rizar el rizo, podemos añadir un fichero Usings.cs (el nombre no es importante) con global using static System.Console; y ahora nuestra aplicación de consola podría ser incluso más parca.

WriteLine("Hello, World!");

Bueno, hasta aquí era un poco preparar el terreno para hacer la pregunta clave ¿dónde están los servicios comunes de .NET? Inyección de dependencias, configuración, logging, etc. Porque yo sin eso no salgo de casa y no soy el único.

Claramente me interesa el tema porque ya escribí sobre ello hace muchos años en el blog antiguo. No obstante, como ha cambiado el cómo resoverlo… voy a repetirme como el ajo.

Los servicios comunes se pueden añadir manualmente o a través de un host.

A mano e instalando los siguientes paquetes, sería algo así:

  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration.Json
  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Logging.Console
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

var services = new ServiceCollection();
services
    .AddTransient<IGreeter, Greeter>()
    .AddTransient<IConfiguration>(_ => configuration)
    .AddLogging(configure => configure.AddConsole());
var provider = services.BuildServiceProvider();

var greeter = provider.GetRequiredService<IGreeter>();
greeter.Greet("Sergio");

public class Greeter(IConfiguration configuration, ILogger<Greeter> logger) : IGreeter
{
    public void Greet(string name)
    {        
        var message = "Hello {name}";
        if (Convert.ToBoolean(configuration["UpperCase"]))
        {
            message = message.ToUpper();
        }
        logger.LogInformation(message, name);
    }
}

public interface IGreeter
{
    void Greet(string name);
}

Recuerda configurar el Build Action a Content y el Copy to Output Directory a Copy if Newer para el fichero appsettings.json.

<ItemGroup>
    <Content Include="appsettings.json">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
</ItemGroup>

Si por el contrario no te apetece hacer esto, puedes optar por crear un worker service en vez de una Console App.

Ahora, el fichero Program.cs está más curioso.

// Program.cs
using WorkerService1;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

var host = builder.Build();
host.Run();

Además tenemos un nuevo fichero Worker.cs con una clase que hereda de BackgroundService.

Lo importante es que ahora se crea un host que ya incluye todo lo necesario (incluso más si te pones tiquismiquis). CreateApplicationBuilder hará su magia.

El ejemplo anterior (de Console App) se convertiría en esto:

var builder = Host.CreateApplicationBuilder(args);
builder.Services
    .AddTransient<IGreeter, Greeter>()
    .AddHostedService<Worker>();

var host = builder.Build();
host.Run();

public class Greeter(IConfiguration configuration, ILogger<Greeter> logger) : IGreeter
{
    public void Greet(string name)
    {
        var message = "Hello {name}";
        if (Convert.ToBoolean(configuration["UpperCase"]))
        {
            message = message.ToUpper();
        }
        logger.LogInformation(message, name);
    }
}

public interface IGreeter
{
    void Greet(string name);
}

public class Worker(IGreeter greeter, IHostApplicationLifetime hostApplicationLifetime) : BackgroundService
{
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        greeter.Greet("Sergio");
        hostApplicationLifetime.StopApplication();
        return Task.CompletedTask;
    }
}

Un saludo!


Ver también