Blazor学习之旅(9)用MudBlazor重构Todo
大家好,我是Edison。
在之前的学习之旅(3)开发一个Todo应用中,我们开发了一个简单版的Todo,这次我们基于MudBlazor来重构这个Todo应用。
Todo V1回顾
在Blazor入门学习(3)文章中,我们基于Blazor实现了一个简单版的Todo应用,它的效果如下:
(1)加载Todo列表
(2)添加新的Todo事项
可以看到,它仅仅实现了最基本的效果,但是如果涉及到分页、修改等操作,现有的界面就无法满足了。
因此,我们基于对MudBlazor组件库的了解,使用MudBlazor来重构一下这个Todo应用。
Todo V2规划
我们首先来做一个规划,期望效果是:
(1)能够有一个分页列表,能够将MongoDB中的数据读取出来并展示;
(2)能够针对Todo Name进行筛选查询;
(3)能够有一个弹出框进行新增Todo Item;
(4)能够有一个弹出框对选中的Todo Item进行修改;
(5)能够对选中的Todo Item进行删除,但要先给出确认删除的提示;
最后,新增、修改和删除操作成功都需要给出提示信息;
这里,我们以终为始,先来看看重构后的效果:
(1)分页列表展示
(2)根据Todo Item Name进行搜索
(3)新增TodoItem
(4)修改TodoItem
(5)删除TodoItem
Todo V2重构
(1)准备工作
在NavMenu.razor中新增一个导航菜单,名为TodoV2
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
.....
<div class="nav-item px-3">
<NavLink class="nav-link" href="todo">
<span class="oi oi-list-rich" aria-hidden="true"></span> Todo
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="todov2">
<span class="oi oi-list-rich" aria-hidden="true"></span>Todo v2
</NavLink>
</div>
</nav>
</div>
(2)重构Todo列表页
在Pages目录下新增一个razor组件:TodoV2.razor,代码如下:
@page "/todov2"
@using EDT.Todo.Application.Models.VO <PageTitle>Todo v2</PageTitle> <MudTable Items="@todos" Dense="true" Bordered="true" Striped="true" Hover="true"
Breakpoint="Breakpoint.Sm" Filter="new Func<TodoItemVO, bool>(FilterTodoItems)"
@bind-SelectedItem="selectedTodoItem">
<ToolBarContent>
<MudText Typo="Typo.h6">My TodoItems (Total: @todos.Count(), NotFinished: @todos.Count(todo => !todo.IsComplete))</MudText>
<MudSpacer />
<MudTextField @bind-Value="searchKeyword" Placeholder="Search" Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0" />
<MudSpacer />
<MudButton OnClick="@OpenCreateTodoDialog" Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Create" Color="Color.Primary">
Create New
</MudButton>
</ToolBarContent>
<HeaderContent>
<MudTh>Id</MudTh>
<MudTh>Name</MudTh>
<MudTh>Category</MudTh>
<MudTh>IsComplete</MudTh>
<MudTh>CheckItems</MudTh>
<MudTh>Action</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Id">@context.Id</MudTd>
<MudTd DataLabel="Name">@context.Name</MudTd>
<MudTd DataLabel="Category">@context.Category</MudTd>
<MudTd DataLabel="IsComplete">
<MudSwitch @bind-Checked="@context.IsComplete" Color="Color.Primary" ReadOnly="true" />
</MudTd>
<MudTd DataLabel="CheckItems">@String.Join(";", context.CheckItems)</MudTd>
<MudTd DataLabel="Action">
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Update" Color="Color.Primary" OnClick="(async () => await OpenUpdateTodoDialog(context.Id))">Update</MudButton>
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Delete" Color="Color.Secondary" OnClick="(async () => await DeleteTodoItem(context.Id))">Delete</MudButton>
</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
</PagerContent>
</MudTable>
新增对应的C#代码类:TodoV2.razor.cs
using Microsoft.AspNetCore.Components;
using MongoDB.Driver;
using MudBlazor;
using EDT.Todo.Application.Contracts.Business;
using EDT.Todo.Application.Models.VO;
using EDT.Todo.Portal.Shared; namespace EDT.Todo.Portal.Pages
{
public partial class TodoV2
{
[Inject]
public ISnackbar Snackbar { get; set; }
[Inject]
public IDialogService DialogService { get; set; }
[Inject]
public ITodoItemService TodoItemService { get; set; }
[Inject]
public ILogger<TodoV2> Logger { get; set; } private IList<TodoItemVO> todos = Array.Empty<TodoItemVO>();
private string searchKeyword = string.Empty;
private TodoItemVO selectedTodoItem = null; protected override async Task OnInitializedAsync()
{
todos = await TodoItemService.GetTodoItems(ReadPreference.SecondaryPreferred);
} private bool FilterTodoItems(TodoItemVO todoItem)
=> FilterTodoItemDetail(todoItem, searchKeyword); private bool FilterTodoItemDetail(TodoItemVO todoItem, string searchString)
{
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (todoItem.Name.Contains(searchString))
return true;
if (todoItem.CheckItems.Contains(searchString))
return true; return false;
} private async Task DeleteTodoItem(string id)
{
var parameters = new DialogParameters();
parameters.Add("ContentText", "Do you really want to delete this todoitem?");
parameters.Add("ButtonText", "Delete");
parameters.Add("Color", Color.Error);
var options = new DialogOptions() { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
var dialog = DialogService.Show<ConfirmDialog>("Delete", parameters, options);
var result = await dialog.Result; if (result.Cancelled || result.Data == null)
return; try
{
// Delete TodoItem
await TodoItemService.DeleteTodoItem(id);
// Refresh Todos
todos = await TodoItemService.GetTodoItems(ReadPreference.PrimaryPreferred);
// Sucess Tip
Snackbar.Add("Delete todoitem success!", Severity.Success);
}
catch (Exception ex)
{
Logger.LogError(ex, $"An exception happened during DeleteTodoItem {id}");
// Failed Tip
Snackbar.Add("Delete todoitem failed!", Severity.Error);
}
} private async Task OpenCreateTodoDialog()
{
DialogOptions closeOnEscapeKey = new DialogOptions() { CloseOnEscapeKey = true };
var dialog = DialogService.Show<CreateTodoDialog>("Create Todo", closeOnEscapeKey);
var result = await dialog.Result; if (result.Cancelled || result.Data == null)
return; // Refresh Todos
todos = await TodoItemService.GetTodoItems(ReadPreference.PrimaryPreferred);
// Sucess Tip
Snackbar.Add("Create todoitem success!", Severity.Success);
} private async Task OpenUpdateTodoDialog(string todoItemId)
{
DialogOptions closeOnEscapeKey = new DialogOptions() { CloseOnEscapeKey = true };
var parameters = new DialogParameters { ["TodoItemId"] = todoItemId };
var dialog = DialogService.Show<UpdateTodoDialog>("Update Todo", parameters, closeOnEscapeKey);
var result = await dialog.Result; if (result.Cancelled || result.Data == null)
return; // Refresh Todos
todos = await TodoItemService.GetTodoItems(ReadPreference.PrimaryPreferred);
// Sucess Tip
Snackbar.Add("Update todoitem success!", Severity.Success);
}
}
}
在Todo列表页中,可以看到在Create和Update以及Delete时都进行了弹框操作,因此我们还需要实现几个Dialog。
(3)开发CreateTodoDialog
在CreateTodoDialog中,使用到了DialogContext 和 MudForm两个重要的标签,以很少的代码实现了一个原本需要用JS实现的对话框。
@using EDT.Todo.Domain.Enums <MudDialog>
<DialogContent>
<MudForm @ref="form" @bind-IsValid="@success">
<MudTextField T="string" Label="Name" @bind-Value="_todoItemDTO.Name"
Required="true" RequiredError="Name is required!" Immediate="true"></MudTextField>
<MudSelect T="Category" Label="Category" AnchorOrigin="Origin.BottomCenter"
@bind-Value="_todoItemDTO.Category">
@foreach (var category in Enum.GetValues<Category>())
{
<MudSelectItem T="Category" Value="category">@category.ToString()</MudSelectItem>
}
</MudSelect>
<MudSwitch @bind-Checked="@_todoItemDTO.IsComplete" Color="Color.Primary" Label="Is Completed" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="Color.Primary" Disabled="@(!success)" OnClick="@CreateTodoItem">Create</MudButton>
</DialogActions>
</MudDialog>
对应的C#代码类如下:CreateTodoDialog.razor.cs
public partial class CreateTodoDialog
{
[Inject]
public ITodoItemService TodoItemService { get; set; }
[CascadingParameter]
MudDialogInstance MudDialog { get; set; } private MudForm form;
private bool success = false;
private TodoItemDTO _todoItemDTO = new(); private void Cancel() => MudDialog.Cancel(); private async Task CreateTodoItem()
{
var result = await TodoItemService.CreateTodoItem(_todoItemDTO);
MudDialog.Close(result);
}
}
对于Dialog组件,默认需要一个级联参数MudDialogInstance,因此需要将其放在代码中。
此外,在此Dialog中还实现了调用Service类进行具体Create的操作。
(4)开发UpdateTodoDialog
开发完CreateTodoDialog后,UpdateTodoDialog就很简单了,复制过来改一下就OK。当然,你也可以将这两个操作放在同一个Dialog中进行。
@using EDT.Todo.Domain.Enums <MudDialog>
<DialogContent>
<MudForm @ref="form" @bind-IsValid="@success">
<MudTextField T="string" Label="Name" @bind-Value="_todoItemVO.Name"
Required="true" RequiredError="Name is required!" Immediate="true"></MudTextField>
<MudSelect T="Category" Label="Category" AnchorOrigin="Origin.BottomCenter"
@bind-Value="_todoItemVO.Category">
@foreach (var category in Enum.GetValues<Category>())
{
<MudSelectItem T="Category" Value="category">@category.ToString()</MudSelectItem>
}
</MudSelect>
<MudSwitch @bind-Checked="@_todoItemVO.IsComplete" Color="Color.Primary" Label="Is Completed" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="Color.Primary" Disabled="@(!success)" OnClick="@UpdateTodoItem">Update</MudButton>
</DialogActions>
</MudDialog>
对应的C#代码类如下:UpdateTodoDialog.razor.cs
public partial class UpdateTodoDialog
{
[Inject]
public ITodoItemService TodoItemService { get; set; }
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
[Parameter]
public string TodoItemId { get; set; } private MudForm form;
private bool success = false;
private TodoItemVO _todoItemVO = new(); private void Cancel() => MudDialog.Cancel(); private async Task UpdateTodoItem()
{
var todoItemDTO = new TodoItemDTO
{
Id = TodoItemId,
Name = _todoItemVO.Name,
Category = _todoItemVO.Category,
IsComplete = _todoItemVO.IsComplete
};
await TodoItemService.UpdateTodoItem(todoItemDTO);
MudDialog.Close(todoItemDTO);
} protected override async Task OnInitializedAsync()
{
if (string.IsNullOrWhiteSpace(TodoItemId))
return; _todoItemVO = await TodoItemService.GetTodoItem(TodoItemId, ReadPreference.PrimaryPreferred);
}
}
对于Update操作,需要Todo列表页将需要修改的TodoItem的Id作为参数传递过来,并在初始化的时候调用Service进行数据的获取 和 绑定。
(5)开发通用ConfirmDialog
对于ConfirmDialog而言,它本身并没有任何逻辑,而且可以被任意页面进行复用,只是提示的消息内容不同而已。
因此,我们在Shared目录下创建一个ConfirmDialog.razor:
<MudDialog>
<DialogContent>
<MudText>@ContentText</MudText>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="@Color" Variant="Variant.Filled" OnClick="Submit">@ButtonText</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; } [Parameter] public string ContentText { get; set; } [Parameter] public string ButtonText { get; set; } [Parameter] public Color Color { get; set; } void Submit() => MudDialog.Close(DialogResult.Ok(true));
void Cancel() => MudDialog.Cancel();
}
由于该页面代码很简单,我们就直接将其放在同一个razor中,不区分前后端的部分类。
小结
本篇,我们试着将之前的Todo应用使用MudBlazor来重构一下,相比之前会有一些互动了,但也仅仅是展示了最基本的界面。实际上,我们可以基于MudBlazor开发更加好看一点的界面和互动效果,这就等待你自己去探索了。
下一篇,我们学习在Blazor如何实现本地化及多语言支持。
参考资料
MudBlazor官网Doc文档
Blazor学习之旅(9)用MudBlazor重构Todo的更多相关文章
- WCF学习之旅—第三个示例之三(二十九)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) 在上一篇文章中我们创建了实体对象与接口协定,在这一篇文章中我们来学习如何创建WCF的服务端代码.具体步骤见下面. ...
- WCF学习之旅—第三个示例之五(三十一)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) WCF学习之旅—第三个示例之三(二十九) WCF学习 ...
- WCF学习之旅—实现支持REST服务端应用(二十三)
在上一篇(WCF学习之旅—实现REST服务(二十二))文章中简单介绍了一下RestFul与WCF支持RestFul所提供的方法,本文讲解一下如何创建一个支持REST的WCF服务端程序. 四.在WCF中 ...
- WCF学习之旅—TCP双工模式(二十一)
WCF学习之旅—请求与答复模式和单向模式(十九) WCF学习之旅—HTTP双工模式(二十) 五.TCP双工模式 上一篇文章中我们学习了HTTP的双工模式,我们今天就学习一下TCP的双工模式. 在一个基 ...
- WCF学习之旅—HTTP双工模式(二十)
WCF学习之旅—请求与答复模式和单向模式(十九) 四.HTTP双工模式 双工模式建立在上文所实现的两种模式的基础之上,实现客户端与服务端相互调用:前面介绍的两种方法只是在客户端调用服务端的方法,然后服 ...
- WCF学习之旅—基于Fault Contract 的异常处理(十八)
WCF学习之旅—WCF中传统的异常处理(十六) WCF学习之旅—基于ServiceDebug的异常处理(十七) 三.基于Fault Contract 的异常处理 第二个示例是通过定制Servic ...
- WCF学习之旅——第一个WCF示例(一)
最近需要用到WCF,所以对WCF进行了解.在实践中学习新知识是最快的,接下来先做了一个简单的WCF服用应用示例. 本文的WCF服务应用功能很简单,却涵盖了一个完整WCF应用的基本结构.希望本文能对那些 ...
- WCF学习之旅—第三个示例之四(三十)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) WCF学习之旅—第三个示例之三(二十九) ...
- Hadoop学习之旅二:HDFS
本文基于Hadoop1.X 概述 分布式文件系统主要用来解决如下几个问题: 读写大文件 加速运算 对于某些体积巨大的文件,比如其大小超过了计算机文件系统所能存放的最大限制或者是其大小甚至超过了计算机整 ...
- WCF学习之旅—第三个示例之二(二十八)
上接WCF学习之旅—第三个示例之一(二十七) 五.在项目BookMgr.Model创建实体类数据 第一步,安装Entity Framework 1) 使用NuGet下载最新版的Entity Fram ...
随机推荐
- javascript 判断浏览器
navigator.userAgent 通常我们可以通过navigator.userAgent只读属性来获取浏览器的一些信息,算是原生方法吧. jquery -jquery1.9 版本可以通过$.br ...
- mybatis-plus.global-config.db-config.id-type=auto 和 @TableId(value = "id", type = IdType.ASSIGN_ID)哪个优先生效
对于id自动生成的方式,有注解和配置两种. 含义相同:不过设置自动增长的时候必须保证数据库中id是自增,assign_id和assign_uuid则不需要. yml配置: mybatis-plus: ...
- 面试官:如果某个业务量突然提升100倍QPS你会怎么做?
"假设你负责的系统,某个业务线的QPS突然暴增100倍,你会怎么应对?" --这是上周朋友去面试,被问到一道题,他答了"加机器扩容",结果面试官眉头一皱:&qu ...
- Spring底层AOP代码实现
一. AOP功能测试 ①. pom.xml 依赖导入 ②. 目标类 ③. 切面类 ④. 配置类 ⑤. 测试类 二. AOP原理-@EnableAspectJAutoProxy AOP原理:[看给容器中 ...
- 🎀Charles激活
简介 Charles激活码计算 激活 Help -> Register Charles 添加 Registered Name 和计算出的 License key 点击 Register Java ...
- jmeter实现幂等测试的一种方法(案例)
最近在研究怎样对电商系统的业务进行幂等测试,利用jmeter对单独业务开展幂等测试可能简便.直接有效吧 场景描述:买家每一笔订单选中商品后,系统会生成一个"ShopCartIds" ...
- 如何对 Java 的垃圾回收进行调优?
如何对 Java 的垃圾回收进行调优? Java 垃圾回收的调优涉及多个方面,从选择合适的垃圾回收器到调整堆内存的大小.配置 GC 参数等,下面是一些常见的调优方法: 1. 选择合适的垃圾回收器 不同 ...
- jwt的个人理解
概念: jwt全名json web token,是一种web登录验证和授权技术 官网debug:#debug 应用场景: 授权这是使用JWT最常见的场景.一旦用户登录,每个后续请求将包括JWT,允许用 ...
- AutoFac(三)——装配扫描(批量注册之扫描类型)
一.装配扫描 Autofac允许通过常规组装的方式去注册组件(构造方法.实例.lambda表达式等),您可以扫描和注册单个类型,也可以具体的扫描Autofac模块去注册. 1.扫描类型 除了已知的的常 ...
- Sentinel——熔断规则
目录 熔断规则 慢调用比例 慢比例调用代码实现 自定义异常处理器(返回响应流) 自定义异常处理类 测试 自定义异常处理器(返回页面) 异常处理器 定义页面 测试 熔断规则 现代微服务架构都是分布式的, ...