MAUI 中使用 DI 及 MVVM
- 为什么要使用 依赖注入 和 MVVM
- 如何在 MAUI 中使用依赖注入
- 如何使用 MVVM
- 不使用框架或组件
- 定义一个 BaseViewModel
- MainViewModel 的实现
- MainPage 中进行 Binding
- 使用组件优化前面的 ViewModel 代码
- 基项目的效果
为什么要使用 依赖注入 和 MVVM
MVVM 和 依赖注入 有助于开发出松耦合可维护以及可测试的应用程序。
如何在 MAUI 中使用依赖注入
依赖注入在MAUI中是原生支持的。
创建一个MAUI APP项目,根目录会有MauiProgram.cs
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// 注入页面
builder.Services.AddTransient<MainPage>();
return builder.Build();
}
- 在 builder 中注入需要的页面或服务即可
如何使用 MVVM
不使用框架或组件
- 先说下不使用任何框架或组件是如何实现的。
定义一个 BaseViewModel
BaseViewModel
会被后来的viewmodel
所继承,并且所有的公共属性、方法都将再次进行声明。
BaseViewModel
需要继承和实现INotifyPropertyChanged
才能够实现view
和viewmodel
之间的通讯。
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace CrossPlatformTools.ViewModel
{
public class BaseViewModel : INotifyPropertyChanged
{
private bool isBusy;
public bool IsBusy
{
get => isBusy;
set
{
if (isBusy == value)
return;
isBusy = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsNoteBusy));
}
}
public bool IsNoteBusy => !IsBusy;
private string title;
public string Title
{
get => title;
set
{
if (title == value)
return;
title = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string name = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
MainViewModel 的实现
MainViewModel
继承BaseViewModel
但是MainViewModel
中独有的属性和方法还是需要的MainViewModel
中进行声明的。
namespace CrossPlatformTools.ViewModel
{
public class MainViewModel : BaseViewModel
{
private int count = 0;
public int Count
{
get { return count; }
set
{
if (count == value)
return;
count = value;
OnPropertyChanged();
}
}
public Command CountCommand { get; }
public MainViewModel()
{
Title = "首页";
CountCommand = new Command(OnCounter);
}
private void OnCounter()
{
Count++;
}
}
}
在 BaseViewModel 声明的属性 MainViewModel 中就可以直接使用了。
Command
的声明相比于通知属性就简便很多。声明Command
然后在构造函数中调用即可。
MainPage 中进行 Binding
在 xaml 中需要引入命名空间:
xmlns:viewmodel="clr-namespace:CrossPlatformTools.ViewModel"
只引入命名空间这时想要使用属性是无法提示的,还需要设置DataType
:
x:DataType="viewmodel:MainViewModel"
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="CrossPlatformTools.View.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodel="clr-namespace:CrossPlatformTools.ViewModel"
Title="{Binding Title}"
x:DataType="viewmodel:MainViewModel">
<Grid>
<VerticalStackLayout
Padding="30,0"
Spacing="25"
VerticalOptions="Center">
<Image
HeightRequest="200"
HorizontalOptions="Center"
SemanticProperties.Description="Cute dot net bot waving hi to you!"
Source="dotnet_bot.png" />
<Label
FontSize="18"
HorizontalOptions="Center"
SemanticProperties.Description="Welcome to dot net Multi platform App U I"
SemanticProperties.HeadingLevel="Level2"
Text="Welcome to .NET Multi-platform App UI" />
<Button
Command="{Binding CountCommand}"
HorizontalOptions="Center"
SemanticProperties.Hint="Counts the number of times you click"
Text="{Binding Count}" />
</VerticalStackLayout>
</Grid>
</ContentPage>
你以为这样就完了吗,当然不是,只在xaml中引入命名空间进行
Command
和Property
的Binding 是不行了,还要进行上下文绑定。
using CrossPlatformTools.ViewModel;
namespace CrossPlatformTools.View;
public partial class MainPage : ContentPage
{
public MainPage(MainViewModel mainViewModel)
{
InitializeComponent();
BindingContext = mainViewModel;
}
}
这里有的一不同点,一般来说
BindingContext
是这样做的BindingContext = new MainViewModel()
但是我这里使用的是构造函数注入。
不能直接使用 需要在MauiProgram.cs
中注入才能使用。
使用组件优化前面的 ViewModel 代码
原生的方式实现 MVVM 时,
ViewModel
中的属性定义非常需要很多的代码,并且要手动实现PropertyChanged
事件。
- 使用
CommunityToolkit.Mvvm
实现
官方描述:
此包包含一个.NET MVVM库,其中包含以下帮助程序:
- ObservableObject:实现INotifyPropertyChanged接口的对象的基类。
- ObservableRecipient:支持IMessenger服务的可观察对象的基类。
- ObservableValidator:实现INotifyDataErrorInfo接口的对象的基类。
- RelayCommand:一个实现ICommand接口的简单委托命令。
- AsyncRelayCommand:支持异步操作和取消的委托命令。
- WeakReferenceMessenger:通过不同的松散耦合对象交换消息的消息传递系统。
- StrongReferenceMessenger:一个高性能的消息传递系统,以弱引用换取速度。
- Ioc:用于配置依赖注入服务容器的助手类。
- CommunityToolkit.Mvvm 是基于 .NET Standard 实现,理论上适用于所有 .NET 平台应用。
需要引入
CommunityToolkit.Mvvm.ComponentModel
命名空间
使用partial
class 标识符,CommunityToolkit.Mvvm
内部实现使用的是源生成器,所有需要使用partial
进行标识。
变量的声明必须使用驼峰式命名规则,才可以与生成的以 Pascal 命名规则的属性名不重复。
-
关于 源生成器 不明白的可以去看前面的文章 C# 源代码生成器 讲解了 源生成器的用法。
-
基类就变成了下面这样,少了很多的代码
using CommunityToolkit.Mvvm.ComponentModel;
namespace CrossPlatformTools.ViewModel
{
public partial class BaseViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(isNoteBusy))]
bool isBusy;
bool isNoteBusy => !isBusy;
[ObservableProperty]
string title;
}
}
- 同样的 MainViewModel 代码也是要进行更改的,精简了很多。
using CommunityToolkit.Mvvm.ComponentModel;
namespace CrossPlatformTools.ViewModel
{
public partial class MainViewModel : BaseViewModel
{
[ObservableProperty]
private int count = 0;
public Command CountCommand { get; }
public MainViewModel()
{
Title = "首页";
CountCommand = new Command(OnCounter);
}
private void OnCounter()
{
Count++;
}
}
}