Maui: [Spec] Microsoft.Extensions.Hosting e / ou Microsoft.Extensions.DependencyInjection

Criado em 18 mai. 2020  ·  21Comentários  ·  Fonte: dotnet/maui

Aqueça os recursos do Microsoft.Extensions.Hosting no .NET MAUI

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

Utilize a estrutura de host genérico que é configurada com .netcore 3.0 para inicializar aplicativos .NET MAUI.

Isso fornecerá aos usuários uma experiência muito Microsoft e alinhará muito do nosso código com o núcleo do ASP.NET

Profundamente root IServiceProvider em .NET MAUI

Substitua todas as instâncias de Activator.CreateInstance (Type) por IServiceProvider.Get ()

Por exemplo, se mudarmos isso em ElementTemplate
https://github.com/dotnet/maui/blob/1a380f3c1ddd9ba76d1146bb9f806a6ed150d486/Xamarin.Forms.Core/ElementTemplate.cs#L26

Então, qualquer DataTemplate especificado por meio do tipo aproveitará a vantagem de ser criado por meio de injeção de construtor.

Exemplos

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

static void ConfigureServices (HostBuilderContext ctx, IServiceCollection services)
{
if (ctx.HostingEnvironment.IsDevelopment ())
{
var world = ctx.Configuration ["Olá"];
}

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));
}

Se todos os DataTemplates estiverem conectados através do IServiceProvider, os usuários podem especificar Interfaces em DataTemplates

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

Cozido em injeção de construtor

`` `C #
aplicativo de classe pública
{
aplicativo público ()
{
InitializeComponent ();
MainPage = ServiceProvider.GetService();
}
}
classe pública parcial MainPage: ContentPage
{
public MainPage (IMainViewModel viewModel)
{
InitializeComponent ();
BindingContext = viewModel;
}
}

public class MainViewModel
{
public MainViewModel (ILoggerlogger, IHttpClientFactory httpClientFactory)
{
var httpClient = httpClientFactory.CreateClient ();
logger.LogCritical ("Sempre esteja registrando!");
Hello = "Hello from 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

Todos os ContentTemplates especificados como parte do Shell serão criados por meio do IServiceProvider

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

Detalhes de implementação a serem considerados

Use o Microsoft.Build para facilitar o pipeline de inicialização

Puxe os recursos do host para articular um local de inicialização específico onde as coisas são registradas
https://montemagno.com/add-asp-net-cores-dependency-injection-into-xamarin-apps-with-hostbuilder/

Isso tem a vantagem de nos permitir vincular as implementações de contêineres IoC que já funcionam no núcleo do asp.net

Prós: Isso proporciona aos desenvolvedores .NET uma experiência consistente.
Contras: desempenho? Isso é um exagero para dispositivos móveis?

Opções de contêiner DI

Suspender DependencyService em favor de Microsoft.Extensions.DependencyInjection

O Xamarin.Forms atualmente tem um serviço de dependência desenvolvido internamente muito simples que não vem com muitos recursos. Aumentar os recursos deste serviço em face das opções já disponíveis não faz muito sentido. Para nos alinharmos mais apropriadamente com outras plataformas Microsoft, podemos mudar para o contêiner dentro de Microsoft.Extensions.DependencyInjection .

O registro automático do DependencyService será vinculado ao novo registrador. Se o usuário optou por que o registrador faça a varredura de assembly, isso acionará o DependencyService para fazer a varredura de atributos de nível de montagem

Uma das advertências de usar esse contêiner é que os tipos não podem ser registrados imediatamente depois que o aplicativo é iniciado. Você só pode registrar tipos como parte do processo de inicialização e, depois de construído o IServicePRovider, é tudo. O registro está fechado para negócios

Prós: é um contêiner completo
Contras: desempenho?

Converta nosso DependencyService para usar IServiceCollection como um contêiner interno e implemente IServiceProvider

Isso nos permitiria usar um contêiner muito estreito, sem recursos, se as pessoas quisessem apenas o melhor desempenho. Provavelmente, poderíamos usar isso como um padrão e, então, as pessoas poderiam optar por aquele com mais recursos, se quisessem.

`` `C #
public class DependencyService: IServiceProvider
{
}

public static ServiceCollectionExtensions
{
public static DependencyService Create (este IServiceCollection);
}
`` `

Considerações

Em geral, isso é útil para uma experiência de aplicativo de novos usuários? Queremos realmente adicionar a sobrecarga de compreensão do loop de inicialização do host de construção para novos usuários? Provavelmente seria útil ter apenas uma configuração padrão para tudo isso que usa apenas Init e, em seguida, novos usuários podem facilmente fazer o que precisam, sem ter que configurar arquivos de configuração / configureservices / etc.

atuação

Em meus testes, testes limitados, leva cerca de 25 ms para os bits do Microsoft Hosting inicializarem. Provavelmente, vamos querer mergulhar mais fundo nesses 25 ms para ver se podemos contornar isso ou se esse custo já faz parte de um custo inicial diferente que já incorreremos

Compatibilidade com versões anteriores

  • O DependencyService atual verifica os atributos de nível de montagem que provavelmente mudaremos para opt-in para .NET MAUI. O padrão exigirá que você registre coisas por meio da Coleção de Serviços explicitamente

Dificuldade: Média / Grande

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

breaking proposal-open

Comentários muito úteis

Algo a se considerar ao implementar isso é usar o projeto Microsoft.Extensions.DependencyInjection.Abstractions como a principal dependência na implementação em Maui. Ao reduzir a pegada de Microsoft.Extensions.DependencyInjection para ser usado apenas na criação do contêiner, ele permitirá que bibliotecas e estruturas de terceiros sejam independentes do contêiner.

Isso permite que um desenvolvedor ou framework tenha a opção de usar um contêiner diferente do fornecido pelo Maui. Ao usar o projeto de abstrações, o trabalho exigido pelo desenvolvedor ou framework é apenas implementar as interfaces no projeto de abstrações. Onde todo o código Maui usará apenas as interfaces. Isso fornecerá um ponto de extensão massivo para todos, conforme retrabalhamos como a injeção de dependência funciona na plataforma.

Todos 21 comentários

Algo a se considerar ao implementar isso é usar o projeto Microsoft.Extensions.DependencyInjection.Abstractions como a principal dependência na implementação em Maui. Ao reduzir a pegada de Microsoft.Extensions.DependencyInjection para ser usado apenas na criação do contêiner, ele permitirá que bibliotecas e estruturas de terceiros sejam independentes do contêiner.

Isso permite que um desenvolvedor ou framework tenha a opção de usar um contêiner diferente do fornecido pelo Maui. Ao usar o projeto de abstrações, o trabalho exigido pelo desenvolvedor ou framework é apenas implementar as interfaces no projeto de abstrações. Onde todo o código Maui usará apenas as interfaces. Isso fornecerá um ponto de extensão massivo para todos, conforme retrabalhamos como a injeção de dependência funciona na plataforma.

@ahoefling Sim, é assim que Maui vai consumir tudo. A única questão é se precisamos usar o contêiner já implementado para a implementação padrão ou lançar o nosso próprio que é um pouco mais voltado para dispositivos móveis no que diz respeito ao desempenho

@PureWeen A implementação do contêiner padrão atual tem um bom desempenho para dispositivos móveis. O monte de dependências que o construtor de host traz é impressionante.

@PureWeen, é ótimo ouvir isso!

Até onde eu entendo, o desempenho do contêiner de projeto de extensões é bastante sólido. Eu recomendaria que comecemos com o contêiner padrão e podemos iterar se os testes de desempenho não atenderem às nossas expectativas. Eu reuni um pequeno exemplo de código do que estava pensando para lidar com alguns dos pontos de extensão.

Isso nos permite trocar facilmente o contêiner por desenvolvedores, frameworks ou a plataforma Maui se quisermos usar um contêiner diferente.

Talvez o System.Maui.Application pudesse ser parecido com isto:
`` `c #
public class Application: Element, IResourcesProvider, IApplicationController, IElementConfiguration
{
public IServiceProvider Container {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 isso para permitir que as pessoas criem seu próprio IServiceProvider?

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

É isso que coisas como o AutoFac se encaixam

Tanto quanto eu entendo, o Host Builder fornece uma API rica para adicionar mais dependências padrão, como fazer o log out of the box. Onde meu código sugerido é muito mais enxuto. Não acho que nenhuma das abordagens seja mais "certa" do que a outra, pois resolvem problemas diferentes com a injeção de dependência.

Host Builder
Se quisermos criar as opiniões de que o log do console acontecerá de uma determinada maneira e digamos que a HttpClientFactory funcione de uma determinada maneira, o Host Builder provavelmente será nossa melhor opção.

Lean ServiceCollection
Se quisermos que nosso sistema de DI seja o mais enxuto possível e permitir que o desenvolvedor do aplicativo decida o que funcionará e o que não funcionará para ele, acho que meu código sugerido ou similar seria a maneira de implementá-lo.

A maneira como estou olhando para o problema é qual é a maneira menos invasiva de trocar DependencyService . Se o objetivo final for seguir os padrões existentes com o núcleo do asp.net e o modelo de construtor de host, provavelmente será melhor criar uma classe Startup.cs que utiliza o construtor de host.

O código no OP é problemático, pois não está claro, a partir dos tipos envolvidos, como ServiceProvider.GetService<MainPage>() funciona e como MainViewModel é descoberto. Também envolve um null que deve acionar um aviso NRT. Tudo isso parece uma forma de contornar o sistema de tipos. ASP.Net não é um exemplo brilhante de uma API limpa.

É importante que os usuários possam evitar o uso desse recurso, como podem agora, e especificar os objetos de forma segura. E, ao evitá-lo, os usuários também evitam pagar a penalidade de desempenho.

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

Se as pessoas desejam obter um modelo de visão de uma forma baseada em reflexão / convenção / anotação, a MAUI realmente precisa fornecer suporte explícito? Eles não podem fazer isso de qualquer maneira? O link original sugere que sim.

@ahoefling

Host Builder vs Lean ServiceCollection

Certo, descobrir o benefício de um em relação ao outro é definitivamente uma grande parte disso!

No início, eu estava apenas olhando para Lean Service Collections, mas decidi apostar tudo.
Parte da vantagem de usar a direção do construtor é que ela apenas se conecta às implementações netcore existentes. Por exemplo, com o AutoFac você pode simplesmente usar a mesma sintaxe de loop de inicialização em vez de ter que inventar nossas próprias coisas. O conhecimento do domínio é transferível.

@PureWeen Isso faz sentido e é uma boa ideia manter a unificação do .NET 5+ e as habilidades transferíveis entre as diferentes ferramentas.

Acho que devemos criar um Startup.cs que fique bem próximo ao App.cs ou App.xaml.cs que lida com todo o código de inicialização. Isso unificará a história da injeção de dependência em Maui com outros projetos no ecossistema .NET. Quando implementei a injeção de dependência em módulos DNN no ano passado, fiz algo semelhante que permitiu aos desenvolvedores transferir suas habilidades com bastante facilidade.

acho que devemos criar um Startup.cs que fica bem próximo ao App.cs ou App.xaml.cs que lida com todo o código de inicialização.

Novos usuários não vão querer descobrir todas essas coisas de bootstrapping. Na minha opinião, esta é a última coisa que você deseja, especialmente porque o MAUI irá aliviar todos os clichês em torno de AppDelegate, MainActivity, etc. 1 startup / app.xaml para governar todos eles.

@aritchie concordou

Eu gostaria de habilitar as pessoas o máximo possível, mas para novos usuários, a maior parte disso pode permanecer oculto.
Mas então, uma vez que eles estejam confortáveis, eles podem começar a estender as coisas.

Acho que há cenários aqui em que os usuários podem obter vantagens sem ter que comprar tudo.

Por exemplo, com o Shell, poderíamos apenas introduzir uma sintaxe RegisterRoute como

Shell.RegisterRoute<TBindingContext, TPage>(String routeName);
ou apenas
Shell.RegisterRoute<TType>();

Que sob o capô usa tudo isso para criar os tipos. Então, se os usuários quiserem um HttpContext que será injetado em etc.

A outra parte desta proposta é tentar criar também todos os DataTemplates através do IServiceProvider, o que possibilita alguns cenários divertidos.

Eu estava brincando com algumas variações com o Shell aqui com base na amostra de James

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

@charlesroddie

Se as pessoas desejam obter um modelo de visão de uma forma baseada em reflexão / convenção / anotação, a MAUI realmente precisa fornecer suporte explícito?

O plano atualmente não é fornecer nada por meio de reflexão devido a considerações de desempenho. Pode haver algumas áreas em que podemos simplificar o registro sem prejudicar o desempenho, mas ainda precisamos explorar algumas delas.

Também atualizei o comentário original com a sintaxe de registro

@PureWeen

Eu recomendaria ter algum tipo de extensão de rota fora do IServiceCollection para que coisas como o Prism possam se conectar ao modelo. Uso o XF há anos e não uso o Shell.

ou seja, services.UseRoute(nome opcional)

Por outro lado, o Shiny pode ser muito usado em torno da mecânica do Microsoft DI se você precisar de outras idéias. Acabei rejeitando o modelo do construtor de host (em sua forma atual) por causa da quantidade de coisas que ele trouxe, o que levou a uma luta horrível com o vinculador. Eu ficaria feliz em compartilhar essas experiências em uma chamada em algum momento.

Talvez os geradores de fonte pudessem ser usados ​​para aliviar os problemas de desempenho? Em seguida, você ainda pode usar os atributos personalizados, mas fazer todo o registro (padrão) em um arquivo de origem gerado.

@aritchie parece bom!

quando estivermos do outro lado da diversão, vamos conversar!

@rogihee

Talvez geradores de fonte

Sim!! Queremos reestruturar o registro de renderizador atual antes de Maui e estamos olhando para os Geradores de código-fonte para esse trabalho. Se acabarmos por seguir esse caminho, poderíamos definitivamente alavancar isso para este

Seria ótimo ter DI no Xamarin Forms, mas não li nada sobre escopos de DI. Usar escopos de DI é uma ótima maneira de gerenciar o tempo de vida dos componentes. É especialmente útil ao trabalhar com contextos de banco de dados do núcleo EF.

Seria ótimo ter uma implementação compatível com DI ICommand que cria um escopo e solicita um objeto manipulador de comando neste escopo.

Usar injeção de dependência em aplicativos WPF também é difícil, então talvez se junte à equipe do WPF para permitir o uso de DI no WPF de maneira semelhante.

A única questão é se precisamos usar o contêiner já implementado para a implementação padrão ou lançar o nosso próprio que é um pouco mais voltado para dispositivos móveis no que diz respeito ao desempenho

Eu diria para implementar um contêiner para o Maui do zero. Como você pode ver aqui , o DependecyService implementado no Xamarin.Forms é o mais rápido. (Eu sei ... Esse artigo é meio antigo, mas não acho que os valores mudem muito).
Também é muito melhor - em termos de desempenho - criar nosso próprio DI Container. Mas minhas preocupações são por um mundo sem .NET 5. Com o .NET 5 e o gerador de código-fonte, podemos ter outra solução para esse problema.
E, para celular , se você precisar de um DI pesado, você está fazendo algo errado.

Eu vim para este repositório para propor fazer aplicativos MAUI suportarem o host genérico (Microsoft.Extensions.Hosting), preferível por padrão (embora algumas pessoas possam querer proteger), já que é muito natural ter DI em um aplicativo UI. Isso significaria que as pessoas precisam entender menos, se estiverem acostumadas à injeção de dependência para o núcleo do asp.net, do que também funcionará com o MAUI. Para iniciantes, pode ser bom manter a configuração minimalista.

Em vez de ser o primeiro, encontrei este assunto, e acho que é uma boa discussão!

Ter aplicativos que fornecem alguns serviços e uma pequena IU (status, configuração, etc.) ainda é uma coisa comum. Cada vez que construo um pequeno aplicativo que fornece algum serviço, continuo descobrindo a necessidade de estendê-lo com uma IU.

Já construí algo para fazer aplicativos Windows Forms e WPF rodarem no host genérico, como pode ser visto neste projeto: https://github.com/dapplo/Dapplo.Microsoft.Extensions.Hosting
Eu esperava usar isso para Greenshot, mas atualmente não lancei nada com ele ainda.
Nas amostras, há projetos WPF que usam ReactiveUI, MahApps e Caliburn.Micro.

Por favor, não me faça registrar as visualizações manualmente no DI. Tire o melhor do blazor, é ótimo! No blazor posso facilmente passar parâmetros ou injetar o serviço no componente. Não há necessidade de registrar o próprio componente. Eu só preciso registrar os serviços. MVVM é ótimo para páginas, mas nunca me senti tão produtivo criando essas visualizações de componentes auto-hospedadas. Eu odeio começar um novo projeto Xamarin, WPF, UWP por causa das coisas que não estão lá fora da caixa. Mais uma vez, olhe para o Blazor, é um verdadeiro projeto para todos os futuros frameworks GUI.

Não consigo entender por que alguém criaria essas duas declarações concecutivas:

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

O que há de errado em injetar o serviço no código de visualizações por trás? É o que você quer 99,99% do tempo.

Também gosto muito da injeção de propriedades no blazor ao invés da injeção de construtor, só porque é mais fácil.

Na verdade, acabei de lançar uma biblioteca que faz isso para Xamarin.Forms https://github.com/hostly-org/hostly . Ele usa uma implementação personalizada de IHost, que pode ser facilmente configurada nos pontos de entrada nativos. por exemplo, em MainActivity você substituiria:

LoadApplication(new App());`

com:

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

ele também possui alguns recursos extras integrados, como suporte para registro de middleware com navegação.

Sou a favor de usar um sistema DI que esteja fora do namespace System.Maui para que o código possa ser compartilhado com projetos MAUI e não-MAUI.

Tenho menos certeza em relação ao uso de Microsoft.Extensions.DependencyInjection ou algo baseado nele como esse sistema DI. Não vou fingir ser um especialista nisso - certamente não usei vários sistemas DI modernos. No entanto, eu me pergunto se outras pessoas leram a segunda edição de “Injeção de Dependência. Princípios, Práticas e Padrões ”por Steven van Deursen e Mark Seemann. Eles dedicam uma seção na segunda edição para examinar Autofac, Simple Injector e Microsoft.Extensions.DependencyInjection, fornecendo prós e contras, bem como suas próprias opiniões / conclusões. Embora eu possa ver alguns benefícios de usar Microsoft.Extensions.DependencyInjection (principalmente porque é usado em outros cenários da Microsoft) no mundo MAUI, eu me pergunto se alguém aqui com experiência em vários sistemas de DI poderia comentar sobre as conclusões dos autores e em que grau eles se relacionam com o mundo MAUI de uso móvel e desktop?

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

ghost picture ghost  ·  7Comentários

qcjxberin picture qcjxberin  ·  5Comentários

Yaroslav08 picture Yaroslav08  ·  6Comentários

jsuarezruiz picture jsuarezruiz  ·  7Comentários

sim756 picture sim756  ·  16Comentários