Maui: [规范] Microsoft.Extensions.Hosting 和/或 Microsoft.Extensions.DependencyInjection

创建于 2020-05-18  ·  21评论  ·  资料来源: dotnet/maui

将 Microsoft.Extensions.Hosting 的功能融入 .NET MAUI

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

利用 .netcore 3.0 设置的通用主机结构来初始化 .NET MAUI 应用程序。

这将为用户提供非常微软的体验,并将带来我们很多符合 ASP.NET 核心的代码

将 IServiceProvider 深入到 .NET MAUI 中

用 IServiceProvider.Get() 替换 Activator.CreateInstance(Type) 的所有实例

例如,如果我们在 ElementTemplate 上更改它
https://github.com/dotnet/maui/blob/1a380f3c1ddd9ba76d1146bb9f806a6ed150d486/Xamarin.Forms.Core/ElementTemplate.cs#L26

然后通过类型指定的任何 DataTemplate 将利用通过构造函数注入创建的优势。

例子

```C#
Host.CreateDefaultBuilder()
.ConfigureHostConfiguration(c =>
{
c.AddCommandLine(new string[] { $"ContentRoot={FileSystem.AppDataDirectory}" });
c.AddJsonFile(fullConfig);
})
.ConfigureServices((c, x) =>
{
nativeConfigureServices(c, x);
配置服务(c, x);
})
.ConfigureLogging(l => l.AddConsole(o =>
{
o.DisableColors = true;
}))
。建造();

静态无效配置服务(HostBuilderContext ctx,IServiceCollection 服务)
{
如果 (ctx.HostingEnvironment.IsDevelopment())
{
var world = ctx.Configuration["你好"];
}

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

如果所有数据模板都通过 IServiceProvider 连接起来,用户可以在数据模板上指定接口

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

在构造函数注入中烘焙

```C#
公开课应用
{
公共应用程序()
{
初始化组件();
MainPage = ServiceProvider.GetService();
}
}
公共部分类 MainPage : ContentPage
{
公共主页面(IMainViewModel viewModel)
{
初始化组件();
BindingContext = 视图模型;
}
}

公共类 MainViewModel
{
公共主视图模型(ILogger记录器,IHttpClientFactory httpClientFactory)
{
var httpClient = httpClientFactory.CreateClient();
logger.LogCritical("一直在记录!");
你好 = "来自国际奥委会的你好";
}
}

### 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

指定为 Shell 一部分的所有 ContentTemplates 都将通过 IServiceProvider 创建

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

要考虑的实施细节

使用 Microsoft.Build 来促进启动管道

拉入主机功能以阐明注册事物的特定启动位置
https://montemagno.com/add-asp-net-cores-dependency-injection-into-xamarin-apps-with-hostbuilder/

这样做的好处是让我们可以绑定到已经在 asp.net core 上工作的 IoC 容器的实现

优点:这为 .NET 开发人员提供了一致的体验。
缺点:性能? 这对手机来说是不是太过分了?

DI 容器选项

弃用 DependencyService 以支持 Microsoft.Extensions.DependencyInjection

Xamarin.Forms 目前有一个非常简单的本地依赖服务,它没有很多功能。 面对已经可用的选项,增加此服务的功能没有多大意义。 为了更恰当地与其他 Microsoft 平台保持一致,我们可以切换到Microsoft.Extensions.DependencyInjection的容器。

DependencyService 的自动注册将绑定到新的注册器。 如果用户选择让注册商进行程序集扫描,那么这将触发 DependencyService 扫描程序集级别的属性

使用此容器的警告之一是,一旦应用程序启动,类型就无法即时注册。 您只能将类型注册为启动过程的一部分,然后一旦构造了 IServicePROvider,就是这样。 注册已停止营业

优点:它是一个功能齐全的容器
缺点:性能?

将我们的 DependencyService 转换为使用 IServiceCollection 作为内部容器并让它实现 IServiceProvider

如果人们只想要最佳性能,这将允许我们使用非常精简的无特色容器。 我们可能会使用它作为默认设置,然后人们可以根据需要选择更具特色的。

```C#
公共类 DependencyService : IServiceProvider
{
}

公共静态 ServiceCollectionExtensions
{
公共静态依赖服务创建(这个 IServiceCollection);
}
``

注意事项

这对新用户的应用体验总体上有用吗? 我们真的想为新用户增加理解构建主机启动循环的开销吗? 对于所有这些只使用 Init 的默认设置可能会很有用,然后新用户可以轻松地做他们需要做的事情,而无需设置设置文件/配置服务/等。

表现

在我的有限测试中,Microsoft Hosting 位启动需要大约 25 毫秒。 我们可能想要更深入地研究这 25 毫秒,看看我们是否可以绕过它,或者该成本是否已经是我们已经产生的不同启动成本的一部分

向后兼容

  • 当前的 DependencyService 会扫描程序集级别的属性,我们很可能会选择加入 .NET MAUI。 默认情况下将要求您通过服务集合显式注册事物

难度:中/大

现有工作:
https://github.com/xamarin/Xamarin.Forms/pull/8220

breaking proposal-open

最有用的评论

实现这一点时需要考虑的是使用Microsoft.Extensions.DependencyInjection.Abstractions项目作为 Maui 中实现的主要依赖项。 通过减少仅用于创建容器的Microsoft.Extensions.DependencyInjection的占用空间,它将使 3rd 方库和框架与容器无关。

这使开发人员或框架可以选择使用与 Maui 提供的容器不同的容器。 通过使用抽象项目,开发人员或框架所需的工作只是在抽象项目中实现接口。 所有 Maui 代码都只使用接口。 当我们重新设计依赖注入在平台中的工作方式时,这将为每个人提供一个巨大的扩展点。

所有21条评论

实现这一点时需要考虑的是使用Microsoft.Extensions.DependencyInjection.Abstractions项目作为 Maui 中实现的主要依赖项。 通过减少仅用于创建容器的Microsoft.Extensions.DependencyInjection的占用空间,它将使 3rd 方库和框架与容器无关。

这使开发人员或框架可以选择使用与 Maui 提供的容器不同的容器。 通过使用抽象项目,开发人员或框架所需的工作只是在抽象项目中实现接口。 所有 Maui 代码都只使用接口。 当我们重新设计依赖注入在平台中的工作方式时,这将为每个人提供一个巨大的扩展点。

@ahoefling是的,毛伊岛就是这样吃掉所有东西的。 唯一的问题是,我们是否需要使用已经实现的容器作为默认实现,还是使用我们自己的容器,这在性能方面更具移动性

@PureWeen当前的默认容器实现在移动设备上表现良好。 然而,主机构建器带来的大量依赖是惊人的。

@PureWeen

据我了解,扩展项目容器的性能非常可靠。 我建议我们从默认容器开始,如果性能测试不符合我们的期望,我们可以进行迭代。 我整理了一些我想处理一些扩​​展点的代码示例。

如果我们想使用不同的容器,这使我们可以轻松地由开发人员、框架或 Maui 平台更换容器。

也许System.Maui.Application看起来像这样:
```c#
公共类应用程序:元素、IResourcesProvider、IApplicationController、IElementConfiguration
{
公共 IServiceProvider 容器 { 获取; }

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我们可以使用它来允许人们创建自己的 IServiceProvider 吗?

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

这就是 AutoFac 之类的东西

据我了解,Host Builder 提供了丰富的 API 来添加更多默认依赖项,例如开箱即用。 我建议的代码更加精简。 我不认为这两种方法中的任何一种都比另一种更“正确”,因为它们通过依赖注入解决了不同的问题。

主机生成器
如果我们想创建控制台日志记录将以某种方式发生的观点,并且假设 HttpClientFactory 将以某种方式工作,那么 Host Builder 可能是我们最好的选择。

精益服务集
如果我们希望我们的 DI 系统尽可能精简,并让应用程序开发人员决定什么对他们有用,什么不适合,那么我认为我建议的代码或类似的代码将是实现它的方式。

我看待问题的方式是换出DependencyService侵入性最小的方法是什么。 如果最终目标是遵循 asp.net 核心和主机构建器模型的模式,那么最好创建一个使用主机构建器的Startup.cs类。

OP 中的代码存在问题,因为从涉及ServiceProvider.GetService<MainPage>()如何工作的类型以及MainViewModel被发现的类型来看不清楚。 它还涉及null应该触发 NRT 警告。 这一切似乎都是绕过类型系统的一种方式。 ASP.Net 并不是一个干净的 API 的光辉例子。

重要的是,用户应该能够像现在一样避免使用此功能,并以类型安全的方式指定对象。 通过避免它,用户还可以避免支付性能损失。

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

如果人们想以基于反射/约定/注释的方式获得一个视图模型,MAUI 真的需要提供明确的支持吗? 他们不能这样做吗? 原始链接表明他们可以。

@ahoefling

Host Builder 与 Lean ServiceCollection

是的,弄清楚一个与另一个的好处绝对是其中的重要组成部分!

起初我只是在看精益服务集合,但决定全力以赴。
使用构建器方向的部分优势在于它仅与现有的 netcore 实现相关联。 例如,使用 AutoFac,您可以使用相同的启动循环语法,而不必发明我们自己的东西。 领域知识是可转移的。

@PureWeen这是有道理的,并且是保持 .NET 5+ 统一和不同工具之间可转移技能的好主意。

我认为我们应该创建一个Startup.cs紧邻App.csApp.xaml.cs处理所有启动代码。 这将统一 Maui 中的依赖注入故事与 .NET 生态系统中的其他项目。 去年我在 DNN 模块中实施依赖注入时,我做了类似的事情,让开发人员可以很容易地转移他们的技能。

认为我们应该创建一个位于处理所有启动代码的 App.cs 或 App.xaml.cs 旁边的 Startup.cs。

新用户不会想弄清楚所有这些引导性的东西。 在我看来,这是您最不想要的,特别是因为 MAUI 将减轻 AppDelegate、MainActivity 等周围的所有样板文件。 1 startup/app.xaml 来统治它们。

@aritchie同意

我想尽可能多地启用人们,但对于新用户来说,大部分内容都可以隐藏。
但是一旦他们感到舒服,他们就可以开始扩展。

我认为这里有一些场景,用户可以在这里获得优势,而无需购买所有东西。

例如,对于 Shell,我们可以只引入一个 RegisterRoute 语法,如

Shell.RegisterRoute<TBindingContext, TPage>(String routeName);
要不就
Shell.RegisterRoute<TType>();

在幕后使用所有这些东西来创建类型。 然后,如果用户想要一个 HttpContext 会被注入等等。

该提案的另一部分是尝试通过 IServiceProvider 创建所有 DataTemplates,这也可以实现一些有趣的场景。

我在这里根据 James 的示例与 Shell 进行了一些变体

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

@查尔斯罗迪

如果人们想以基于反射/约定/注释的方式获得一个视图模型,MAUI 真的需要提供明确的支持吗?

由于性能方面的考虑,目前的计划不是通过反思来提供任何东西。 可能有一些领域我们可以在不影响性能的情况下简化注册,但仍然需要探索这些。

我还用注册语法更新了原始评论

@PureWeen

我建议在 IServiceCollection 之外添加某种路由扩展,以便像 Prism 这样的东西可以插入到模型中。 我多年来一直在使用 XF,但我不使用 Shell。

IE。 services.UseRoute(可选名称)

另一方面,如果您需要其他想法,Shiny 在 Microsoft DI 机制周围有大量用途。 我最终拒绝了 hostbuilder 模型(以其当前形式),因为它带来了大量的东西,导致与链接器的可怕斗争。 我很乐意在某个时候通过电话分享这些经验。

也许源生成器可以用来缓解性能问题? 然后您仍然可以使用自定义属性,但在生成的源文件中进行所有(默认)注册。

@aritchie听起来不错!

一旦我们在构建乐趣的另一边,让我们聊天吧!

@rogihee

也许源生成器

是的!! 我们想要在 Maui 之前重构当前的渲染器注册,我们正在查看用于该工作的源生成器。 如果我们最终走那条路,我们绝对可以为此利用它

在 Xamarin Forms 中使用 DI 会很棒,但我没有阅读任何关于 DI 范围的内容。 使用 DI 范围是管理组件生命周期的好方法。 它在使用 EF 核心的数据库上下文时特别有用。

如果有一个 DI-aware ICommand实现来创建一个作用域并在这个作用域上请求一个命令处理程序对象,那就太好了。

在 WPF 应用程序中使用依赖注入也很困难,所以也许与 WPF 团队合作,允许以类似的方式在 WPF 中使用 DI。

唯一的问题是,我们是否需要使用已经实现的容器作为默认实现,还是使用我们自己的容器,这在性能方面更具移动性

我会说从头开始为毛伊岛实现一个容器。 正如你在这里看到的,在DependecyService实现的Xamarin.Forms是更快的。 (我知道……那篇文章有点老了,但我不认为价值观变化太大)。
此外,在性能方面,创建我们自己的 DI 容器更好。 但我担心的是一个没有 .NET 5 的世界。有了 .NET 5 和源代码生成器,我们可能有另一个解决这个问题的方法。
而且,对于移动设备,如果您需要大量 DI,那您就做错了。

我来到这个存储库是为了建议让 MAUI 应用程序支持通用主机 (Microsoft.Extensions.Hosting),默认情况下更可取(尽管有些人可能想要保护),因为在 UI 应用程序中具有 DI 是很自然的。 这意味着人们需要了解的更少,如果他们习惯于 ASP.NET Core 的依赖注入,那么它也适用于 MAUI。 对于初学者来说,保持配置非常简约可能是好的。

我不是第一个,而是发现了这个问题,并认为这是一个很好的讨论!

拥有提供一些服务和小 UI(状态、配置等)的应用程序仍然是常见的事情。 每次我构建一个提供一些服务的小应用程序时,我都会发现需要用 UI 扩展它。

我已经构建了一些东西来让 Windows 窗体和 WPF 应用程序在通用主机上运行,​​正如在这个项目中看到的: https :
我希望将它用于 Greenshot,但目前我还没有发布任何内容。
在示例中有使用 ReactiveUI、MahApps 和 Caliburn.Micro 的 WPF 项目。

请不要让我在 DI 中手动注册视图。 从 blazor 中获取最好的,它很棒! 在 blazor 中,我可以轻松地传递参数或在组件中注入服务。 无需自行注册组件。 我只需要注册服务。 MVVM 非常适合页面,但我从未觉得创建这些自托管组件视图更有效率。 我讨厌开始一个新的 Xamarin、WPF、UWP 项目,因为这些东西不是开箱即用的。 再看看 Blazor,它是未来所有 GUI 框架的真正蓝图。

我不明白为什么有人会创造这两个概念性的陈述:

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

将服务注入后面的视图代码有什么问题? 它在 99.99% 的情况下都是您想要的。

此外,我非常喜欢 blazor 中的属性注入而不是构造函数注入,只是因为它更容易。

我实际上刚刚发布了一个为 Xamarin.Forms https://github.com/hostly-org/hostly执行此操作的库。 它使用 IHost 的自定义实现,可以在本机入口点中轻松设置。 例如在MainActivity您将替换:

LoadApplication(new App());`

和:

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

它还内置了一些额外的好处,例如支持使用导航注册中间件。

我赞成使用 System.Maui 命名空间之外的 DI 系统,以便可以与 MAUI 和非 MAUI 项目共享代码。

我不太确定的是关于使用 Microsoft.Extensions.DependencyInjection 或基于它的东西作为那个 DI 系统。 我不会假装自己是这方面的专家——我自己当然没有使用过多个现代 DI 系统。 不过不知道大家有没有看过《依赖注入》第二版。 原则、实践和模式”,Steven van Deursen 和 Mark Seemann。 他们在第二版中有一节专门介绍 Autofac、Simple Injector 和 Microsoft.Extensions.DependencyInjection,提供优缺点,以及他们自己的意见/结论。 虽然我可以看到在 MAUI 世界中使用 Microsoft.Extensions.DependencyInjection(主要是它在其他 Microsoft 场景中使用)的一些好处,但我想知道这里是否有多个 DI 系统经验的人可以评论作者的结论以及程度它们与移动和桌面使用的 MAUI 世界有关吗?

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

Suplanus picture Suplanus  ·  4评论

4creators picture 4creators  ·  31评论

adojck picture adojck  ·  15评论

njsokalski picture njsokalski  ·  6评论

mhrastegary77 picture mhrastegary77  ·  3评论