在 使用 Hosting 构建 WPF 程序 - Stylet 篇 中,使用 Hosting + Stylet 的方式,构建了一个 WPF 框架, 本文用于记录使用 .NET Generic Host + Prism 构建 WPF 所需的修改,仅供参考。
示例代码:Jasongrass/Demo.AppHostPrism: WPF + Prism + Hosting
新建一个 WPF 项目,修改 .csproj 和 App.cs
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net8.0-windows</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <UseWPF>true</UseWPF> </PropertyGroup>
<ItemGroup> <PackageReference Include="Prism.Core" Version="9.0.537" /> <PackageReference Include="Prism.Wpf" Version="9.0.537" /> <PackageReference Include="Prism.DryIoc" Version="9.0.537" /> <PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0-preview3" /> </ItemGroup>
<ItemGroup> <ApplicationDefinition Remove="App.xaml" /> <Page Include="App.xaml" /> </ItemGroup>
<ItemGroup> <Compile Update="App.startup.cs"> <DependentUpon>App.xaml</DependentUpon> </Compile> <Compile Update="App.static.cs"> <DependentUpon>App.xaml</DependentUpon> </Compile> </ItemGroup>
</Project>
将 App.xaml
从 ApplicationDefinition
修改为 Page
,移除自启动特性,待会添加手动的 Main 函数启动。
下面这里的修改,只是增加了两个文件,App.static.cs
和 App.startup.cs
,让他们看起来属于 App.xaml
,具体内容见后面。
<ItemGroup> <Compile Update="App.startup.cs"> <DependentUpon>App.xaml</DependentUpon> </Compile> <Compile Update="App.static.cs"> <DependentUpon>App.xaml</DependentUpon> </Compile></ItemGroup>
同时,App.xaml
内的 App,需要修改成 PrismApplication
<prism:PrismApplication x:Class="WpfAppPrismT1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfAppPrismT1" xmlns:prism="http://prismlibrary.com/"> <Application.Resources>
</Application.Resources></prism:PrismApplication>
以下是 App.static.cs
的内容。
public partial class App{ private static IServiceCollection? _serviceCollection; private static IServiceProvider? _serviceProvider; private static IContainer? _appContainer;
public static IServiceProvider ServiceProvider { get => _serviceProvider!; private set => _serviceProvider = value; }
public static IContainer AppContainer { get { if (_appContainer == null) { throw new InvalidOperationException("App Container not init"); } return _appContainer; } private set { if (_appContainer != null) { throw new InvalidOperationException("App Container had been initialized"); } _appContainer = value; } }
[STAThread] static void Main(string[] args) { using IHost host = CreateHostBuilder(args).Build(); ServiceProvider = host.Services;
host.StartAsync();
var app = new App(); app.InitializeComponent(); app.Run(); }
private static IHostBuilder CreateHostBuilder(string[] args) { var builder = Host.CreateDefaultBuilder(args) .ConfigureServices(serviceCollection => { // 在这里注册的服务,通过 ServiceProvider 和 AppContainer 都可以拿到 // 因为 prism 的相关服务,是在 App 启动之后才注册,所以通过 ServiceProvider 就拿不到,需要通过 DryIoc 的 AppContainer 去拿。 _serviceCollection = serviceCollection; ConfigureServicesBeforeAppLaunch(serviceCollection); });
return builder; }
public static T GetService<T>() where T : class { var service = AppContainer.GetService<T>();
if (service == null) { throw new InvalidOperationException($"Cannot get service if {typeof(T).Name}"); }
return service; }}
以下是 App.xaml.cs
的内容。
public partial class App : PrismApplication{ protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation<Page1View>(); containerRegistry.RegisterForNavigation<Page2View>(); }
protected override IContainerExtension CreateContainerExtension() { if (_serviceCollection == null) { throw new InvalidOperationException( "Application Startup Error. Cannot found microsoft dependency injection service collection" ); }
ConfigureServicesWhenAppLaunch(_serviceCollection);
var container = new DryIoc.Container(CreateContainerRules()); var newContainer = container.WithDependencyInjectionAdapter(_serviceCollection);
AppContainer = newContainer;
return new DryIocContainerExtension(newContainer); }
protected override Window CreateShell() { return Container.Resolve<MainWindow>(); }}
以下是 App.startup.cs
的内容。
public partial class App{ /// <summary> /// 在这里注册的服务,通过 ServiceProvider 和 AppContainer 都可以拿到 /// </summary> /// <param name="services"></param> private static void ConfigureServicesBeforeAppLaunch(IServiceCollection services) { services.AddSingleton(_ => Current.Dispatcher); services.AddSingleton<IMyService, MyService>(); }
/// <summary> /// 在这里注册的服务,只能通过 AppContainer 拿到 /// </summary> /// <param name="services"></param> private void ConfigureServicesWhenAppLaunch(IServiceCollection services) { // 在这里注册的服务,只能通过 AppContainer 拿到 // services.AddSingleton<IMyService, MyService>(); }}
一些有 IOC 支持的类库的扩展方法,可能只支持 Microsoft.DependencyInjection,并且 Microsoft Hosting 中的配置,日志等依赖,也都放在 Microsoft.DependencyInjection 中, 所以这里想要使用 Microsoft.DependencyInjection 来代替默认的 DryIoc 的实现。
DryIoc.Microsoft.DependencyInjection
这个包提供了这个支持,关键代码在 CreateContainerExtension
方法中。
使用 Prism 时,同步引入 Prism.DryIoc
这个包,Prism
相关的服务,就统一被 DryIoc 管理了。
这里需要注意的是,代码中有 Microsoft.DependencyInjection 的 ServiceProvider
和 DryIoc 的 AppContainer
,实际使用时,应该使用 AppContainer
。 这样才能把 Prism 以及 App 启动时注册的服务纳入其中。 在实际开发时,不应该暴露 ServiceProvider
给外部使用,容易造成误操作。
具体原因还没有深究,或许是这里的 Microsoft.DependencyInjection 替换实现有点问题?
在 App.startup.cs
中放了 ConfigureServicesBeforeAppLaunch
和 ConfigureServicesWhenAppLaunch
两个注册服务的入口。 在实际使用中,有其实一个就可以。这里只是为了演示两个不同的服务注册时机。
虽然使用了 Prism,但在基础的 ViewModel 的属性通知,属性校验,RelayCommand 的书写体验和功能上,感觉还是 CommunityToolkit.Mvvm
更胜一筹。
Prism 的 ViewModel 的基类 BindableBase
其实功能很简单,不如 CommunityToolkit.Mvvm
丰富。
而且,prism 对于导航操作,View 与 ViewModel 的绑定等,并不会强制要求 ViewModel 必须继承 BindableBase
这个基类,很多扩展功能,比如 INavigationAware
等,prism 的设计也是基于接口的,而不是基于 BindableBase
内部的实现。这点就非常棒,为自定义 ViewModel 的实现留足了空间。
CommunityToolkit.Mvvm
和 Prism
可以非常好地共存。
Prism框架与Microsoft.Extensions.DependencyInjection的集成使用笔记 - 非法关键字 - 博客园
.NET 6.0 + WPF 使用 Prism 框架实现导航 - 小码编匠 - 博客园
Prism程序入口、View ViewModel关联、数据绑定、数据校验、cmd - AJun816 - 博客园
原文链接: https://cloud.tencent.com/developer/article/2481590
本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。