-
我们继续上一个案例,实现一个可以修改当前用户信息功能。
当用户点击某个信息时,跳转到信息详情页,然后可以点击编辑按钮导航到编辑页面。
创建项目
-
我们首先在ViewModels目录下创建UserDetailViewModel。
-
实现从详情信息页面导航到编辑页面。
-
这里要使用一个字典来传输对象。
cs
public partial class UserDetailViewModel:ObservableObject,IQueryAttributable
{
[ObservableProperty] private User itemUser;
public Func<User, Task> ParentRefreshAction { get;set; }
[RelayCommand]
async Task ShowEditFormAsync()
{
var context = new UserContext();
var editedItem = context.Users.FirstOrDefault(u => u.Id == ItemUser.Id);
await Shell.Current.GoToAsync(nameof(UserEditPage),
parameters: new Dictionary<string, object>
{
{ "ParentRefreshAction", (Func<User, Task>)ItemEditedAsync },
{ "Item", editedItem }
});
}
async Task ItemEditedAsync(User user) {
ItemUser = user;
await ParentRefreshAction(user);
}
public virtual void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.TryGetValue("Item",out object currentItem))
{
ItemUser = (User)currentItem;
}
if (query.TryGetValue("ParentRefreshAction",out object parentRefreshAction))
{
ParentRefreshAction = (Func<User, Task>)parentRefreshAction;
}
query.Clear();
}
}
-
创建用户详情页面,用来显示用户的全部信息。
-
在ToolbarItem中添加一个命令导航到编辑页面。
XML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MauiApp3.ViewModels"
Title="用户详情"
x:Class="MauiApp3.Views.UserDetailPage">
<ContentPage.BindingContext>
<vm:UserDetailViewModel/>
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="编辑" Command="{Binding ShowEditFormCommand}"/>
</ContentPage.ToolbarItems>
<VerticalStackLayout>
<Label Text="{Binding ItemUser.Id}" FontSize="Large"/>
<Label Text="{Binding ItemUser.Name}" FontSize="Large"/>
<Label Text="{Binding ItemUser.Phone}" FontSize="Large"/>
<Label Text="{Binding ItemUser.Email}" FontSize="Large"/>
</VerticalStackLayout>
</ContentPage>
- 更新UserEditViewModel,我们让他直接继承自UserDetailViewModel.
cs
public partial class UserEditViewModel:UserDetailViewModel
{
[ObservableProperty] private bool isNewItem;
[RelayCommand]
private async Task SaveAsync()
{
await using var context = new UserContext();
if (IsNewItem)
{
context.Users.Add(ItemUser);
}
else
{
context.Users.Attach(ItemUser);
context.Entry(ItemUser).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
}
context.SaveChangesAsync();
await ParentRefreshAction(ItemUser);
await Shell.Current.GoToAsync("..");
}
public override void ApplyQueryAttributes(IDictionary<string, object> query) {
if (query.TryGetValue("IsNewItem", out object isNew)) {
IsNewItem = (bool)isNew;
}
base.ApplyQueryAttributes(query);
}
}
- 修改MainViewModel。
cs
public partial class MainViewModel:ObservableObject
{
[ObservableProperty]
ObservableCollection<User> users;
[ObservableProperty] private bool refreshing;
[RelayCommand]
private async Task LoadUsersAsync()
{
await Task.Run(() =>
{
using var context = new UserContext();
Users = new ObservableCollection<User>(context.Users);
});
Refreshing = false;
}
[RelayCommand]
private void Showing()
{
Refreshing = true;
}
[RelayCommand]
private void DeleteUser(User user)
{
var context = new UserContext();
context.Users.Remove(user);
context.SaveChanges();
Users.Remove(user);
}
[RelayCommand]
private async Task ShowNewFormAsync()
{
await Shell.Current.GoToAsync(nameof(UserEditPage),parameters:new Dictionary<string, object>
{
{"ParentRefreshAction",(Func<User,Task>)RefreshAddedAsync},
{"Item",new User()},
{"IsNewItem",true}
});
}
Task RefreshAddedAsync(User addedUser)
{
Users.Add(addedUser);
return Task.CompletedTask;
}
[RelayCommand]
async Task ShowDetailFormAsync(User user)
{
await Shell.Current.GoToAsync(nameof(UserDetailPage),parameters:new Dictionary<string, object>
{
{"ParentRefreshAction",(Func<User,Task>)RefreshEditedAsync},
{"Item",user},
});
}
async Task RefreshEditedAsync(User updataUser)
{
int editedItemIndex = -1;
await Task.Run(() =>
{
editedItemIndex = Users.Select(
(user, index) => new { user, index })
.First(x => x.user.Id == updataUser.Id)
.index;
});
if (editedItemIndex==-1)
{
return;
}
Users[editedItemIndex] = updataUser;
}
}
- 注册路由
cs
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(UserEditPage),typeof(UserEditPage));
Routing.RegisterRoute(nameof(UserDetailPage),typeof(UserDetailPage));
}
}
- 在主页添加GestureRecognizers,对详情页面的跳转
XML
<Grid RowDefinitions="40,40"
ColumnDefinitions="*,*"
Padding="10">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.ShowDetailFormCommand,Source={RelativeSource Mode=FindAncestor,AncestorType={x:Type ContentPage}}}"
CommandParameter="{Binding}"/>
</Grid.GestureRecognizers>
IOS下运行程序
我们实现了查看用户详情,并且可以修改
优化代码
-
我们要在数据库和视图模型之间提供一个抽象层,它能使项目有不同的模块区分,更明确的分离开。
-
在项目中往往需要对多张表进行操作,我们创建一个泛型接口,来抽象对数据库中CURD。
-
在Models中添加一个IRepository.cs文件
cs
public interface IRepository<T> where T:class
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T item);
Task UpdateAsync(T item);
Task DeleteAsync(T item);
}
- 实现接口
cs
public class UserRepository: IRepository<User>
{
private readonly DbSet<User> DbSet;
private readonly UserContext Context;
public UserRepository(UserContext context)
{
Context = context;
DbSet = Context.Set<User>();
}
public async Task<User> GetByIdAsync(int id)
{
return await Task.Run(() => DbSet.Find(id));
}
public async Task<IEnumerable<User>> GetAllAsync()
{
return await Task.Run(() => DbSet.ToList());
}
public async Task AddAsync(User item)
{
DbSet.Add(item);
await Task.CompletedTask;
}
public async Task UpdateAsync(User item)
{
DbSet.Attach(item);
Context.Entry(item).State = EntityState.Modified;
await Task.CompletedTask;
}
public async Task DeleteAsync(User item)
{
DbSet.Remove(item);
await Task.CompletedTask;
}
- 创建工作单元,它的作用主要是作用于不同的数据表。
cs
public class DbUnitOfWork:IDisposable,IUnitOfWork<User>
{
readonly UserContext Context=new UserContext();
private IRepository<User> userRepository;
public IRepository<User> Items => userRepository ??= new UserRepository(Context);
public void Dispose()
{
Context.Dispose();
}
public async Task SaveAsync()
{
await Task.Run(() => Context.SaveChangesAsync());
}
}
public interface IUnitOfWork<T> where T : class {
IRepository<T> Items { get; }
Task SaveAsync();
}
修改MainViewModel
- LoadUserAsync
cs
[RelayCommand]
private async Task LoadUsersAsync()
{
using var uniOfWork = new DbUnitOfWork();
Users = new ObservableCollection<User>(await uniOfWork.Items.GetAllAsync());
Refreshing = false;
}
- DeleteUserAsync
cs
[RelayCommand]
private async Task DeleteUser(User user)
{
using var uniOfWork = new DbUnitOfWork();
await uniOfWork.Items.DeleteAsync(user);
await uniOfWork.SaveAsync();
Users.Remove(user);
}
修改CustomerEditViewModel
- SaveAsync
cs
[RelayCommand]
private async Task SaveAsync()
{
using var unitOfWork = new DbUnitOfWork();
if (IsNewItem)
await unitOfWork.Items.AddAsync(ItemUser);
else
await unitOfWork.Items.UpdateAsync(ItemUser);
await unitOfWork.SaveAsync();
await ParentRefreshAction(ItemUser);
await Shell.Current.GoToAsync("..");
}
修改UserDetailViewModel
- ShowEditFormAsync
cs
[RelayCommand]
async Task ShowEditFormAsync()
{
using var unitOfWork = new DbUnitOfWork();
var editedItem = await unitOfWork.Items.GetByIdAsync(ItemUser.Id);
await Shell.Current.GoToAsync(nameof(UserEditPage),
parameters: new Dictionary<string, object>
{
{ "ParentRefreshAction", (Func<User, Task>)ItemEditedAsync },
{ "Item", editedItem }
});
}
数据库验证错误
-
很多时候我们需要对用户输入的数据进行验证,有很多方法和形式,我们来看看在数据库层面如何做错误验证处理。并反馈在页面给用户。
-
我们在UserContext中进行简单的数据约束
-
使用try catch来捕获异常
cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//用户邮箱唯一
modelBuilder.Entity<User>().HasIndex(u => u.Email).IsUnique();
//用户名不能为空
modelBuilder.Entity<User>().Property(u => u.Name).IsRequired();
//初始化数据
modelBuilder.Entity<User>().HasData(new User
{
Id = 1,
Name = "张三",
Email = "张三@163.com",
Phone = "123456789"
});
base.OnModelCreating(modelBuilder);
}
修改MainViewModel
- DeleteCustomerAsync
cs
[RelayCommand]
private async Task DeleteUserAsync(User user)
{
using var uniOfWork = new DbUnitOfWork();
try
{
await uniOfWork.Items.DeleteAsync(user);
await uniOfWork.SaveAsync();
}
catch (Exception ex)
{
await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
return;
}
Users.Remove(user);
}
修改UserEditViewModel
- SaveAsync
cs
[RelayCommand]
private async Task SaveAsync()
{
using var unitOfWork = new DbUnitOfWork();
try
{
if (IsNewItem)
await unitOfWork.Items.AddAsync(ItemUser);
else
await unitOfWork.Items.UpdateAsync(ItemUser);
await unitOfWork.SaveAsync();
}
catch (Exception ex)
{
await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
return;
}
await ParentRefreshAction(ItemUser);
await Shell.Current.GoToAsync("..");
}
IOS下运行程序
- 这里如果用户没有添加用户名,程序会提出错误信息。
在UI中验证数据
- 程序中,在将数据提交到数据库之前可以在UI层执行某些验证规则,可以在用户保存某些修改时告知用户,从而改善用户体验。
修改UserEditViewModel
- 在编辑用户页面我们添加对用户名和邮箱的验证,并关联到保存命令上。
cs
[NotifyCanExecuteChangedFor(nameof(SaveCommand))] [ObservableProperty]
private bool isEmailValid;
[NotifyCanExecuteChangedFor(nameof(SaveCommand))] [ObservableProperty]
private bool isNameValid;
bool CanSave() => IsEmailValid&& IsNameValid;
[RelayCommand(CanExecute = nameof(CanSave))]
private async Task SaveAsync()
{
using var unitOfWork = new DbUnitOfWork();
try
{
if (IsNewItem)
await unitOfWork.Items.AddAsync(ItemUser);
else
await unitOfWork.Items.UpdateAsync(ItemUser);
await unitOfWork.SaveAsync();
}
catch (Exception ex)
{
await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
return;
}
await ParentRefreshAction(ItemUser);
await Shell.Current.GoToAsync("..");
}
-
在UserEditPage文件中,我们添加一个样式来反馈给用户,并使用工具包中的ValidationBehavior来进行验证和绑定命令。
-
这个验证很简单,当用户输入的内容不满足条件时,会使用我们设置的样式颜色来显示,保存按钮无法点击,当满足条件时颜色变为正常并可以保存内容。
XML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MauiApp3.ViewModels;assembly=MauiApp3"
xmlns:toolkit = "http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="MauiApp3.Views.UserEditPage"
Title="新用户">
<ContentPage.BindingContext>
<vm:UserEditViewModel/>
</ContentPage.BindingContext>
<ContentPage.Resources>
<Style TargetType="Entry" x:Key="invalidEntryStyle">
<Setter Property="TextColor" Value="Red"></Setter>
</Style>
</ContentPage.Resources>
<Grid>
<VerticalStackLayout VerticalOptions="Start">
<Entry Placeholder="用户名"
Text="{Binding ItemUser.Name}">
<Entry.Behaviors>
<toolkit:TextValidationBehavior
InvalidStyle="{StaticResource invalidEntryStyle}"
IsValid="{Binding IsNameValid}"
Flags="ValidateOnValueChanged,ValidateOnAttaching"
//内容长度不能小于5
MinimumLength="5"/>
</Entry.Behaviors>
</Entry>
<Entry Placeholder="电话"
Text="{Binding ItemUser.Phone}"/>
<Entry Placeholder="Email"
Text="{Binding ItemUser.Email}"
ReturnCommand="{Binding SaveCommand}">
<Entry.Behaviors>
//验证是否时正常的邮箱格式
<toolkit:EmailValidationBehavior
InvalidStyle="{StaticResource invalidEntryStyle}"
IsValid="{Binding IsEmailValid}"
Flags="ValidateOnValueChanged,ValidateOnAttaching"/>
</Entry.Behaviors>
</Entry>
<Button Text="保存"
Command="{Binding SaveCommand}"/>
</VerticalStackLayout>
<ActivityIndicator
VerticalOptions="Center"
HorizontalOptions="Center"
IsRunning="{Binding SaveCommand.IsRunning}"/>
</Grid>
</ContentPage>