Maui: [Spec] Microsoft.Extensions.Hosting y / o Microsoft.Extensions.DependencyInjection

Creado en 18 may. 2020  ·  21Comentarios  ·  Fuente: dotnet/maui

Hornee las características de Microsoft.Extensions.Hosting en .NET MAUI

https://montemagno.com/add-asp-net-cores-dependency-injection-into-xamarin-apps-with-hostbuilder/

Utilice la estructura de host genérico que está configurada con .netcore 3.0 para inicializar las aplicaciones .NET MAUI.

Esto proporcionará a los usuarios una experiencia muy de Microsoft y alineará gran parte de nuestro código con el núcleo de ASP.NET.

Rootear profundamente IServiceProvider en .NET MAUI

Reemplace todas las instancias de Activator.CreateInstance (Type) con IServiceProvider.Get ()

Por ejemplo, si cambiamos esto en ElementTemplate
https://github.com/dotnet/maui/blob/1a380f3c1ddd9ba76d1146bb9f806a6ed150d486/Xamarin.Forms.Core/ElementTemplate.cs#L26

Entonces, cualquier DataTemplate especificado a través del tipo se beneficiará de ser creado a través de la inyección del constructor.

Ejemplos de

`` C #
Host.CreateDefaultBuilder ()
.ConfigureHostConfiguration (c =>
{
c.AddCommandLine (nueva cadena [] {$ "ContentRoot = {FileSystem.AppDataDirectory}"});
c.AddJsonFile (fullConfig);
})
.ConfigureServices ((c, x) =>
{
nativeConfigureServices (c, x);
ConfigureServices (c, x);
})
.ConfigureLogging (l => l.AddConsole (o =>
{
o.DisableColors = verdadero;
}))
.Construir();

ConfigureServices vacío estático (HostBuilderContext ctx, servicios IServiceCollection)
{
if (ctx.HostingEnvironment.IsDevelopment ())
{
var world = ctx.Configuration ["Hola"];
}

services.AddHttpClient();
services.AddTransient<IMainViewModel, MainViewModel>();
services.AddTransient<MainPage>();
services.AddSingleton<App>();

}

### Shell Examples

Shell is already string based and just uses types to create everything so we can easily hook into DataTemplates and provide ServiceCollection extensions 

```C#
static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services)
{
     services.RegisterRoute(typeof(MainPage));
     services.RegisterRoute(typeof(SecondPage));
}

Si todas las plantillas de datos están conectadas a través de IServiceProvider, los usuarios podrían especificar interfaces en plantillas de datos

<ShellContent ContentTemplate="{DataTemplate view:MainPage}"/ShellContent>
<ShellContent ContentTemplate="{DataTemplate view:ISecondPage}"></ShellContent>

Al horno en inyección de constructor

`` C #
aplicación de clase pública
{
aplicación pública ()
{
InitializeComponent ();
MainPage = ServiceProvider.GetService();
}
}
MainPage de clase parcial pública: ContentPage
{
MainPage pública (IMainViewModel viewModel)
{
InitializeComponent ();
BindingContext = viewModel;
}
}

clase pública MainViewModel
{
MainViewModel público (ILoggerregistrador, IHttpClientFactory httpClientFactory)
{
var httpClient = httpClientFactory.CreateClient ();
logger.LogCritical ("¡Esté siempre registrando!");
Hola = "Hola desde IoC";
}
}

### This will allow Shell to also have baked in Constructor Injection

```C#
Routing.RegisterRoute("MainPage", MainPage)

GotoAsync("MainPage") // this will use the ServiceProvider to create the type

Todas las plantillas de contenido especificadas como parte de Shell se crearán a través de IServiceProvider

    <ShellContent
        x:Name="login"
        ContentTemplate="{DataTemplate MainPage}"
        Route="login" />

Detalles de implementación a considerar

Utilice Microsoft.Build para facilitar el proceso de inicio

Extraiga las funciones del host para articular una ubicación de inicio específica donde se registran las cosas
https://montemagno.com/add-asp-net-cores-dependency-injection-into-xamarin-apps-with-hostbuilder/

Esto tiene la ventaja de permitirnos vincularnos con implementaciones de contenedores de IoC que ya funcionan contra el núcleo de asp.net.

Ventajas: Esto brinda a los desarrolladores de .NET una experiencia consistente.
Desventajas: Performance? ¿Es esto excesivo para dispositivos móviles?

Opciones de contenedor DI

Dejar obsoleto DependencyService en favor de Microsoft.Extensions.DependencyInjection

Xamarin.Forms actualmente tiene un servicio de dependencia de producción propia muy simple que no viene con muchas características. Hacer crecer las características de este servicio frente a las opciones ya disponibles no tiene mucho sentido. Para alinearnos más apropiadamente con otras plataformas de Microsoft, podemos cambiar al contenedor dentro de Microsoft.Extensions.DependencyInjection .

El registro automático de DependencyService estará vinculado al nuevo registrador. Si el usuario ha optado por que el registrador realice un escaneo de ensamblaje, esto activará el DependencyService para buscar atributos de nivel de ensamblaje

Una de las advertencias del uso de este contenedor es que los tipos no se pueden registrar sobre la marcha una vez que se ha iniciado la aplicación. Solo puede registrar tipos como parte del proceso de inicio y luego, una vez que se construye IServicePRovider, eso es todo. El registro está cerrado para negocios

Ventajas: es un contenedor con todas las funciones
Desventajas: Performance?

Convierta nuestro DependencyService para usar IServiceCollection como un contenedor interno y haga que implemente IServiceProvider

Esto nos permitiría usar un contenedor muy delgado sin funciones si la gente solo quiere el mejor rendimiento. Probablemente podríamos usar esto como predeterminado y luego las personas podrían optar por el más destacado si lo desean.

`` C #
DependencyService de clase pública: IServiceProvider
{
}

ServiceCollectionExtensions estático público
{
DependencyService Create estático público (este IServiceCollection);
}
''

Consideraciones

¿Es esto útil en general para la experiencia de una aplicación de nuevos usuarios? ¿Realmente queremos agregar la sobrecarga de comprender el ciclo de inicio del host de compilación para nuevos usuarios? Probablemente sería útil tener una configuración predeterminada para todo esto que solo use Init y luego los nuevos usuarios pueden hacer fácilmente lo que necesitan sin tener que configurar los archivos de configuración / configureservices / etc.

Rendimiento

En mis pruebas, pruebas limitadas, los bits de Microsoft Hosting tardan unos 25 ms en iniciarse. Probablemente querremos profundizar en esos 25 ms para ver si podemos evitarlo o si ese costo ya es parte de un costo inicial diferente en el que ya incurriremos.

Compatibilidad con versiones anteriores

  • El DependencyService actual analiza los atributos de nivel de ensamblado que probablemente cambiaremos para optar por .NET MAUI. El valor predeterminado requerirá que registre cosas a través de la Colección de servicios explícitamente

Dificultad: media / grande

Trabajo existente:
https://github.com/xamarin/Xamarin.Forms/pull/8220

breaking proposal-open

Comentario más útil

Algo a considerar al implementar esto es usar el proyecto Microsoft.Extensions.DependencyInjection.Abstractions como la dependencia principal en la implementación en Maui. Al reducir la huella de Microsoft.Extensions.DependencyInjection para que solo se use en la creación del contenedor, permitirá que las bibliotecas y los marcos de terceros sean independientes del contenedor.

Esto permite a un desarrollador o marco la opción de usar un contenedor diferente al proporcionado por Maui. Al usar el proyecto de abstracciones, el trabajo requerido por el desarrollador o el marco es simplemente implementar las interfaces en el proyecto de abstracciones. Donde todo el código de Maui solo usará las interfaces. Esto proporcionará un punto de extensión masivo para todos mientras volvemos a trabajar cómo funciona la inyección de dependencia en la plataforma.

Todos 21 comentarios

Algo a considerar al implementar esto es usar el proyecto Microsoft.Extensions.DependencyInjection.Abstractions como la dependencia principal en la implementación en Maui. Al reducir la huella de Microsoft.Extensions.DependencyInjection para que solo se use en la creación del contenedor, permitirá que las bibliotecas y los marcos de terceros sean independientes del contenedor.

Esto permite a un desarrollador o marco la opción de usar un contenedor diferente al proporcionado por Maui. Al usar el proyecto de abstracciones, el trabajo requerido por el desarrollador o el marco es simplemente implementar las interfaces en el proyecto de abstracciones. Donde todo el código de Maui solo usará las interfaces. Esto proporcionará un punto de extensión masivo para todos mientras volvemos a trabajar cómo funciona la inyección de dependencia en la plataforma.

@ahoefling Sí, así es como Maui consumirá todo. La única pregunta es si necesitamos usar el contenedor ya implementado para la implementación predeterminada o lanzar el nuestro, que tiene una mentalidad un poco más móvil en cuanto al rendimiento.

@PureWeen La implementación del contenedor predeterminado actual funciona bien para dispositivos móviles. Sin embargo, el montón de dependencias que trae el constructor de host es asombroso.

@PureWeen ¡ Es genial escuchar esto!

Por lo que tengo entendido, el rendimiento del contenedor del proyecto de extensiones es bastante sólido. Recomendaría que comencemos con el contenedor predeterminado y podamos iterar si las pruebas de rendimiento no cumplen con nuestras expectativas. Reuní una pequeña muestra de código de lo que estaba pensando para manejar algunos de los puntos de extensión.

Esto nos permite intercambiar fácilmente el contenedor por desarrolladores, frameworks o la plataforma Maui si queremos usar un contenedor diferente.

Quizás el System.Maui.Application podría verse así:
`` c #
Aplicación de clase pública: Element, IResourcesProvider, IApplicationController, IElementConfiguration
{
contenedor público IServiceProvider {get; }

protected virtual IServiceProvider CreateContainer()
{
    var serviceCollection = new ServiceCollection();
    RegisterDependencies(serviceCollection);
    return serviceCollection.BuildServiceProvider();
}

// This should be implemented in the app code
protected virtual void RegisterDependencies(IServiceCollection serviceCollection)
{
    // TODO - Register dependencies in app code
}

public Application()
{
    Container = CreateContainer();

    // omited code
}

// omitted code

}
''

@ahoefling ¿podemos usar esto para permitir que las personas creen su propio IServiceProvider?

https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostbuilder.useserviceproviderfactory?view=dotnet-plat-ext-3.1

Eso es a lo que se enganchan cosas como AutoFac

Hasta donde tengo entendido, Host Builder proporciona una API enriquecida para agregar más dependencias predeterminadas, como cerrar la sesión. Donde mi código sugerido es mucho más sencillo. No creo que ninguno de los enfoques sea más "correcto" que el otro, ya que resuelven diferentes problemas con la inyección de dependencia.

Generador de host
Si queremos crear las opiniones de que el registro de la consola ocurrirá de cierta manera y digamos que HttpClientFactory funcionará de cierta manera, entonces el Host Builder probablemente será nuestra mejor opción.

Lean ServiceCollection
Si queremos que nuestro sistema DI sea lo más sencillo posible y dejar que el desarrollador de la aplicación decida qué funcionará para ellos y qué no, entonces creo que mi código sugerido o similar sería la forma de implementarlo.

La forma en que veo el problema es cuál es la forma menos invasiva de cambiar el DependencyService . Si el objetivo final es seguir los patrones establecidos con asp.net core y el modelo de generador de host, probablemente será mejor crear una clase Startup.cs que utilice el generador de host.

El código en el OP es problemático ya que no está claro por los tipos involucrados cómo funciona ServiceProvider.GetService<MainPage>() y cómo se descubre MainViewModel . También implica un null que debería activar una advertencia NRT. Todo esto parece una forma de eludir el sistema de tipos. ASP.Net no es un ejemplo brillante de una API limpia.

Es importante que los usuarios puedan evitar el uso de esta capacidad, como pueden hacerlo ahora, y especificar los objetos de forma segura. Y que al evitarlo, los usuarios también evitan pagar la penalización de rendimiento.

type App() =
    inherit Application()
    let vm = someMainViewModel(...)
    let mainPage = MainPage(vm)
    do  base.MainPage <- mainPage

Si la gente quiere obtener un modelo de vista en una forma basada en reflexión / convención / anotación, ¿MAUI realmente necesita brindar apoyo explícito? ¿No pueden hacer esto de todos modos? El enlace original sugiere que pueden.

@ahoefling

Host Builder vs Lean ServiceCollection

Correcto, ¡descubrir el beneficio de uno frente al otro es definitivamente una gran parte de esto!

Al principio, solo miraba las colecciones de servicios ajustados, pero decidí ir con todo.
Parte de la ventaja de usar la dirección del constructor es que simplemente se vincula con las implementaciones de netcore existentes. Por ejemplo, con AutoFac puede usar la misma sintaxis de bucle de inicio en lugar de tener que inventar lo nuestro. El conocimiento del dominio es transferible.

@PureWeen Esto tiene sentido y es una buena idea para mantener la unificación de .NET 5+ y las habilidades transferibles entre las diferentes herramientas.

Creo que deberíamos crear un Startup.cs que se encuentra justo al lado del App.cs o App.xaml.cs que maneja todo el código de inicio. Esto unificará la historia de la inyección de dependencia en Maui con otros proyectos en el ecosistema .NET. Cuando implementé la inyección de dependencia en los módulos DNN el año pasado, hice algo similar que permitió a los desarrolladores transferir sus habilidades con bastante facilidad.

Creo que deberíamos crear un Startup.cs que se encuentra justo al lado de App.cs o App.xaml.cs que maneja todo el código de inicio.

Los nuevos usuarios no querrán descubrir todas estas cosas de arranque. En mi opinión, esto es lo último que desea, especialmente porque MAUI aliviará todo el texto estándar en torno a AppDelegate, MainActivity, etc. 1 inicio / app.xaml para gobernarlos a todos.

@aritchie estuvo de acuerdo

Me gustaría habilitar a las personas tanto como sea posible, pero para los nuevos usuarios, la mayor parte de esto puede permanecer oculto.
Pero luego, una vez que se sientan cómodos, pueden comenzar a extender las cosas.

Creo que hay escenarios aquí donde los usuarios pueden obtener ventajas aquí sin tener que comprar todo.

Por ejemplo, con Shell podríamos simplemente introducir una sintaxis de RegisterRoute como

Shell.RegisterRoute<TBindingContext, TPage>(String routeName);
o solo
Shell.RegisterRoute<TType>();

Que bajo el capó usa todas estas cosas para crear los tipos. Luego, si los usuarios quieren un HttpContext que se inyectará, etc.

La otra parte de esta propuesta es intentar crear todas las plantillas de datos a través de IServiceProvider, lo que permite algunos escenarios divertidos.

Estaba jugando con algunas variaciones con Shell aquí basadas en la muestra de James.

https://github.com/PureWeen/AllExtensions-DI-IoC/tree/shell_ioc

@charlesroddie

Si la gente quiere obtener un modelo de vista en una forma basada en reflexión / convención / anotación, ¿MAUI realmente necesita brindar apoyo explícito?

Actualmente, el plan no es proporcionar nada a través de la reflexión debido a consideraciones de rendimiento. Puede haber algunas áreas en las que podemos simplificar el registro sin afectar el rendimiento, pero aún necesitamos explorarlas.

También he actualizado el comentario original con la sintaxis de registro.

@PureWeen

Recomendaría tener algún tipo de extensiones de ruta fuera de IServiceCollection para que cosas como Prism se puedan agregar al modelo. He estado usando XF durante años y no uso Shell.

es decir. services.UseRoute(nombre opcional)

En otra nota, Shiny tiene una gran cantidad de uso en torno a la mecánica DI de Microsoft si necesita otras ideas. Terminé rechazando el modelo de hostbuilder (en su forma actual) debido a la cantidad de cosas que trajo y que llevaron a una pelea horrible con el enlazador. Estaría feliz de compartir estas experiencias en una llamada en algún momento.

¿Quizás los generadores de fuentes podrían usarse para aliviar los problemas de rendimiento? Entonces aún puede usar los atributos personalizados pero hacer todo el registro (predeterminado) en un archivo fuente generado.

¡@aritchie suena bien!

una vez que estemos del otro lado de la diversión de construir, ¡charlemos!

@rogihee

Quizás generadores de fuentes

¡¡Sí!! Queremos reestructurar el registro de renderizador actual antes de Maui y estamos buscando los generadores de origen para ese trabajo. Si terminamos yendo por esa ruta, definitivamente podríamos aprovechar eso para esto.

Sería genial tener DI en Xamarin Forms, pero no leo nada sobre los ámbitos de DI. El uso de ámbitos DI es una excelente manera de administrar la vida útil de los componentes. Es especialmente útil cuando se trabaja con contextos de base de datos de EF core.

Sería estupendo tener una implementación ICommand consciente de DI que cree un alcance y solicite un objeto controlador de comandos en este alcance.

El uso de la inyección de dependencia en las aplicaciones de WPF también es difícil, por lo que tal vez se asocie con el equipo de WPF para permitir el uso de DI en WPF de una manera similar.

La única pregunta es si necesitamos usar el contenedor ya implementado para la implementación predeterminada o lanzar el nuestro, que tiene una mentalidad un poco más móvil en cuanto al rendimiento.

Yo diría que implementar un contenedor para Maui desde cero. Como puede ver aquí , el DependecyService implementado en el Xamarin.Forms es el más rápido. (Lo sé ... Ese artículo es un poco antiguo, pero no creo que los valores cambien demasiado).
También es mucho mejor, en términos de rendimiento, crear nuestro propio contenedor DI. Pero mi preocupación es por un mundo sin .NET 5. Con .NET 5 y el generador de código fuente, podemos tener otra solución a este problema.
Y, para dispositivos móviles , si necesita una DI pesada, está haciendo algo mal.

Vine a este repositorio para proponer que las aplicaciones MAUI admitan el host genérico (Microsoft.Extensions.Hosting), preferible de forma predeterminada (aunque algunas personas pueden querer proteger), ya que es muy natural tener DI en una aplicación de interfaz de usuario. Significaría que la gente necesita entender menos, si están acostumbrados a la inyección de dependencia para el núcleo de asp.net, entonces también funcionará con MAUI. Para los principiantes, sería bueno mantener la configuración muy minimalista.

En lugar de ser el primero, encontré este problema y creo que es una buena discusión.

Tener aplicaciones que brindan algunos servicios y una pequeña interfaz de usuario (estado, configuración, etc.) sigue siendo algo común. Cada vez que creo una pequeña aplicación que proporciona algún servicio, sigo encontrando la necesidad de ampliarla con una interfaz de usuario.

Ya construí algo para que las aplicaciones de Windows Forms y WPF se ejecuten en el host genérico, como se puede ver en este proyecto: https://github.com/dapplo/Dapplo.Microsoft.Extensions.Hosting
Esperaba usar esto para Greenshot, pero todavía no he publicado nada con él.
En las muestras hay proyectos de WPF que utilizan ReactiveUI, MahApps y Caliburn.Micro.

Por favor, no me obligue a registrar las vistas manualmente en DI. Toma lo mejor de blazor, ¡es genial! En blazor puedo pasar parámetros fácilmente o inyectar el servicio en el componente. No es necesario registrar el componente por sí mismo. Solo necesito registrar los servicios. MVVM es excelente para las páginas, pero nunca me he sentido más productivo al crear estas vistas de componentes autohospedadas. Odio comenzar un nuevo proyecto de Xamarin, WPF, UWP debido a las cosas que no están disponibles de fábrica. Una vez más, mire Blazor, es un verdadero modelo para todos los futuros marcos de GUI.

No puedo entender por qué alguien alguna vez crearía estas dos declaraciones consecutivas:

    services.AddTransient<IMainViewModel, MainViewModel>();
    services.AddTransient<MainPage>();

¿Qué hay de malo en inyectar el servicio en el código de vistas que hay detrás? Es lo que quieres el 99,99% del tiempo.

También me gusta mucho la inyección de propiedades en blazor en lugar de la inyección de constructor, solo porque es más fácil.

De hecho, acabo de publicar una biblioteca que hace esto para Xamarin.Forms https://github.com/hostly-org/hostly . Utiliza una implementación personalizada de IHost, que se puede configurar fácilmente en los puntos de entrada nativos. por ejemplo, en MainActivity reemplazaría:

LoadApplication(new App());`

con:

new XamarinHostBuilder().UseApplication<App>()
   .UseStartup<Startup>()
   .UsePlatform(this)
   .Start();

también tiene algunas ventajas adicionales integradas como soporte para registrar middleware con navegación.

Estoy a favor de usar un sistema DI que esté fuera del espacio de nombres System.Maui para que el código se pueda compartir con proyectos MAUI y no MAUI.

Donde estoy menos seguro es con respecto al uso de Microsoft.Extensions.DependencyInjection o algo basado en él como ese sistema DI. No pretendo ser un experto en esto, ciertamente no he usado múltiples sistemas DI modernos. Sin embargo, me pregunto si otros han leído la segunda edición de “Inyección de dependencia. Principios, prácticas y patrones ”por Steven van Deursen y Mark Seemann. Dedican una sección en la segunda edición a analizar Autofac, Simple Injector y Microsoft.Extensions.DependencyInjection, proporcionando pros y contras, así como sus propias opiniones / conclusiones. Si bien puedo ver algunos beneficios de usar Microsoft.Extensions.DependencyInjection (principalmente que se usa en otros escenarios de Microsoft) en el mundo MAUI, me pregunto si alguien aquí con experiencia en múltiples sistemas DI podría comentar sobre las conclusiones de los autores y en qué medida ¿Se relacionan con el mundo MAUI del uso de dispositivos móviles y de escritorio?

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

Amine-Smahi picture Amine-Smahi  ·  3Comentarios

Joshua-Ashton picture Joshua-Ashton  ·  9Comentarios

Yaroslav08 picture Yaroslav08  ·  6Comentarios

njsokalski picture njsokalski  ·  6Comentarios

mhrastegary77 picture mhrastegary77  ·  3Comentarios