【发布时间】:2021-01-01 15:21:38
【问题描述】:
我想使用 .NET Core 3.1 启动一个 WPF 应用程序
Prism 可以使用 .Net Core 的内置 DI (IServiceCollection) 还是我必须使用 Unity 之类的东西?
如果 Prism 不能使用内置 DI,它们可以并存吗?
【问题讨论】:
我想使用 .NET Core 3.1 启动一个 WPF 应用程序
Prism 可以使用 .Net Core 的内置 DI (IServiceCollection) 还是我必须使用 Unity 之类的东西?
如果 Prism 不能使用内置 DI,它们可以并存吗?
【问题讨论】:
Prism 能否利用 .Net Core 的内置 DI
据我所知,您不能真正用 ASP.NET Core 内置的替代 Prism 的 DryIot。主要是 DryIot 比 ServiceCollection API 功能更丰富。有这个开源package 我发现它有一个使用ServiceCollection 的IContainerExtension 实现,但根据开发人员自己的话,这更像是一个概念证明而不是可靠的解决方案。
如果 Prism 不能使用内置 DI,它们可以并排存在吗?
是的,他们可以。需要注意的是 - 您不能简单地在 ServiceCollection 中注册服务并期望能够直接在您的应用程序、模块和视图模型中注入该服务。这将失败,因为这些文件由 Prism 框架管理,因此注入仅适用于您使用 IContainerRegistry 接口注册的服务。
你为什么要这样做?作为内置物联网容器,ServiceCollection API 是众所周知的,因此对于 .Net 开发人员来说会更简单。此外,您可以构建非 WPF 项目以使用默认容器注册服务,从而使它们与您的桌面项目完全分离。这对于领域驱动设计等更复杂的架构非常有用。
让我们考虑以下项目结构:
solution
-- Desktop // Prism WPF application, containing only views and models
-- Application // Class library, containing operational logic.
假设作为应用程序项目的一部分,您需要一个IUserService,它保存有关当前用户的信息,当用户在桌面应用程序中进行身份验证时,这些信息必须填充到内存中。注册方法如下所示:
public IServiceCollection AddServices(this IServiceCollection services)
{
services.AddSingleton<IUserService, UserService>()
}
所以现在我们需要将它注入到桌面项目中。我建议两种方法:
这种方法需要非常简单的启动配置。需要注意的是,您将无法直接在构造函数中注入服务,而是通过IServiceProvider 接口。
参考Microsoft.Extensions.DependencyInjection
在App中调用你的服务注册方法:
protected override void RegisterTypes(IContainerRegistry container)
{
// Build service provider using the ServiceCollection API
var provider = new ServiceCollection()
.AddServices()
.BuildServiceProvider();
// Register IServiceProvider within DryIot and provide
// a factory method to retrieve the service instance
container.Register<IServiceProvider>(() => provider);
}
在需要 IUserService 的地方注入 IServiceProvider。在本例中,我将使用 Prism 模块:
public class Module : IModule
{
private readonly IUserService userService;
public Module(IServiceProvider serviceProvider)
{
this.userService = serviceProvider.GetService<IUserService>();
}
...
private void Authenticate()
{
this.userService.IsAuthenticated = true;
}
}
就是这样,您现在可以在任何可以通过 Prism 注入访问 IServiceProvider 的地方使用您的 ServiceCollection 注册依赖项。这是我推荐的方法,因为我们只是将 .Net 容器包装在 Prism 中。
这是它变得更有趣的地方。完全免责声明 - 您可能会在使用这种方法时遇到问题。除了最基本的用例之外,我还没有测试过这个。此方法提供的唯一优点是您将能够直接在构造函数中注入服务,而不必通过IServiceProvider。
本质上,此方法只是循环通过ServiceCollection 并直接在 Prism 容器中注册所有服务。如果我们看一下ServiceCollection 的实现,它只是一个ServiceDescriptors 的列表。经过进一步检查,我们发现ServiceDescriptior 包含一堆构造函数。我们将忽略这些并专注于属性:
ServiceType - 注入时将使用的类型ImplementationType - 要注入的实现类型ImplementationInstance - 实现类型的实例ImplementationFactory - 返回实现类型实例的工厂委托LifeTime - Singleton、Scoped 或 Transient 类型现在让我们检查IContainerRegistry 接口。我们会看到有很多Register 的重载接受类型、对象和委托。
利用这些知识,我们现在可以创建一个从 ServiceDescriptor 到注册 IContainerRegistry 的适配器。下面的实现将只关注瞬态服务,但服务生命周期之间的区别只是我们调用的注册表方法 - Register 用于瞬态,RegisterSingleton 用于单例。
使用接受IContainerRegistry 和ServiceDescriptor 参数的静态方法创建和适配器类:
public static void Register(IContainerRegistry container, ServiceDescriptor service)
{
// In case an implementation instance is provided we simply need to register it
if (service.ImplementationInstance != null)
{
containter.Register(service.ServiceType, service.ImplementationInstance);
}
// In case a factory is provided we have a bit more work.
// We need to modify it in order for it to be usable by the DryIot container
else if (service.ImlementationFactory != null)
{
var factory = service.ImplementationFactory;
var dryIotFactory = dryIotProvider =>
{
var provider = dryIotProvider.Resolve<IServiceProvider>();
return factory(provider);
};
container.Register(service.ServiceType, dryIotFactory);
}
// If no implementation or factory is provided then we simply register the
// types and let the container create the implementation instance on its own.
else
{
container.Register(service.ServiceType, service.ImplementationType);
}
}
这里最棘手的部分是工厂。为了更好地了解服务注册中的工厂,请知道有时您可能需要访问其他服务以提供正确的实现实例。例如,如果IHttpClient 已注册,您需要为IAuthorizationSerice 提供HttpAuthorizationService 实现而不是DesktopAuthorizationService。
本质上,我们使用 DryIot 兼容工厂(接受 DryIot 容器的实例)包装原始工厂方法,该工厂可以为原始工厂提供 IServiceProvider 实例。
参考Microsoft.Extensions.DependencyInjection
在App中调用你的服务注册方法:
protected override void RegisterTypes(IContainerRegistry container)
{
var services = new ServiceCollection().AddServices()
foreach (var service in services)
{
Adapter.Register(container, service);
}
}
直接在模块构造函数中注入IUserService:
public class Module : IModule
{
private readonly IUserService userService;
public Module(IUserService userService)
{
this.userService = userService;
}
}
再次,我推荐简单的方法。简单意味着更低的学习曲线和更少的出错空间。相比之下,不便之处很小。
另一个公平的警告 - 这不是生产就绪代码。尤其是无缝的方法。我还没有对这个实现进行“实战测试”,但它可能会为你指明正确的方向。
如果有人有反馈/意见,我很乐意阅读:)
【讨论】:
Prism 能否利用 .Net Core 的内置 DI
简答,否
这是@brianlagunas(棱镜的创造者)的a comment
正如我所提到的,我们不能像在 netstandard 1.0 中那样使用 IServiceProvider。 ServiceProvider 和 IServiceCollection 在 netstandard 2.0 中。此外,Prism 需要在 IServiceCollection 实现中限制的许多功能。例如命名实例和注册,以及可变容器。
这里是@dansiegel 的a comment
我花了很多时间讨论这个问题,最终我们不能直接依赖 IServiceProvider 和 IServiceCollection,原因有很多,超出了它们是否可用。
这里也是another comment @brianlagunas
【讨论】:
IServiceProvider 并并排使用它们。
我必须使用 Unity 之类的东西吗?
ServiceCollection 是“有点像 Unity”。而且,是的,您可以将它与 prism 一起使用:
ServiceCollection 的IContainerExtension 实现
PrismApplicationBase 派生并从CreateContainerExtension 返回您的容器扩展
【讨论】: