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的服务端代码.具体步骤见下面. ...
随机推荐
- 2025年Anaconda官网最新版本Anaconda下创建虚拟环境,并在新创建的虚拟环境下一次性安装多个软件包的经验总结
一.为了使用tensorflow软件包中的tensorboard软件包工具,在python==3.12.8环境下创建虚拟环境pytorch,使用命令行如下.1.1 使用Anaconda prompt, ...
- 应用引入LLM实践
LLM最近在各行各业遍地开花,产生了很好的效果,也落地了很多好的功能应用. 无论是从实际应用角度,还是从营销角度,我们都需要接入大模型能力. 拿国内比较火的Deepseek来说,具有良好的推理能力,可 ...
- ShardingSphere 解决关联表查询问题的详细方案
一.基础概念 在分库分表场景下,关联表(JOIN)查询的复杂性主要源于数据分布在不同的数据库或表中.ShardingSphere 通过 绑定表(Binding Table) 和 广播表(Broadca ...
- Model接口
/** * Model接口 * 作用:将值存放到request对象 * * * @return */ @RequestMapping(value = "/testModle") p ...
- .NET Core短信验证(分布式session)
一.手机短信验证码登录过程 1.构造手机验证码,需要生成一个6位的随机数字串: 2.找短信平台获取使用接口向短信平台发送手机号和验证码,然后短信平台再把验证码发送到制定手机号上 3.将手机号验证码.操 ...
- System Integrity Protection (SIP) iOS10.15安装软件提示文件损坏问题解决方法
方法如下:1.开机的时候立即按住command+R 组合键不要放手,直到进度条加载完才松手,进入到系统恢复工具界面 2. 然后点击"实用工具"选项卡中的"终端" ...
- 银河麒麟系统 jenkins docker 部署 自动化打包部署git 项目
Jenkins 是一个开源的自动化服务器,主要用于实现 持续集成(CI) 和 持续交付/部署(CD),其核心作用在于通过自动化流程提升软件开发和交付的效率与质量 一.环境准备 1. 安装 Docker ...
- 基于SaaS纯BS架构的全院级PACS系统
2014年曾经做过一版简单的Dicom Web Viewer,之前的Web版本由于技术和功能的极限性,仅能简单的运用于临床阅片和患者的电子胶片使用,无法普及到放射和超声等影像科室.影像科 ...
- sql注入与防止sql注入
数据库中的数据 sql代码 package com.zjw.jdbc2; import java.sql.Connection; import java.sql.DriverManager; impo ...
- Java---switch...case中case可以匹配些什么
switch-case语句 case 标签可以是 : •类型为 char.byte.short 或 int 的常量表达式. •枚举常量. •从 Java SE 7 开始,case 标签还可以是字符串字 ...