使用 Hosting 构建 WPF 程序 - prism 篇
在 使用 Hosting 构建 WPF 程序 - Stylet 篇 中,使用 Hosting + Stylet 的方式,构建了一个 WPF 框架,
本文用于记录使用 .NET Generic Host + Prism 构建 WPF 所需的修改,仅供参考。
示例代码:Jasongrass/Demo.AppHostPrism: WPF + Prism + Hosting
🍕 1 初始化构建#
新建一个 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>
🍕 2 Main 方法与服务注册#
以下是 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>();
}
}
2.1 使用 Microsoft.DependencyInjection 来实现 DryIoc#
一些有 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 替换实现有点问题?
2.2 App.startup.cs#
在 App.startup.cs
中放了 ConfigureServicesBeforeAppLaunch
和 ConfigureServicesWhenAppLaunch
两个注册服务的入口。
在实际使用中,有其实一个就可以。这里只是为了演示两个不同的服务注册时机。
🍕 3 CommunityToolkit.Mvvm#
虽然使用了 Prism,但在基础的 ViewModel 的属性通知,属性校验,RelayCommand 的书写体验和功能上,感觉还是 CommunityToolkit.Mvvm
更胜一筹。
Prism 的 ViewModel 的基类 BindableBase
其实功能很简单,不如 CommunityToolkit.Mvvm
丰富。
而且,prism 对于导航操作,View 与 ViewModel 的绑定等,并不会强制要求 ViewModel 必须继承 BindableBase
这个基类,很多扩展功能,比如 INavigationAware
等,prism 的设计也是基于接口的,而不是基于 BindableBase
内部的实现。这点就非常棒,为自定义 ViewModel 的实现留足了空间。
CommunityToolkit.Mvvm
和 Prism
可以非常好地共存。
🍕 4 参考资料#
Prism框架与Microsoft.Extensions.DependencyInjection的集成使用笔记 - 非法关键字 - 博客园
.NET 6.0 + WPF 使用 Prism 框架实现导航 - 小码编匠 - 博客园
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
2021-12-03 在博客中插入希沃白板课件