Usar el host genérico de .NET en una aplicación WPF

El host genérico de .NET proporciona una manera estandarizada de configurar y ejecutar aplicaciones con compatibilidad integrada con inserción de dependencias (DI), configuración y registro. Las aplicaciones wpF no incluyen la integración de Host Builder de forma predeterminada, pero puede agregarla. En este artículo se muestra cómo configurar el host genérico en una aplicación WPF para insertar servicios en las ventanas y usar la infraestructura de hospedaje completa de .NET.

Prerrequisitos

Configuración del host genérico

Para integrar el host genérico con la aplicación WPF:

  1. Quite StartupUri del archivo XAML de la aplicación y conecte los controladores de eventos Startup y Exit.

    <Application x:Class="HostBuilderApp.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 Startup="Application_Startup"
                 Exit="Application_Exit">
        <Application.Resources>
        </Application.Resources>
    </Application>
    
  2. Compile el host en el código subyacente.

    El Application_Startup método crea el host con CreateApplicationBuilder, registra los servicios en la Services propiedad , inicia el host y muestra la ventana principal:

    private async void Application_Startup(object sender, StartupEventArgs e)
    {
        var builder = Host.CreateApplicationBuilder();
    
        builder.Services.AddHostedService<SampleLifecycleService>();
        builder.Services.AddSingleton<IGreetingService, GreetingService>();
        builder.Services.AddSingleton<MainWindow>();
    
        _host = builder.Build();
    
        await _host.StartAsync();
    
        MainWindow mainWindow = _host.Services.GetRequiredService<MainWindow>();
        mainWindow.Show();
    }
    
    Private Async Sub Application_Startup(sender As Object, e As StartupEventArgs)
        Dim builder = Host.CreateApplicationBuilder()
    
        builder.Services.AddHostedService(Of SampleLifecycleService)()
        builder.Services.AddSingleton(Of IGreetingService, GreetingService)()
        builder.Services.AddSingleton(Of MainWindow)()
    
        _host = builder.Build()
    
        Await _host.StartAsync()
    
        Dim mainWindow As MainWindow = _host.Services.GetRequiredService(Of MainWindow)()
        mainWindow.Show()
    End Sub
    
  3. Detenga y elimine el host cuando la aplicación salga para limpiar los recursos:

    private async void Application_Exit(object sender, ExitEventArgs e)
    {
        using (_host)
        {
            await _host.StopAsync();
        }
    }
    
    Private Async Sub Application_Exit(sender As Object, e As ExitEventArgs)
        Using _host
            Await _host.StopAsync()
        End Using
    End Sub
    

Crear un servicio

La Services propiedad de la sección anterior registra los servicios con el contenedor de inserción de dependencias. Para crear un servicio personalizado:

  1. Definir una interfaz de servicio:

    public interface IGreetingService
    {
        string GetGreeting();
    }
    
    Public Interface IGreetingService
    
        Function GetGreeting() As String
    
    End Interface
    
  2. Cree una clase que implemente la interfaz . La GreetingService clase acepta IConfiguration a través de la inserción de constructores para leer valores de appsettings.json:

    public class GreetingService(IConfiguration configuration) : IGreetingService
    {
        public string GetGreeting() =>
            configuration.GetValue<string>("GreetingMessage")
            ?? "Hello from the Generic Host!";
    }
    
    Public Class GreetingService
        Implements IGreetingService
    
        Private ReadOnly _configuration As IConfiguration
    
        Public Sub New(configuration As IConfiguration)
            _configuration = configuration
        End Sub
    
        Public Function GetGreeting() As String Implements IGreetingService.GetGreeting
            Dim message As String = _configuration.GetValue(Of String)("GreetingMessage")
    
            If String.IsNullOrEmpty(message) Then
                Return "Hello from the Generic Host!"
            End If
    
            Return message
        End Function
    
    End Class
    

Ejecución de un servicio hospedado

El host genérico también puede ejecutar servicios en segundo plano que participan en el ciclo de vida de la aplicación. El host llama a una IHostedService implementación cuando se inicia y se detiene. Para agregar un servicio hospedado:

  1. Cree una clase que implemente IHostedService. La siguiente clase escribe en la salida de depuración cuando el host se inicia y detiene:

    public class SampleLifecycleService : IHostedService
    {
        public Task StartAsync(CancellationToken cancellationToken)
        {
            System.Diagnostics.Debug.WriteLine("SampleLifecycleService: Started.");
            return Task.CompletedTask;
        }
    
        public Task StopAsync(CancellationToken cancellationToken)
        {
            System.Diagnostics.Debug.WriteLine("SampleLifecycleService: Stopped.");
            return Task.CompletedTask;
        }
    }
    
    Public Class SampleLifecycleService
        Implements IHostedService
    
        Public Function StartAsync(cancellationToken As CancellationToken) As Task Implements IHostedService.StartAsync
            System.Diagnostics.Debug.WriteLine("SampleLifecycleService: Started.")
            Return Task.CompletedTask
        End Function
    
        Public Function StopAsync(cancellationToken As CancellationToken) As Task Implements IHostedService.StopAsync
            System.Diagnostics.Debug.WriteLine("SampleLifecycleService: Stopped.")
            Return Task.CompletedTask
        End Function
    
    End Class
    
  2. Registre el servicio con AddHostedService en la propiedad Services del generador, como se muestra en el método Application_Startup de la sección Configurar el Host Genérico.

El host llama StartAsync al iniciar y StopAsync al detener, por lo que la salida de depuración aparece en la ventana Salida de Visual Studio.

Inserción de servicios en una ventana

El contenedor de inserción de dependencias inserta dependencias en ventanas registradas a través de la inserción de constructores. Para consumir servicios en una ventana:

  1. Agregue parámetros de constructor para cada servicio que necesite la ventana.
  2. Almacene los servicios inyectados en campos privados.
  3. Usa los servicios en manejadores de eventos u otros métodos.

El código siguiente muestra cómo MainWindow acepta ILogger<MainWindow> y IGreetingService a través de su constructor.

public partial class MainWindow : Window
{
    private readonly ILogger<MainWindow> _logger;
    private readonly IGreetingService _greetingService;

    public MainWindow(ILogger<MainWindow> logger, IGreetingService greetingService)
    {
        InitializeComponent();

        _logger = logger;
        _greetingService = greetingService;
    }

    private void OnGetGreetingClick(object sender, RoutedEventArgs e)
    {
        _logger.LogInformation("Get Greeting button clicked.");
        GreetingText.Text = _greetingService.GetGreeting();
    }
}
Class MainWindow

    Private ReadOnly _logger As ILogger(Of MainWindow)
    Private ReadOnly _greetingService As IGreetingService

    Public Sub New(logger As ILogger(Of MainWindow), greetingService As IGreetingService)
        InitializeComponent()

        _logger = logger
        _greetingService = greetingService
    End Sub

    Private Sub OnGetGreetingClick(sender As Object, e As RoutedEventArgs)
        _logger.LogInformation("Get Greeting button clicked.")
        GreetingText.Text = _greetingService.GetGreeting()
    End Sub

End Class

Agregar configuración

CreateApplicationBuilder se carga appsettings.json automáticamente cuando el archivo está en el directorio de salida. Para agregar un archivo de configuración al proyecto:

  1. Cree un appsettings.json archivo en la raíz del proyecto con los valores de configuración:

    {
      "GreetingMessage": "Hello from the .NET Generic Host!"
    }
    
  2. Establecer CopyToOutputDirectory como PreserveNewest en el archivo del proyecto para que el archivo se copie en el directorio de salida:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net10.0-windows</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <UseWPF>true</UseWPF>
      </PropertyGroup>
    
      <ItemGroup>
        <None Update="appsettings.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
      </ItemGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.*" />
      </ItemGroup>
    
    </Project>