Blazor学习之旅(3)实现一个Todo应用
最近在学习Blazor做全栈开发,因此根据老习惯,我会将我的学习过程记录下来,一来体系化整理,二来作为笔记供将来翻看。
本篇,我们通过一个简单的Todo示例应用来介绍如何实现基础的数据绑定和事件。
添加Todo组件
在Pages目录下,新增一个Razor组件,命名:Todo.razor
@page "/todo"
<h3>Todo</h3>
@code {
}
将Todo组件添加到导航栏
我们知道,在Shared目录下的NavMenu组件用于应用的导航,因此我们需要将Todo组件加进去以便可以访问到:
<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>
</nav>
</div>
这时导航栏中也就有Todo了:

添加Model
添加一个Models目录,在此目录下新建一个TodoItem类:
namespace EDT.BlazorServer.App.Models
{
public class TodoItem
{
public string Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Remark { get; set; }
}
}
为了模拟实现数据库访问的效果,这里我们使用EF Core的内存数据库来模拟。
首先,添加对Microsoft.EntityFrameworkCore.InMemory的应用。
其次,在Models目录下创建一个TodoContext类:
using Microsoft.EntityFrameworkCore; namespace EDT.BlazorServer.App.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
} public DbSet<TodoItem> TodoItems { get; set; }
}
}
然后,在Program.cs中注入这个DbContext:
// Add database context
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
添加种子数据
为了方便演示,我们提前准备一些SeedData,创建一个SeedData的静态类:
namespace EDT.BlazorServer.App.Models
{
public static class SeedData
{
public static void Initialize(TodoContext db)
{
var todos = new TodoItem[]
{
new TodoItem { Id = Guid.NewGuid().ToString(), Name = "Study Computer Network", IsComplete=false, Remark = "Take a Test" },
new TodoItem { Id = Guid.NewGuid().ToString(), Name = "Study Operation System", IsComplete=false, Remark = "Take a Test" },
new TodoItem { Id = Guid.NewGuid().ToString(), Name = "Study Data Structure", IsComplete=false, Remark = "Take a Test" },
new TodoItem { Id = Guid.NewGuid().ToString(), Name = "Walk the dog", IsComplete=true, Remark = string.Empty },
new TodoItem { Id = Guid.NewGuid().ToString(), Name = "Run 5km in 40mins", IsComplete=true, Remark = string.Empty },
}; db.TodoItems.AddRange(todos);
db.SaveChanges();
}
}
}
然后,在Program.cs中确保运行这个初始化操作:
添加Service
假设我们所有的TodoItem都是通过Service来完成的,不直接在Pages下的组件中来操作。
首先,创建一个接口ITodoItemService:
using EDT.BlazorServer.App.Models; namespace EDT.BlazorServer.App.Service.Contracts
{
public interface ITodoItemService
{
Task<IList<TodoItem>> GetTodoItemsAsync();
Task<TodoItem> GetTodoItemAsync(string id);
Task<TodoItem> AddTodoItemAsync(TodoItem todoItem);
Task<TodoItem> UpdateTodoItemAsync(TodoItem todoItem);
Task<TodoItem> DeleteTodoItemAsync(TodoItem todoItem);
}
}
这时,我们重新启动应用就可以看到Counter组件显示在主页上面了:

其次,实现TodoItemService:
using EDT.BlazorServer.App.Models;
using EDT.BlazorServer.App.Service.Contracts;
using Microsoft.EntityFrameworkCore; namespace EDT.BlazorServer.App.Service
{
public class TodoItemService : ITodoItemService
{
private readonly TodoContext _todoContext; public TodoItemService(TodoContext todoContext)
{
_todoContext = todoContext;
} public async Task<TodoItem> AddTodoItemAsync(TodoItem todoItem)
{
todoItem.Id = Guid.NewGuid().ToString();
todoItem.IsComplete = false; _todoContext.TodoItems.Add(todoItem);
await _todoContext.SaveChangesAsync(); return todoItem;
} public async Task<TodoItem> DeleteTodoItemAsync(TodoItem todoItem)
{
_todoContext.TodoItems.Remove(todoItem);
await _todoContext.SaveChangesAsync(); return todoItem;
} public async Task<TodoItem> GetTodoItemAsync(string id)
{
return await _todoContext.TodoItems.FirstOrDefaultAsync(t => t.Id == id);
} public async Task<IList<TodoItem>> GetTodoItemsAsync()
{
return await _todoContext.TodoItems.ToListAsync();
} public async Task<TodoItem> UpdateTodoItemAsync(TodoItem todoItem)
{
_todoContext.TodoItems.Update(todoItem);
await _todoContext.SaveChangesAsync(); return todoItem;
}
}
}
完善Todo组件
这里,我们仿照FetchData组件添加一个表格 并 实现TodoItem的添加:
@page "/todo"
@using EDT.BlazorServer.App.Models
@using EDT.BlazorServer.App.Service.Contracts
@inject ITodoItemService todoItemService; <h3>Todo (@todos.Count(todo => !todo.IsComplete))</h3> @if (todos == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>IsComplete</th>
<th>Remark</th>
</tr>
</thead>
<tbody>
@foreach (var todo in todos)
{
<tr>
<td>@todo.Id.ToString()</td>
<td>@todo.Name</td>
<td><input type="checkbox" @bind="todo.IsComplete" /></td>
<td>@todo.Remark</td>
</tr>
}
</tbody>
</table>
} <input placeholder="Todo Item Name (Necessary)" @bind="newTodoItemName" />
<input placeholder="Todo Item Remark (Optioinal)" @bind="newTodoItemRemark" />
<button @onclick="AddTodo">Add todo</button> @code {
private IList<TodoItem> todos;
private string? newTodoItemName;
private string? newTodoItemRemark; protected override async Task OnInitializedAsync()
{
todos = await todoItemService.GetTodoItemsAsync();
} private async void AddTodo()
{
if (string.IsNullOrWhiteSpace(newTodoItemName))
return; var todoItem = new TodoItem { Name = newTodoItemName, Remark = newTodoItemRemark };
await todoItemService.AddTodoItemAsync(todoItem);
// Clear Textboxes
newTodoItemName = newTodoItemRemark = string.Empty;
// Refresh Todos
todos = await todoItemService.GetTodoItemsAsync();
}
}
需要注意的是:
(1)通过@inject指令进行Service的注入,和常见的构造函数注入不同。
(2)通过重写OnInitializeAsync事件,进行数据的初始化,即从数据库中读取TodoItem的列表。这部分属于Blazor组件的生命周期范畴,这里不过多纠结即可。唯一需要了解的是,OnInitialized 和 OnInitializeAsync 事件是在做组件的初始化,它发生在参数注入完成之后(这里的ITodoItemService就是注入的参数)。
(3)除了foreach,Blazor还包含其他循环指令,例如 @for、@while 和 @do while。这些指令返回重复的标记块。它们的工作方式与等效的 C# for、while 和 do...while 循环类似。
到此,最终的项目结构如下图所示:

运行效果
运行起来的效果如下图所示:
(1)加载Todo列表

(2)添加新的Todo事项

小结
本篇,我们实现了一个Todo应用。
下一篇,我们学习一下在Blazor中数据是如何被共享的。
参考资料
Microsoft Learning,《使用Blazor生成Web应用》

Blazor学习之旅(3)实现一个Todo应用的更多相关文章
- WCF学习之旅——第一个WCF示例(一)
最近需要用到WCF,所以对WCF进行了解.在实践中学习新知识是最快的,接下来先做了一个简单的WCF服用应用示例. 本文的WCF服务应用功能很简单,却涵盖了一个完整WCF应用的基本结构.希望本文能对那些 ...
- WCF学习之旅——第一个WCF示例(三)
第五步:创建客户端 WCF应用服务被成功寄宿后,WCF服务应用便开始了服务调用请求的监听工作.此外,服务寄宿将服务描述通过元数据的形式发布出来,相应的客户端就可以获取这些元数据.接下来我们来创建客户端 ...
- WCF学习之旅——第一个WCF示例(二)
第四步:通过自我寄宿的方式寄宿服务 WCF服务需要依存一个运行着的进程(宿主),服务寄宿就是为服务指定一个宿主的过程.WCF是一个基于消息的通信框架,采用基于终结点(Endpoint)的通信手段. 终 ...
- 【转】Esp8266学习之旅① 搭建开发环境,开始一个“hellow world”串口打印。
@2019-02-28 [小记] Esp8266学习之旅① 搭建开发环境,开始一个“hellow world”串口打印.
- 写一个TODO App学习Flutter本地存储工具Moor
写一个TODO App学习Flutter本地存储工具Moor Flutter的数据库存储, 官方文档: https://flutter.dev/docs/cookbook/persistence/sq ...
- WCF学习之旅—WCF服务的Windows 服务程序寄宿(十一)
上接 WCF学习之旅—WCF服务部署到IIS7.5(九) WCF学习之旅—WCF服务部署到应用程序(十) 七 WCF服务的Windows 服务程序寄宿 这种方式的服务寄宿,和IIS一样有一个一样 ...
- WCF学习之旅—第三个示例之四(三十)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) WCF学习之旅—第三个示例之三(二十九) ...
- Hadoop学习之旅二:HDFS
本文基于Hadoop1.X 概述 分布式文件系统主要用来解决如下几个问题: 读写大文件 加速运算 对于某些体积巨大的文件,比如其大小超过了计算机文件系统所能存放的最大限制或者是其大小甚至超过了计算机整 ...
- WCF学习之旅—第三个示例之二(二十八)
上接WCF学习之旅—第三个示例之一(二十七) 五.在项目BookMgr.Model创建实体类数据 第一步,安装Entity Framework 1) 使用NuGet下载最新版的Entity Fram ...
- WCF学习之旅—第三个示例之三(二十九)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) 在上一篇文章中我们创建了实体对象与接口协定,在这一篇文章中我们来学习如何创建WCF的服务端代码.具体步骤见下面. ...
随机推荐
- 【Maven】POM基本概念
目前的技术在开发中存在的问题: 一个项目就是一个工程 如果项目非常庞大,就不适合继续使用 package 来划分模块.最好是每一个模块对应一个工程,利于分工协作. 借助于 Maven 就可以将一个项目 ...
- 探秘Transformer系列之(26)--- KV Cache优化---分离or合并
探秘Transformer系列之(26)--- KV Cache优化 之 PD分离or合并 目录 探秘Transformer系列之(26)--- KV Cache优化 之 PD分离or合并 0x00 ...
- windows下jdk版本切换(bat)
1.jdk下载 Oracle官网 https://www.oracle.com/cn/ 资源->下载->Java下载 jdk当前最新版本 jdk22版本 jdk8版本 当前页面向下拉 2. ...
- jmeter使用时报错问题
一.打开时命令行提示按任意键继续图形界面无法打开 如图,打开时jmeter命令行报错 根据报错内容,是Java没有安装好. jdk安装好后,需要在环境变量中配置. 但是jdk安装配置好后打开还是报错, ...
- Web前端开发规范手册(有点老,仅供参考)
一.规范目的 1.1 概述 为提高团队协作效率, 便于后台人员添加功能及前端后期优化维护, 输出高质量的文档, 特制订此文档. 本规范文档一经确认, 前端开发人员必须按本文档规范进行前台页面开发. 本 ...
- 工具 | Hfish
0x00 简介 HFish是一款社区型免费蜜罐. 下载地址 HFish下载: HFish下载 0x01 功能说明 支持多种蜜罐服务 支持自定义Web蜜罐 支持流量牵引 支持端口扫描感知能力 支持多种告 ...
- 『Plotly实战指南』--在科学数据可视化中的应用(上)
在科学研究中,数据可视化是连接实验与理论的关键桥梁. 它不仅能够清晰地呈现实验规律,还能验证假设并支持科研决策. Plotly作为一款强大的可视化工具,凭借其交互性.动态图表支持和灵活的可定制性,在科 ...
- 盈亏平衡之"盈亏平衡点和总可变成本和总收入和利润和边际收益"
案例1 案例2 案例3 因为他这里没有按2w件来算,而是按4w件 利润 = 总收入 - 总成本 总收入 = 产量 * 单价 总成本 = 固定成本 + 变动成本 变动成本 = 单件可变成本 * 产量 案 ...
- SpringBoot中的拦截器江湖
前言 很多小伙伴在工作中遇到拦截需求就无脑写HandlerInterceptor,结果被复杂场景搞得鼻青脸肿. 作为一名有多年开发经验的程序员,今天领大家到SpringBoot的山头认认6把交椅: 这 ...
- 递归神经网络 RNN 原理(下)
基于对 RNN 的初步认识, 还是先回顾一下它核心的步骤: (1) words / onehot vectors : \(x^{(t)} \in R^{|v|}\) **(2) word embedd ...