Resolución de dependencias Xamarin.Forms

Descargar ejemploDescargar el ejemplo

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.