Resolución de dependencias Xamarin.Forms
En este artículo se explica cómo insertar un método de resolución de dependencias en Xamarin.Forms para que el contenedor de inserción de dependencias de una aplicación tenga control sobre la creación y duración de los representadores personalizados, los efectos y las implementaciones de DependencyService. Los ejemplos de código de este artículo se toman del ejemplo De resolución de dependencias mediante contenedores .
En el contexto de una Xamarin.Forms aplicación que usa el patrón Model-View-ViewModel (MVVM), se puede usar un contenedor de inserción de dependencias para registrar y resolver modelos de vista, y para registrar servicios e insertarlos en modelos de vista. Durante la creación del modelo de vista, el contenedor inserta las dependencias necesarias. Si no se han creado esas dependencias, el contenedor crea y resuelve primero las dependencias. Para obtener más información sobre la inserción de dependencias, incluidos ejemplos de inserción de dependencias en modelos de vista, consulte Inserción de dependencias.
Tradicionalmente, el control sobre la creación y duración de los tipos en proyectos de plataforma se realiza mediante Xamarin.Forms, que usa el Activator.CreateInstance
método para crear instancias de representadores, efectos e DependencyService
implementaciones personalizados. Desafortunadamente, esto limita el control del desarrollador sobre la creación y duración de estos tipos, y la capacidad de insertar dependencias en ellos. Este comportamiento se puede cambiar insertando un método de resolución de dependencias en Xamarin.Forms que controla cómo se crearán los tipos, ya sea mediante el contenedor de inserción de dependencias de la aplicación o .Xamarin.Forms Sin embargo, tenga en cuenta que no hay ningún requisito para insertar un método de resolución de dependencias en Xamarin.Forms. Xamarin.Forms seguirán generando y administrando la duración de los tipos en proyectos de plataforma si no se inserta un método de resolución de dependencias.
Nota
Aunque este artículo se centra en insertar un método de resolución de dependencias en Xamarin.Forms que resuelve los tipos registrados mediante un contenedor de inserción de dependencias, también es posible insertar un método de resolución de dependencias que use métodos de fábrica para resolver los tipos registrados. Para obtener más información, consulte el ejemplo De resolución de dependencias mediante métodos de fábrica .
Inserción de un método de resolución de dependencias
La DependencyResolver
clase proporciona la capacidad de insertar un método de resolución de dependencias en Xamarin.Forms, mediante el ResolveUsing
método . A continuación, cuando Xamarin.Forms necesita una instancia de un tipo determinado, el método de resolución de dependencias tiene la oportunidad de proporcionar la instancia. Si el método de resolución de dependencias devuelve null
para un tipo solicitado, Xamarin.Forms recurre a intentar crear la propia instancia de tipo mediante el Activator.CreateInstance
método .
En el ejemplo siguiente se muestra cómo establecer el método de resolución de dependencias con el ResolveUsing
método :
using Autofac;
using Xamarin.Forms.Internals;
...
public partial class App : Application
{
// IContainer and ContainerBuilder are provided by Autofac
static IContainer container;
static readonly ContainerBuilder builder = new ContainerBuilder();
public App()
{
...
DependencyResolver.ResolveUsing(type => container.IsRegistered(type) ? container.Resolve(type) : null);
...
}
...
}
En este ejemplo, el método de resolución de dependencias se establece en una expresión lambda que usa el contenedor de inserción de dependencias autofac para resolver cualquier tipo que se haya registrado en el contenedor. De lo contrario, null
se devolverá , lo que dará lugar Xamarin.Forms a un intento de resolver el tipo.
Nota
La API usada por un contenedor de inserción de dependencias es específica del contenedor. Los ejemplos de código de este artículo usan Autofac como contenedor de inserción de dependencias, que proporciona los IContainer
tipos y ContainerBuilder
. Los contenedores alternativos de inserción de dependencias podrían usarse igualmente, pero usarían api diferentes de las que se presentan aquí.
Tenga en cuenta que no hay ningún requisito para establecer el método de resolución de dependencias durante el inicio de la aplicación. Se puede establecer en cualquier momento. La única restricción es que Xamarin.Forms debe conocer el método de resolución de dependencias en el momento en que la aplicación intenta consumir tipos almacenados en el contenedor de inserción de dependencias. Por lo tanto, si hay servicios en el contenedor de inserción de dependencias que la aplicación necesitará durante el inicio, el método de resolución de dependencias tendrá que establecerse al principio del ciclo de vida de la aplicación. Del mismo modo, si el contenedor de inserción de dependencias administra la creación y duración de un determinado Effect
, Xamarin.Forms deberá conocer el método de resolución de dependencias antes de intentar crear una vista que use ese Effect
.
Advertencia
El registro y resolución de tipos con un contenedor de inserción de dependencias tiene un costo de rendimiento debido al uso de reflexión del contenedor para crear cada tipo, especialmente si se reconstruyen las dependencias para cada navegación de página en la aplicación. Si hay muchas dependencias, o estas son muy amplias, el costo de la creación puede aumentar significativamente.
Registrar tipos
Los tipos se deben registrar con el contenedor de inserción de dependencias para poder resolverlos mediante el método de resolución de dependencias. En el ejemplo de código siguiente se muestran los métodos de registro que expone la aplicación de ejemplo en la App
clase para el contenedor de Autofac:
using Autofac;
using Autofac.Core;
...
public partial class App : Application
{
static IContainer container;
static readonly ContainerBuilder builder = new ContainerBuilder();
...
public static void RegisterType<T>() where T : class
{
builder.RegisterType<T>();
}
public static void RegisterType<TInterface, T>() where TInterface : class where T : class, TInterface
{
builder.RegisterType<T>().As<TInterface>();
}
public static void RegisterTypeWithParameters<T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where T : class
{
builder.RegisterType<T>()
.WithParameters(new List<Parameter>()
{
new TypedParameter(param1Type, param1Value),
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
(pi, ctx) => ctx.Resolve(param2Type))
});
}
public static void RegisterTypeWithParameters<TInterface, T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where TInterface : class where T : class, TInterface
{
builder.RegisterType<T>()
.WithParameters(new List<Parameter>()
{
new TypedParameter(param1Type, param1Value),
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
(pi, ctx) => ctx.Resolve(param2Type))
}).As<TInterface>();
}
public static void BuildContainer()
{
container = builder.Build();
}
...
}
Cuando una aplicación usa un método de resolución de dependencias para resolver tipos de un contenedor, los registros de tipos se suelen realizar desde proyectos de plataforma. Esto permite a los proyectos de plataforma registrar tipos para representadores personalizados, efectos e DependencyService
implementaciones.
Después del registro de tipos de un proyecto de plataforma, el IContainer
objeto debe compilarse, lo que se logra mediante una llamada al BuildContainer
método . Este método invoca el método de Build
Autofac en la ContainerBuilder
instancia de , que crea un nuevo contenedor de inserción de dependencias que contiene los registros que se han realizado.
En las secciones siguientes, se inserta una Logger
clase que implementa la ILogger
interfaz en constructores de clase. La Logger
clase implementa la funcionalidad de registro simple mediante el Debug.WriteLine
método y se usa para demostrar cómo se pueden insertar servicios en representadores, efectos e DependencyService
implementaciones personalizados.
Registro de representadores personalizados
La aplicación de ejemplo incluye una página que reproduce vídeos web, cuyo origen XAML se muestra en el ejemplo siguiente:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:FormsVideoLibrary"
...>
<video:VideoPlayer Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
</ContentPage>
La VideoPlayer
vista se implementa en cada plataforma mediante una VideoPlayerRenderer
clase , que proporciona la funcionalidad para reproducir el vídeo. Para obtener más información sobre estas clases de representador personalizadas, consulte Implementación de un reproductor de vídeo.
En iOS y la Plataforma universal de Windows (UWP), las VideoPlayerRenderer
clases tienen el siguiente constructor, que requiere un ILogger
argumento:
public VideoPlayerRenderer(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
En todas las plataformas, el método realiza RegisterTypes
el registro de tipos con el contenedor de inserción de dependencias, que se invoca antes de cargar la aplicación con el LoadApplication(new App())
método . En el ejemplo siguiente se muestra el RegisterTypes
método en la plataforma iOS:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
App.BuildContainer();
}
En este ejemplo, el Logger
tipo concreto se registra a través de una asignación con su tipo de interfaz y el VideoPlayerRenderer
tipo se registra directamente sin una asignación de interfaz. Cuando el usuario navega a la página que contiene la VideoPlayer
vista, se invocará el método de resolución de dependencias para resolver el VideoPlayerRenderer
tipo del contenedor de inserción de dependencias, que también resolverá e insertará el Logger
tipo en el VideoPlayerRenderer
constructor.
El VideoPlayerRenderer
constructor de la plataforma Android es ligeramente más complicado, ya que requiere un Context
argumento además del ILogger
argumento :
public VideoPlayerRenderer(Context context, ILogger logger) : base(context)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
En el ejemplo siguiente se muestra el RegisterTypes
método en la plataforma Android:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
App.BuildContainer();
}
En este ejemplo, el App.RegisterTypeWithParameters
método registra con VideoPlayerRenderer
el contenedor de inserción de dependencias. El método de registro garantiza que la MainActivity
instancia se insertará como Context
argumento y que el Logger
tipo se insertará como ILogger
argumento.
Registrar efectos
La aplicación de ejemplo incluye una página que usa un efecto de seguimiento táctil para arrastrar BoxView
instancias alrededor de la página. Effect
se agrega al BoxView
mediante el código siguiente:
var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);
La TouchEffect
clase es un RoutingEffect
que se implementa en cada plataforma mediante una TouchEffect
clase que es .PlatformEffect
La clase de plataforma TouchEffect
proporciona la funcionalidad para arrastrar el BoxView
alrededor de la página. Para obtener más información sobre estas clases de efecto, consulte Invocación de eventos a partir de efectos.
En todas las plataformas, la TouchEffect
clase tiene el siguiente constructor, que requiere un ILogger
argumento :
public TouchEffect(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
En todas las plataformas, el método realiza RegisterTypes
el registro de tipos con el contenedor de inserción de dependencias, que se invoca antes de cargar la aplicación con el LoadApplication(new App())
método . En el ejemplo siguiente se muestra el RegisterTypes
método en la plataforma Android:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<TouchTracking.Droid.TouchEffect>();
App.BuildContainer();
}
En este ejemplo, el Logger
tipo concreto se registra a través de una asignación con su tipo de interfaz y el TouchEffect
tipo se registra directamente sin una asignación de interfaz. Cuando el usuario navega a la página que contiene una BoxView
instancia que tiene asociada TouchEffect
, se invocará el método de resolución de dependencias para resolver el tipo de plataforma TouchEffect
desde el contenedor de inserción de dependencias, que también resolverá e insertará el Logger
tipo en el TouchEffect
constructor.
Registro de implementaciones de DependencyService
La aplicación de ejemplo incluye una página que usa DependencyService
implementaciones en cada plataforma para permitir al usuario elegir una foto de la biblioteca de imágenes del dispositivo. La IPhotoPicker
interfaz define la funcionalidad implementada por las DependencyService
implementaciones y se muestra en el ejemplo siguiente:
public interface IPhotoPicker
{
Task<Stream> GetImageStreamAsync();
}
En cada proyecto de plataforma, la PhotoPicker
clase implementa la IPhotoPicker
interfaz mediante las API de plataforma. Para obtener más información sobre estos servicios de dependencia, consulte Selección de una foto de la biblioteca de imágenes.
En iOS y UWP, las PhotoPicker
clases tienen el siguiente constructor, que requiere un ILogger
argumento:
public PhotoPicker(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
En todas las plataformas, el método realiza RegisterTypes
el registro de tipos con el contenedor de inserción de dependencias, que se invoca antes de cargar la aplicación con el LoadApplication(new App())
método . En el ejemplo siguiente se muestra el RegisterTypes
método en UWP:
void RegisterTypes()
{
DIContainerDemo.App.RegisterType<ILogger, Logger>();
DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
DIContainerDemo.App.BuildContainer();
}
En este ejemplo, el Logger
tipo concreto se registra a través de una asignación con su tipo de interfaz y el PhotoPicker
tipo también se registra a través de una asignación de interfaz.
El PhotoPicker
constructor de la plataforma Android es ligeramente más complicado, ya que requiere un Context
argumento además del ILogger
argumento :
public PhotoPicker(Context context, ILogger logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
En el ejemplo siguiente se muestra el RegisterTypes
método en la plataforma Android:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
App.BuildContainer();
}
En este ejemplo, el App.RegisterTypeWithParameters
método registra con PhotoPicker
el contenedor de inserción de dependencias. El método de registro garantiza que la MainActivity
instancia se insertará como Context
argumento y que el Logger
tipo se insertará como ILogger
argumento.
Cuando el usuario navega a la página de selección de fotos y elige seleccionar una foto, se ejecuta el OnSelectPhotoButtonClicked
controlador:
async void OnSelectPhotoButtonClicked(object sender, EventArgs e)
{
...
var photoPickerService = DependencyService.Resolve<IPhotoPicker>();
var stream = await photoPickerService.GetImageStreamAsync();
if (stream != null)
{
image.Source = ImageSource.FromStream(() => stream);
}
...
}
Cuando se invoca el DependencyService.Resolve<T>
método , se invocará al método de resolución de dependencias para resolver el PhotoPicker
tipo del contenedor de inserción de dependencias, que también resolverá e insertará el Logger
tipo en el PhotoPicker
constructor.
Nota
El Resolve<T>
método se debe usar al resolver un tipo del contenedor de inserción de dependencias de la aplicación a través de DependencyService
.