You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Prism WPF按需模块加载:如何优雅实现默认视图显示?

优化Prism WPF按需模块加载与视图导航的方案

我来给你几个实用的优化思路,既能解决你当前依赖约定命名(模块名+View)的不严谨问题,又能优雅地获取模块的额外信息,还完全贴合Prism的设计原则:


方案一:自定义模块特性 + 反射读取(无需重写DirectoryModuleCatalog)

这个方案最直接,不需要改动Prism的模块发现逻辑,只是利用它已经获取到的模块类型信息,通过反射读取自定义特性里的元数据。

步骤1:定义自定义元数据特性

先创建一个特性类,用来存储你需要的模块信息:

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ModuleMetadataAttribute : Attribute
{
    // 默认视图名称
    public string DefaultViewName { get; set; }
    // 按钮显示的友好名称
    public string DisplayName { get; set; }
    // 还可以扩展其他字段,比如图标路径、模块描述等
}

步骤2:在模块类上标记特性

每个按需加载的模块,同时保留原有的[Module]特性,加上自定义的元数据特性:

[Module(OnDemand = true)]
[ModuleMetadata(DefaultViewName = "CustomerListView", DisplayName = "客户管理")]
public class CustomerModule : IModule
{
    private readonly IRegionManager _regionManager;

    public CustomerModule(IRegionManager regionManager)
    {
        _regionManager = regionManager;
    }

    public void Initialize()
    {
        // 注册视图到目标区域(这一步不能少)
        _regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(CustomerListView));
    }
}

步骤3:模块发现时缓存元数据

在初始化DirectoryModuleCatalog后,遍历模块信息,通过反射读取特性并缓存:

// 先定义一个类存储带元数据的模块信息
public class ModuleInfoWithMetadata
{
    public string ModuleName { get; set; }
    public string DefaultViewName { get; set; }
    public string DisplayName { get; set; }
}

// 替换原来的_availableModules为这个类型的集合
private readonly List<ModuleInfoWithMetadata> _availableModules = new();

// 模块发现逻辑
var modulesCatalog = new DirectoryModuleCatalog { ModulePath = @"C:\WhereTheModulesLive" };
modulesCatalog.Initialize();

foreach (var module in modulesCatalog.Modules)
{
    // 通过ModuleType获取模块类的特性
    var metadataAttr = module.ModuleType.GetCustomAttribute<ModuleMetadataAttribute>();
    
    if (metadataAttr != null)
    {
        _availableModules.Add(new ModuleInfoWithMetadata
        {
            ModuleName = module.ModuleName,
            DefaultViewName = metadataAttr.DefaultViewName,
            DisplayName = metadataAttr.DisplayName
        });
    }
    else
    {
        // 兼容未标记特性的模块, fallback到原有的约定命名
        _availableModules.Add(new ModuleInfoWithMetadata
        {
            ModuleName = module.ModuleName,
            DefaultViewName = module.ModuleName + "View",
            DisplayName = module.ModuleName
        });
    }
}

步骤4:按钮点击时使用元数据导航

现在不需要再拼接字符串,直接用缓存的元数据:

private void LoadAndDisplayModule(ModuleInfoWithMetadata moduleInfo)
{
    _moduleManager.LoadModule(moduleInfo.ModuleName);
    // 注意:这里不需要手动调用_moduleManager.Run(),LoadModule会自动触发模块初始化
    _regionManager.RequestNavigate(RegionNames.ContentRegion, moduleInfo.DefaultViewName);
}

方案二:复用Prism的ModuleInfo.CustomProperties

如果你不想额外定义DTO类,可以直接用Prism自带的ModuleInfo对象里的CustomProperties字典来存储元数据,更轻量。

步骤1:模块发现时填充CustomProperties

// 直接缓存ModuleInfo对象即可
private readonly List<ModuleInfo> _availableModules = new();

var modulesCatalog = new DirectoryModuleCatalog { ModulePath = @"C:\WhereTheModulesLive" };
modulesCatalog.Initialize();

foreach (var module in modulesCatalog.Modules)
{
    var metadataAttr = module.ModuleType.GetCustomAttribute<ModuleMetadataAttribute>();
    
    if (metadataAttr != null)
    {
        module.CustomProperties["DefaultViewName"] = metadataAttr.DefaultViewName;
        module.CustomProperties["DisplayName"] = metadataAttr.DisplayName;
    }
    else
    {
        module.CustomProperties["DefaultViewName"] = module.ModuleName + "View";
        module.CustomProperties["DisplayName"] = module.ModuleName;
    }
    
    _availableModules.Add(module);
}

步骤2:点击时读取字典信息

private void LoadAndDisplayModule(ModuleInfo module)
{
    _moduleManager.LoadModule(module.ModuleName);
    var viewName = module.CustomProperties["DefaultViewName"] as string;
    _regionManager.RequestNavigate(RegionNames.ContentRegion, viewName);
}

方案三:元数据服务注册(松耦合的Prism风格)

如果追求最纯正的Prism松耦合设计,让模块自己主动注册元数据,而不是外部反射读取,这个方案更合适。唯一需要注意的是:如果需要在模块未加载前获取按钮显示名称,这个方案需要结合方案一的反射读取(因为模块未加载时无法执行初始化逻辑)。

步骤1:定义元数据服务

public interface IModuleMetadataService
{
    void RegisterMetadata(string moduleName, string defaultViewName, string displayName);
    ModuleMetadata GetMetadata(string moduleName);
}

public class ModuleMetadata
{
    public string DefaultViewName { get; set; }
    public string DisplayName { get; set; }
}

// 实现服务,注册为单例
public class ModuleMetadataService : IModuleMetadataService
{
    private readonly Dictionary<string, ModuleMetadata> _metadataStore = new();

    public void RegisterMetadata(string moduleName, string defaultViewName, string displayName)
    {
        if (!_metadataStore.ContainsKey(moduleName))
        {
            _metadataStore[moduleName] = new ModuleMetadata
            {
                DefaultViewName = defaultViewName,
                DisplayName = displayName
            };
        }
    }

    public ModuleMetadata GetMetadata(string moduleName)
    {
        _metadataStore.TryGetValue(moduleName, out var metadata);
        // 兼容未注册的模块
        return metadata ?? new ModuleMetadata 
        { 
            DefaultViewName = moduleName + "View", 
            DisplayName = moduleName 
        };
    }
}

步骤2:注册服务到Prism容器

在Bootstrapper的ConfigureContainer方法里注册单例:

protected override void ConfigureContainer(ContainerRegistry containerRegistry)
{
    base.ConfigureContainer(containerRegistry);
    containerRegistry.RegisterSingleton<IModuleMetadataService, ModuleMetadataService>();
}

步骤3:模块初始化时注册元数据

每个模块在初始化时,通过注入的服务注册自己的元数据:

[Module(OnDemand = true)]
public class CustomerModule : IModule
{
    private readonly IModuleMetadataService _metadataService;
    private readonly IRegionManager _regionManager;

    public CustomerModule(IModuleMetadataService metadataService, IRegionManager regionManager)
    {
        _metadataService = metadataService;
        _regionManager = regionManager;
    }

    public void Initialize()
    {
        // 注册元数据
        _metadataService.RegisterMetadata(nameof(CustomerModule), "CustomerListView", "客户管理");
        // 注册视图
        _regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(CustomerListView));
    }
}

步骤4:按钮点击时获取元数据

private void LoadAndDisplayModule(string moduleName)
{
    _moduleManager.LoadModule(moduleName);
    var metadata = _metadataService.GetMetadata(moduleName);
    _regionManager.RequestNavigate(RegionNames.ContentRegion, metadata.DefaultViewName);
}

额外小提示

你现有代码里的_moduleManager.Run();是多余的,Prism的LoadModule方法会自动处理模块的初始化流程,调用Run()反而可能导致重复初始化,直接删掉就行。

内容的提问来源于stack exchange,提问作者Alan Sage

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 21:42:36