从零开始搭建Maui框架02-自动注册视图和视图模型 上一次我们创建了基础的Maui框架,现在我们进一步优化上次的框架。
我们在创建视图(View)和视图模型(ViewModel)后,需要再在模块(Module)中将这两者注册到容器中,比较麻烦,而且代码量也很大,还容易遗漏注册,最主要的是不够优雅,下面我们将实现视图和视图模型自动注册。
这个教程中用到的技术原理是反射(reflection)和特性(Attribute)以及扩展方法。
一、新增类库项目 为了后续更好的模块化,现在在解决方案中新增两个新的Maui类库项目 ,分别命名为Core和MauiLib
现在项目中有3个类库项目(Core、MauiLib、UI),1个Maui应用程序项目(MauiPrism9Demo)。
Core:存放项目基础框架代码,这次新增的主要代码都在其中。
MauiLib:存放项目中用到的第三方类库,像之前Prism.Maui、Prism.Ioc.Maui 等都放在这里,其他项目只需要引用这个项目,不需要重复导入第三方类库,同时也方便管理。
MauiPrism9Demo:应用程序入口,模块加载,应用程序配置都在这里。
UI:主要存放视图和视图模型。后面可以把主题也放这里。
应用程序的引用路径为:Maui -> Core -> UI-> MauiPrism9Demo,不要重复引用,以免造成循环引用。
二、新增特性Attribute 在Core类库中新增Attributes文件夹,并在其中新增一个类,命名为IocForRegionNavigationAttribute ,继承Attribute
1.在类中新增两个自动属性
1 2 3 4 5 6 7 8 9 public Type ViewModelType { get ; set ; } = viewModelType;public string ? RegisterName { get ; set ; } = registerName;
ViewModelType 是用来表示要注入到视图的ViewModel类型
RegisterName是注册的名字,区域导航的时候用到,如果为空则使用View的name
2.添加构造函数,将两个自动属性通过构造赋值。我这里使用了C#12中的主构函数。
1 public class IocForRegionNavigationAttribute (Type viewModelType, string ? registerName = null ) : Attribute
3.完整的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 namespace Core.Attributes { [AttributeUsage(AttributeTargets.Class) ] public class IocForRegionNavigationAttribute (Type viewModelType, string ? registerName = null ) : Attribute { public Type ViewModelType { get ; set ; } = viewModelType; public string ? RegisterName { get ; set ; } = registerName; } }
代码中的AttributeUsage特性是指示当前这个特性(IocForRegionNavigationAttribute)只能用在类上。
三、使用特性 特性官方文档
在UI类库中,找到我们的视图(View),在视图的隐藏代码上添加IocForRegionNavigation特性,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 using Core.Attributes;using UI.ViewModels;namespace UI.Views ; [IocForRegionNavigation(typeof(ViewAViewModel)) ]public partial class ViewA : ContentView { public ViewA () { InitializeComponent(); } }
这里我们将ViewAViewModel作为ViewA的ViewModel
四、编写容器扩展 扩展方法的官方文档
在Core类库中新增Extensions文件夹,用来存放扩展方法。
新增静态类,命名ContainerExtension
类中新增RegisterAllViewModel扩展方法,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 using Core.Attributes;using System.Reflection;namespace Core.Extensions { public static class ContainerExtension { public static void RegisterAllViewModel (this IContainerRegistry container, Assembly assembly ) { var types = assembly.GetTypes().Where(c => c.BaseType == typeof (ContentView)); foreach (var type in types) { var attribute = type.GetCustomAttribute<IocForRegionNavigationAttribute>(); if (attribute != null ) { var viewModelType = attribute.ViewModelType; var registerName = attribute.RegisterName ?? type.Name; container.RegisterForRegionNavigation(type, viewModelType, registerName); } } } } }
var types = assembly.GetTypes().Where(c => c.BaseType == typeof(ContentView));是为了获取程序集中所有的ContentView,因为我们的特性都是放在ContentView上的,它承载了应用的UI。
我们这里找到所有使用了IocForRegionNavigationAttribute特性的ContentView,并根据ViewModelType注册进容器。
五、使用扩展方法 在UI类库中,修改UIModul
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using Core;using Core.Extensions;using System.Reflection;using UI.Views;namespace UI { public class UiModule (IRegionManager regionManager ) : IModule { #region Implementation of IModule public void RegisterTypes (IContainerRegistry container ) { container.RegisterAllViewModel(Assembly.GetAssembly(typeof (UiModule))!); } public void OnInitialized (IContainerProvider containerProvider ) { regionManager.RegisterViewWithRegion(RegionNames.MainRegion, nameof (ViewA)); } #endregion } }
在这里我们注释掉了原来的视图注册方法,直接调用container.RegisterAllViewModel(Assembly.GetAssembly(typeof(UiModule))!);进行注册。
其他代码不需要改动。
自此,所有新建在Ui类库中的视图,只要使用了IocForRegionNavigationAttribute ,程序都会自动在加载的时候将视图注入到容器中。