使用 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.xamlApplicationDefinition 修改为 Page,移除自启动特性,待会添加手动的 Main 函数启动。

下面这里的修改,只是增加了两个文件,App.static.csApp.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 中放了 ConfigureServicesBeforeAppLaunchConfigureServicesWhenAppLaunch 两个注册服务的入口。
在实际使用中,有其实一个就可以。这里只是为了演示两个不同的服务注册时机。

🍕 3 CommunityToolkit.Mvvm#

虽然使用了 Prism,但在基础的 ViewModel 的属性通知,属性校验,RelayCommand 的书写体验和功能上,感觉还是 CommunityToolkit.Mvvm 更胜一筹。
Prism 的 ViewModel 的基类 BindableBase 其实功能很简单,不如 CommunityToolkit.Mvvm 丰富。

而且,prism 对于导航操作,View 与 ViewModel 的绑定等,并不会强制要求 ViewModel 必须继承 BindableBase 这个基类,很多扩展功能,比如 INavigationAware 等,prism 的设计也是基于接口的,而不是基于 BindableBase 内部的实现。这点就非常棒,为自定义 ViewModel 的实现留足了空间。

CommunityToolkit.MvvmPrism 可以非常好地共存。

🍕 4 参考资料#

Prism框架与Microsoft.Extensions.DependencyInjection的集成使用笔记 - 非法关键字 - 博客园

github - PrismLibrary/Prism

Introduction to Prism | Prism

.NET 6.0 + WPF 使用 Prism 框架实现导航 - 小码编匠 - 博客园

Prism程序入口、View ViewModel关联、数据绑定、数据校验、cmd - AJun816 - 博客园

原文链接:https://www.cnblogs.com/jasongrass/p/18585090

作者:JasonGrass

出处:https://www.cnblogs.com/jasongrass/p/18585090

版权:本作品采用「署名 4.0 国际」许可协议进行许可。

posted @   J.晒太阳的猫  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2021-12-03 在博客中插入希沃白板课件
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示