Inserción de dependencia
La interfaz de usuario de aplicaciones multiplataforma de .NET (.NET MAUI) proporciona compatibilidad integrada para usar la inserción de dependencias. La inserción de dependencias es una versión especializada del patrón Inversion of Control (IoC), donde la preocupación que se invierte es el proceso de obtener la dependencia necesaria. Con la inserción de dependencias, otra clase es responsable de insertar dependencias en un objeto en tiempo de ejecución.
Normalmente, se invoca un constructor de clase al crear una instancia de un objeto, y los valores que el objeto necesita se pasan como argumentos al constructor. Este es un ejemplo de inserción de dependencias conocida como inserción de constructores. Las dependencias que necesita el objeto se insertan en el constructor.
Nota:
También hay otros tipos de inserción de dependencias, como la inserción de establecedores de propiedades y la inserción de llamadas de método, pero se usan con menos frecuencia.
Al especificar dependencias como tipos de interfaz, la inserción de dependencias permite desacoplar los tipos concretos del código que depende de estos tipos. Por lo general, usa un contenedor que contiene una lista de registros y asignaciones entre interfaces y tipos abstractos, y los tipos concretos que implementan o extienden estos tipos.
Contenedores de inserción de dependencias
Si una clase no crea instancias directamente de los objetos que necesita, otra clase debe asumir esta responsabilidad. Considere el ejemplo siguiente, que muestra una clase de modelo de vista que requiere argumentos de constructor:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
{
_loggingService = loggingService;
_settingsService = settingsService;
}
}
En este ejemplo, el MainPageViewModel
constructor requiere dos instancias de objeto de interfaz como argumentos insertados por otra clase. La única dependencia de la clase MainPageViewModel
es de los tipos de interfaz. Por lo tanto, la clase MainPageViewModel
no tiene ningún conocimiento de la clase responsable de crear instancias de los objetos de interfaz.
Del mismo modo, considere el ejemplo siguiente que muestra una clase de página que requiere un argumento de constructor:
public MainPage(MainPageViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
En este ejemplo, el MainPage
constructor requiere un tipo concreto como argumento insertado por otra clase. La única dependencia de la MainPage
clase está en el MainPageViewModel
tipo . Por lo tanto, la MainPage
clase no tiene ningún conocimiento de la clase responsable de crear instancias del tipo concreto.
En ambos casos, la clase responsable de crear instancias de las dependencias e insertarlas en la clase dependiente se conoce como contenedor de inserción de dependencias.
Los contenedores de inserción de dependencias reducen el acoplamiento entre objetos proporcionando un recurso para crear instancias de clase y administrar su duración en función de la configuración del contenedor. Durante la creación de objetos, el contenedor inserta todas las dependencias que requiera el objeto. Si no se han creado esas dependencias, el contenedor crea y resuelve primero sus dependencias.
El uso de un contenedor de inserción de dependencias ofrece varias ventajas:
- Un contenedor elimina la necesidad de que una clase localice sus dependencias y administre sus duraciones.
- Un contenedor permite la asignación de dependencias implementadas sin afectar a la clase.
- Un contenedor facilita la comprobación, ya que permite simular las dependencias.
- Un contenedor aumenta la capacidad de mantenimiento al permitir que las nuevas clases se agreguen fácilmente a la aplicación.
En el contexto de una aplicación MAUI de .NET que usa el patrón Model-View-View-ViewModel (MVVM), normalmente se usará un contenedor de inserción de dependencias para registrar y resolver vistas, registrar y resolver modelos de vista, así como para registrar servicios e insertarlos en modelos de vista. Para obtener más información sobre el patrón MVVM, consulte Model-View-ViewModel (MVVM).
Hay muchos contenedores de inserción de dependencias disponibles para .NET. .NET MAUI tiene compatibilidad integrada para usar Microsoft.Extensions.DependencyInjection para administrar la creación de instancias de vistas, modelos de vista y clases de servicio en una aplicación. Microsoft.Extensions.DependencyInjection facilita la creación de aplicaciones de acoplamiento flexible y proporciona todas las características que se encuentran normalmente en contenedores de inserción de dependencias, incluidos métodos para registrar asignaciones de tipos e instancias de objeto, resolver objetos, administrar duraciones de objetos e insertar objetos dependientes en constructores de objetos que resuelve. Para más información sobre Microsoft.Extensions.DependencyInjection, vea Inserción de dependencias en .NET.
En tiempo de ejecución, el contenedor debe saber qué implementación de las dependencias se solicitan para crear instancias de ellos para los objetos solicitados. En el ejemplo anterior, las ILoggingService
interfaces y ISettingsService
deben resolverse antes de que se pueda crear una instancia del MainPageViewModel
objeto. Esto implica que el contenedor realice las siguientes acciones:
- Decidir cómo crear una instancia de un objeto que implementa la interfaz. Esto se conoce como registro. Para más información, consulte Registro.
- Crear instancias del objeto que implementa la interfaz necesaria y el objeto
MainPageViewModel
. Esto se conoce como resolución. Para obtener más información, consulte Resolución.
Finalmente, una aplicación terminará de usar el MainPageViewModel
objeto y estará disponible para la recolección de elementos no utilizados. En este momento, el recolector de elementos no utilizados debe eliminar cualquier implementación de interfaz de corta duración si otras clases no comparten las mismas instancias.
Registro
Antes de que las dependencias se puedan insertar en un objeto, los tipos de las dependencias deben registrarse primero en el contenedor. El registro de un tipo normalmente implica pasar el contenedor un tipo concreto, o una interfaz y un tipo concreto que implementa la interfaz.
Hay dos enfoques principales para registrar tipos y objetos con el contenedor:
- Registre un tipo o una asignación en el contenedor. Esto se conoce como registro transitorio. Cuando sea necesario, el contenedor compilará una instancia del tipo especificado.
- Registre un objeto existente en el contenedor como singleton. Cuando sea necesario, el contenedor devolverá una referencia al objeto existente.
Precaución
Los contenedores de inserción de dependencias no siempre son adecuados para una aplicación MAUI de .NET. La inserción de dependencias presenta complejidad y requisitos adicionales que podrían no ser adecuados o útiles para aplicaciones más pequeñas. Si una clase no tiene ninguna dependencia o no es una dependencia para otros tipos, es posible que no tenga sentido colocarla en el contenedor. Además, si una clase tiene un único conjunto de dependencias que son integrales al tipo y nunca cambiará, es posible que no tenga sentido colocarlas en el contenedor.
El registro de tipos que requieren inserción de dependencias debe realizarse en un único método de la aplicación. Este método debe invocarse al principio del ciclo de vida de la aplicación para asegurarse de que conoce las dependencias entre sus clases. Las aplicaciones normalmente deben realizar esto en el CreateMauiApp
método de la MauiProgram
clase . La MauiProgram
clase llama al CreateMauiApp
método para crear un MauiAppBuilder objeto . El MauiAppBuilder objeto tiene una Services propiedad de tipo IServiceCollection, que proporciona un lugar para registrar los tipos, como vistas, modelos de vista y servicios para la inserción de dependencias:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Services.AddTransient<ILoggingService, LoggingService>();
builder.Services.AddTransient<ISettingsService, SettingsService>();
builder.Services.AddSingleton<MainPageViewModel>();
builder.Services.AddSingleton<MainPage>();
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
Los tipos registrados con la Services propiedad se proporcionan al contenedor de inserción de dependencias cuando MauiAppBuilder.Build() se llama a .
Al registrar dependencias, debe registrar todas las dependencias, incluidos los tipos que requieran las dependencias. Por lo tanto, si tiene un modelo de vista que toma una dependencia como parámetro de constructor, debe registrar el modelo de vista junto con todas sus dependencias. Del mismo modo, si tiene una vista que toma una dependencia de modelo de vista como parámetro de constructor, debe registrar la vista y el modelo de vista junto con todas sus dependencias.
Sugerencia
Un contenedor de inserción de dependencias es ideal para crear instancias de modelo de vista. Si un modelo de vista tiene dependencias, administrará la creación e inserción de los servicios necesarios. Solo tiene que asegurarse de registrar los modelos de vista y las dependencias que puedan tener en el CreateMauiApp
método de la MauiProgram
clase .
Duración de dependencias
En función de las necesidades de la aplicación, es posible que deba registrar dependencias con distintas duraciones. En la tabla siguiente se enumeran los métodos principales que puede usar para registrar dependencias y sus duraciones de registro:
Method | Descripción |
---|---|
AddSingleton<T> |
Crea una única instancia del objeto que permanecerá durante la vigencia de la aplicación. |
AddTransient<T> |
Crea una nueva instancia del objeto cuando se solicita durante la resolución. Los objetos transitorios no tienen una duración predefinida, pero normalmente durarán lo mismo que su host. |
AddScoped<T> |
Crea una instancia del objeto que comparte la duración de su host. Cuando el host sale del ámbito, así que hace su dependencia. Por lo tanto, resolver la misma dependencia varias veces dentro del mismo ámbito produce la misma instancia, mientras que la resolución de la misma dependencia en ámbitos diferentes producirá instancias diferentes. |
Nota:
Si un objeto no hereda de una interfaz, como una vista o un modelo de vista, solo debe proporcionarse su tipo concreto al AddSingleton<T>
método , AddTransient<T>
o AddScoped<T>
.
La MainPageViewModel
clase se usa cerca de la raíz de la aplicación y siempre debe estar disponible, por lo que registrarla con AddSingleton<T>
es beneficiosa. Es posible que otros modelos de vista se naveguen o se usen más adelante en una aplicación. Si tiene un tipo que podría no usarse siempre, o si es memoria o un uso intensivo de cálculo o requiere datos Just-In-Time, puede ser un mejor candidato para AddTransient<T>
el registro.
Otra manera común de registrar dependencias es usar los AddSingleton<TService, TImplementation>
métodos , AddTransient<TService, TImplementation>
o AddScoped<TService, TImplementation>
. Estos métodos toman dos tipos: la definición de interfaz y la implementación concreta. Este tipo de registro es más conveniente para los casos en los que se implementan servicios basados en interfaces.
Una vez registrados todos los tipos, MauiAppBuilder.Build() se debe llamar a para crear el MauiApp objeto y rellenar el contenedor de inserción de dependencias con todos los tipos registrados.
Importante
Una vez MauiAppBuilder.Build() que se haya llamado a , los tipos registrados con el contenedor de inserción de dependencias serán inmutables y ya no se pueden actualizar ni modificar.
Registro de dependencias con un método de extensión
El MauiApp.CreateBuilder método crea un MauiAppBuilder objeto que se puede usar para registrar dependencias. Si la aplicación necesita registrar muchas dependencias, puede crear métodos de extensión para ayudar a proporcionar un flujo de trabajo de registro organizado y fácil de mantener:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
=> MauiApp.CreateBuilder()
.UseMauiApp<App>()
.RegisterServices()
.RegisterViewModels()
.RegisterViews()
.Build();
public static MauiAppBuilder RegisterServices(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddTransient<ILoggingService, LoggingService>();
mauiAppBuilder.Services.AddTransient<ISettingsService, SettingsService>();
// More services registered here.
return mauiAppBuilder;
}
public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddSingleton<MainPageViewModel>();
// More view-models registered here.
return mauiAppBuilder;
}
public static MauiAppBuilder RegisterViews(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddSingleton<MainPage>();
// More views registered here.
return mauiAppBuilder;
}
}
En este ejemplo, los tres métodos de extensión de registro usan la MauiAppBuilder instancia para acceder a la Services propiedad para registrar dependencias.
Solución
Una vez registrado un tipo, se puede resolver o insertar como una dependencia. Cuando se resuelve un tipo y el contenedor debe crear una instancia, inserta todas las dependencias en la instancia.
Por lo general, cuando se resuelve un tipo, se produce uno de los tres escenarios:
- Si el tipo no se ha registrado, el contenedor produce una excepción.
- Si el tipo se ha registrado como singleton, el contenedor devuelve la instancia singleton. Si es la primera vez que se llama al tipo, el contenedor lo crea si es necesario y mantiene una referencia a él.
- Si el tipo se ha registrado como transitorio, el contenedor devuelve una nueva instancia y no mantiene una referencia a él.
.NET MAUI admite la resolución de dependencias automática y explícita . La resolución automática de dependencias usa la inserción de constructores sin solicitar explícitamente la dependencia del contenedor. La resolución de dependencias explícita se produce a petición solicitando explícitamente una dependencia del contenedor.
Resolución automática de dependencias
La resolución automática de dependencias se produce en las aplicaciones que usan el shell de MAUI de .NET, siempre que haya registrado el tipo de dependencia y el tipo que usa la dependencia con el contenedor de inserción de dependencias.
Durante la navegación basada en Shell, .NET MAUI buscará registros de página y, si se encuentra alguno, creará esa página e insertará dependencias en su constructor:
public MainPage(MainPageViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
En este ejemplo, el MainPage
constructor recibe una MainPageViewModel
instancia que se inserta. A su vez, la MainPageViewModel
instancia tiene ILoggingService
y ISettingsService
las instancias insertadas:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
{
_loggingService = loggingService;
_settingsService = settingsService;
}
}
Además, en una aplicación basada en Shell, .NET MAUI insertará dependencias en páginas de detalles registradas con el Routing.RegisterRoute método .
Resolución de dependencias explícita
Una aplicación basada en Shell no puede usar la inserción de constructores cuando un tipo solo expone un constructor sin parámetros. Como alternativa, si la aplicación no usa Shell, deberá usar la resolución de dependencias explícita.
Se puede acceder explícitamente al contenedor de inserción de dependencias desde a Element través de su Handler.MauiContext.Service
propiedad , que es de tipo IServiceProvider:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
HandlerChanged += OnHandlerChanged;
}
void OnHandlerChanged(object sender, EventArgs e)
{
BindingContext = Handler.MauiContext.Services.GetService<MainPageViewModel>();
}
}
Este enfoque puede ser útil si necesita resolver una dependencia desde o Elementdesde fuera del constructor de .Element En este ejemplo, el acceso al contenedor de inserción de dependencias en el HandlerChanged
controlador de eventos garantiza que se ha establecido un controlador para la página y, por tanto, que la Handler
propiedad no será null
.
Advertencia
La Handler
propiedad de puede Element
ser null
, por lo que debe tener en cuenta esta situación. Para obtener más información, consulte Ciclo de vida del controlador.
En un modelo de vista, se puede acceder explícitamente al contenedor de inserción de dependencias a través de la Handler.MauiContext.Service
propiedad de Application.Current.MainPage
:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel()
{
_loggingService = Application.Current.MainPage.Handler.MauiContext.Services.GetService<ILoggingService>();
_settingsService = Application.Current.MainPage.Handler.MauiContext.Services.GetService<ISettingsService>();
}
}
Un inconveniente de este enfoque es que el modelo de vista ahora tiene una dependencia del Application tipo. Sin embargo, este inconveniente se puede eliminar pasando un IServiceProvider argumento al constructor de modelo de vista. IServiceProvider se resuelve a través de la resolución automática de dependencias sin tener que registrarla en el contenedor de inserción de dependencias. Con este enfoque se puede resolver automáticamente un tipo y su IServiceProvider dependencia siempre que el tipo se registre en el contenedor de inserción de dependencias. IServiceProvider Después, se puede usar para la resolución de dependencias explícita:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel(IServiceProvider serviceProvider)
{
_loggingService = serviceProvider.GetService<ILoggingService>();
_settingsService = serviceProvider.GetService<ISettingsService>();
}
}
Además, se puede acceder a una IServiceProvider instancia a través de las siguientes propiedades nativas:
- Android-
MauiApplication.Current.Services
- iOS y Mac Catalyst -
MauiUIApplicationDelegate.Current.Services
- Windows:
MauiWinUIApplication.Current.Services
Limitaciones con recursos XAML
Un escenario común es registrar una página con el contenedor de inserción de dependencias y usar la resolución automática de dependencias para insertarla en el App
constructor y establecerla como el valor de la MainPage
propiedad :
public App(MyFirstAppPage page)
{
InitializeComponent();
MainPage = page;
}
Sin embargo, en este escenario si MyFirstAppPage
intenta tener acceso a un StaticResource
que se ha declarado en XAML en el App
diccionario de recursos, se producirá un XamlParseException elemento con un mensaje similar a Position {row}:{column}. StaticResource not found for key {key}
. Esto ocurre porque la página resuelta a través de la inserción de constructores se ha creado antes de inicializar los recursos XAML de nivel de aplicación.
Una solución alternativa para este problema es insertar un elemento IServiceProvider en la App
clase y, a continuación, usarlo para resolver la página dentro de la App
clase :
public App(IServiceProvider serviceProvider)
{
InitializeComponent();
MainPage = serviceProvider.GetService<MyFirstAppPage>();
}
Este enfoque obliga a crear e inicializar el árbol de objetos XAML antes de que se resuelva la página.
Comentarios
https://aka.ms/ContentUserFeedback.
Próximamente: A lo largo de 2024 iremos eliminando gradualmente GitHub Issues como mecanismo de comentarios sobre el contenido y lo sustituiremos por un nuevo sistema de comentarios. Para más información, vea:Enviar y ver comentarios de