Leer en inglés

Compartir a través de


Ioc (inversión de control)

Un patrón común que se puede usar para aumentar la modularidad en el código base de una aplicación mediante el patrón MVVM es usar alguna forma de inversión de control. Una de las soluciones más comunes, en particular, es usar la inserción de dependencias, que consiste en crear una serie de servicios que se inyectan en las clases del back-end (es decir, se pasan como parámetros a los constructores del modelo de vista). Esto permite que el código que usa estos servicios no dependa de los detalles de implementación de los mismos, y también facilita el intercambio de las implementaciones concretas de estos servicios. Este patrón también facilita que las características específicas de la plataforma estén disponibles para el código back-end, abstrayéndolas a través de un servicio que, a continuación, se inserta cuando sea necesario.

El kit de herramientas de MVVM no proporciona API integradas para facilitar la utilización de este patrón, puesto que ya existen bibliotecas dedicadas específicamente a ello, como el paquete Microsoft.Extensions.DependencyInjection, que proporciona un conjunto de API de inserción de dependencias completo y potente, y actúa como un IServiceProvider fácil de configurar y usar. La siguiente guía hará referencia a esta biblioteca y proporcionará una serie de ejemplos de cómo integrarla en aplicaciones mediante el patrón de MVVM.

API de la plataforma: Ioc

Configuración y resolución de servicios

El primer paso es declarar una instancia de IServiceProvider e inicializar todos los servicios necesarios, normalmente en el inicio. Por ejemplo, en UWP (pero también se puede usar una configuración similar en otros marcos):

public sealed partial class App : Application
{
    public App()
    {
        Services = ConfigureServices();

        this.InitializeComponent();
    }

    /// <summary>
    /// Gets the current <see cref="App"/> instance in use
    /// </summary>
    public new static App Current => (App)Application.Current;

    /// <summary>
    /// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
    /// </summary>
    public IServiceProvider Services { get; }

    /// <summary>
    /// Configures the services for the application.
    /// </summary>
    private static IServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();

        services.AddSingleton<IFilesService, FilesService>();
        services.AddSingleton<ISettingsService, SettingsService>();
        services.AddSingleton<IClipboardService, ClipboardService>();
        services.AddSingleton<IShareService, ShareService>();
        services.AddSingleton<IEmailService, EmailService>();

        return services.BuildServiceProvider();
    }
}

Aquí se inicializa la propiedad Services en el inicio y se registran todos los servicios de aplicación y los modelos de vista. También hay una nueva propiedad Current que se puede usar para acceder fácilmente a la propiedad Services desde otras vistas de la aplicación. Por ejemplo:

IFilesService filesService = App.Current.Services.GetService<IFilesService>();

// Use the files service here...

El aspecto clave aquí es que cada servicio puede perfectamente estar usando API específicas de la plataforma, pero como todas ellas están abstraídas a través de la interfaz que está usando nuestro código, no necesitamos preocuparnos por ellas cuando solo estamos resolviendo una instancia y usándola para realizar operaciones.

Inserción de constructores

Una potente característica disponible es la "inserción de constructores", lo que significa que el proveedor de servicios de DI es capaz de resolver automáticamente las dependencias indirectas entre los servicios registrados al crear instancias del tipo que se está solicitando. Considere el siguiente servicio:

public class FileLogger : IFileLogger
{
    private readonly IFilesService FileService;
    private readonly IConsoleService ConsoleService;

    public FileLogger(
        IFilesService fileService,
        IConsoleService consoleService)
    {
        FileService = fileService;
        ConsoleService = consoleService;
    }

    // Methods for the IFileLogger interface here...
}

Aquí tenemos un tipo FileLogger que implementa la interfaz IFileLogger y requiere instancias de IFilesService y IConsoleService. La inserción de constructores significa que el proveedor de servicios de DIs recopilará automáticamente todos los servicios necesarios, de la siguiente manera:

/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
    var services = new ServiceCollection();

    services.AddSingleton<IFilesService, FilesService>();
    services.AddSingleton<IConsoleService, ConsoleService>();
    services.AddSingleton<IFileLogger, FileLogger>();

    return services.BuildServiceProvider();
}

// Retrieve a logger service with constructor injection
IFileLogger fileLogger = App.Current.Services.GetService<IFileLogger>();

El proveedor de servicios de DIcomprobará automáticamente si todos los servicios necesarios están registrados, los recuperará e invocará el constructor para el tipo concreto registrado IFileLogger, para obtener la instancia que se va a devolver.

¿Qué ocurre con los modelos de vista?

Un proveedor de servicios tiene "servicio" en su nombre, pero realmente se puede usar para resolver instancias de cualquier clase, incluidos los modelos de vista. Se siguen aplicando los mismos conceptos explicados anteriormente, incluida la inserción de constructores. Imaginemos que tenemos un tipo ContactsViewModel, usando una instancia de IContactsService y otra de IPhoneService a través de su constructor. Podríamos tener un método ConfigureServices similar al siguiente:

/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
    var services = new ServiceCollection();

    // Services
    services.AddSingleton<IContactsService, ContactsService>();
    services.AddSingleton<IPhoneService, PhoneService>();

    // Viewmodels
    services.AddTransient<ContactsViewModel>();

    return services.BuildServiceProvider();
}

Y, después, en nuestra ContactsView, asignaríamos el contexto de datos de la siguiente manera:

public ContactsView()
{
    this.InitializeComponent();
    this.DataContext = App.Current.Services.GetService<ContactsViewModel>();
}

Más documentos

Para más información sobre Microsoft.Extensions.DependencyInjection, vaya aquí.

Ejemplos

  • Consulte la aplicación de ejemplo (para varios marcos de interfaz de usuario) para ver el kit de herramientas de MVVM en acción.
  • También puede encontrar más ejemplos en las pruebas unitarias.