《進擊吧!Blazor!》第一章 4.數據交互
- 2021 年 2 月 5 日
- 筆記
- .NET, Asp.Net, Blazor, c#, WebAssembly, 前端, 進擊吧!Blazor!
《進擊吧!Blazor!》是本人與張善友老師合作的Blazor零基礎入門系列影片,此系列能讓一個從未接觸過Blazor的程式設計師掌握開發Blazor應用的能力。
影片地址://space.bilibili.com/483888821/channel/detail?cid=151273
演示程式碼://github.com/TimChen44/Blazor-ToDo
本系列文章是基於《進擊吧!Blazor!》直播內容編寫,升級.Net5,改進問題,講解更全面。
作者:陳超超
Ant Design Blazor 項目貢獻者,擁有十多年從業經驗,長期基於.Net技術棧進行架構與開發產品的工作,現就職於正泰集團。
郵箱:timchen@live.com
歡迎各位讀者有任何問題聯繫我,我們共同進步。
上一次課程我們完成了ToDo應用的介面製作,這次我們要將客戶端的數據寫入資料庫,並從資料庫中讀物我們需要的數據。
數據交互過程
我們先看一下從客戶端到資料庫的流程
Blazor
Blazor客戶端就是我們上節課做的ToDo程式。
HttpClient
HttpClient就是我們完成網路通訊用的組件,對於這類組件我們希望在一個應用中只構造一次,這樣避免重複分配資源,因此我們在Program.cs
中進行註冊。
public class Program
{
public static async Task Main(string[] args)
{
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
}
}
BaseAddress
為基地址,這樣我們使用時,Url只需要傳入相對地址即可,此處默認為當前主機的地址。
DefaultRequestHeaders
默認HTTP請求頭參數
Timeout
連接超時參數
- 依賴關係注入
上面通過服務注入的方式實現了HttpClient
全局共享(單例),那麼如何使用服務?這裡我們就需要引入一下「依賴關係注入 (DI)」的概念。
DI是一種技術,基本原理是把有依賴關係的類放到容器中,解析出這些類的實例,就是依賴注入。應用可通過將內置服務注入組件來使用這些服務。 應用還可定義和註冊自定義服務,並通過 DI 使其在整個應用中可用。
該技術在 Blazor 應用中常用於以下兩個方面:
服務生存期決定了服務何時創建,何時銷毀,有三種模式:
Scoped
:Blazor WebAssembly
應用當前沒有 DI 範圍的概念。 已註冊 Scoped
的服務的行為與 Singleton
服務類似。 但是,Blazor Server 託管模型支援 Scoped
生存期。 在 Blazor Server
應用中,Scoped
服務註冊的範圍為「連接」。 因此,即使當前意圖是在瀏覽器中運行客戶端,對於範圍應限定為當前用戶的服務來說,首選使用 Scoped
服務。
Singleton
:DI 創建服務的單個實例。 需要 Singleton
服務的所有組件都會接收同一服務的實例。
Transient
:每當組件從服務容器獲取 Transient
服務的實例時,它都會接收該服務的新實例。
這裡的 HttpClient
使用了 AddScoped
方法,那麼就是當前範圍內使用同一個實例,因為項目是Blazor WebAssembly
模式,所以相當於單例服務。
ASP.Net Core
我用ASP.Net Core項目給Blazor應用提供WebAPI介面
項目結構如下
- launchSettings.json
這裡配置了我們調試的方式,埠等,相對於普通的Web項目多了inspectUri
屬性,具有以下作用:
- 使 IDE 能夠檢測到該應用為 Blazor WebAssembly 應用。
- 指示腳本調試基礎結構通過 Blazor 的調試代理連接到瀏覽器。
- 已啟動的瀏覽器 (browserInspectUri) 上 WebSocket 協議 (wsProtocol)、主機 (url.hostname)、埠 (url.port) 和檢查器 URI 的佔位符值由框架提供。
{
//省略其他配置
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
//省略其他配置
}
- Controllers
控制器(Controller
)放在這裡,站點的路由表是通過遍歷項目中帶有ApiControllerAttribute
(基類ControllerAttribute
)的類,然後尋找裡面的方法實現,他和Blazor的路由表創建方法上有點相似。
[ApiController]
[Route("api/[controller]/[action]")]
public class TaskController : ControllerBase
Route
定義了路由格式,上例中[controller]/[action]
意為使用Controller
和action
的名稱作為路由地址,這樣寫可以省去每個action
上標記路由名字的麻煩。
- Pages
存放頁面文件的位置,因為我們的項目頁面全部使用Blazor構建,所以用不到此文件夾,因此這裡就不做介紹了。
- appsettings.json
站點的配置文件,我們的項目就用到了資料庫鏈接字元串配置
- Program.cs
應用的Main
函數在這裡,這裡完成了Host
的創建與啟動
- Startup.cs
啟動類,項目啟動時的服務註冊,配置等工作都在此處完成
ConfigureServices
使用此方法將服務添加到容器。
Configure
使用此方法來配置HTTP請求管道。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//省略其他程式碼
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
app.UseBlazorFrameworkFiles();
配置應用程式提供Blazor WebAssembly框架文件,默認根路徑為/
,也可以自定義路徑前綴
endpoints.MapControllers();
添加控制器(Controller
)的路由。
endpoints.MapFallbackToFile("index.html");
添加默認路由地址是index.html
。
EF Code
所有的數據我們需要保存入資料庫,這裡我選擇使用EF Core作為我們的數據訪問技術
EF Core部分特點
- Entity Framework (EF) Core 是輕量化、可擴展、開源和跨平台版的數據訪問技術。
- EF Core可用作對象關係映射程式 (O/RM),能讓我們用對象來處理資料庫,使用Linq進行查詢,這樣我們就可以不用編寫大量SQL程式碼了。
- EF Core 支援多個資料庫引擎,比如MySQL、SQLite等。
他支援採用Code Firs或者Database First兩種模式
Code Firs
用程式碼編寫對象關係,然後通過它創建資料庫。Database First
可以提供現有資料庫,反向生成對象映射 。
Database
資料庫我選擇SQL Server,使用全套微軟技術棧工具鏈使用體驗比較好,當然我們也可以選擇其他資料庫。
SQL Server產品家族中有一個SQL Server LocalDB的東西,它是SQL Server的一個超級精簡版本,安裝包只有幾十MB(安裝好後200+MB),它包含了資料庫的基礎功能,但是不支援聯網,只能本機連接,對於個人開發資源佔用少,強烈推薦,VS安裝Web開發組件會默認安裝此資料庫。
連接時伺服器名稱默認是(localdb)\MSSQLLocalDB
,也可以使用C:\Program Files\Microsoft SQL Server\130\Tools\Binn\SqlLocalDB.exe
進行配置資料庫實例
我們可以使用VS的SQL Server對象資源管理器來查看我們的資料庫,不過我這裡強烈推薦使用SQL Server Management Studio (SSMS) 的「資料庫關係圖」功能來維護資料庫,可視化編輯表,主外鍵關係等,保存即更新資料庫,這對於資料庫優先的模式下開發非常友好,效率極高。
下圖是我們ToDo應用使用的表結構。
程式碼實戰
上面介紹了數據交互的流程概念,接下來我們改造上回製作的ToDo項目。
引入和配置EF Code
我們先創建一個ToDo.Entity
項目用於存儲ORM映射以及EF的Context。
注意:目前VS 16.8.4版本創建類庫會默認使用.net core 3.1,需要手動修改成.net 5
使用EF Core Power Tools工具創建程式碼
因為我們上面已經把資料庫設計完成了,所以我們採用Database First
模式創建EF相關的程式碼。
此處推薦一個從資料庫到EF實體的程式碼生成擴展EF Core Power Tools
擴展下載地址://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools
選擇要連接的資料庫。
選擇要添加的資料庫對象。
配置Context
的名稱和命名空間等,下圖是我常用配置。
EF Core Power Tools
生成的程式碼文件如下
appsettings.json中添加鏈接字元串
打開ToDo.Server\appsettings.json
添加資料庫連接字元串
"ConnectionStrings": {
"DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=ToDo;Integrated Security=True"
},
使用
EF Core Power Tools
生成的TodoContext.cs
文件中就有默認的連接字元串,開發時想偷懶可以直接從這裡複製。
ConfigureServices中添加服務註冊
打開ToDo.Server\Startup.cs
,把TodoContext
註冊到DbContext
中為,並設置連接字元串
services.AddDbContext<TodoContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
有時候我們需要在輸出EF執行的SQL語句,這便於我們調試以及優化資料庫,下面的配置就把EF日誌輸出到控制台
/// <summary>
/// 輸出日誌
/// </summary>
public static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
public void ConfigureServices(IServiceCollection services)
{
//省略其他程式碼
services.AddDbContext<TodoContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).UseLoggerFactory(loggerFactory);
});
}
功能實現
首先創建ToDo.Server\Controllers\TaskController.cs
文件用於編寫WebAPI介面,程式碼如下:
namespace ToDo.Server.Controllers
{
[ApiController]
[Route("api/[controller]/[action]")]
public class TaskController : ControllerBase
{
TodoContext Context;
public TaskController(TodoContext context)
{
Context = context;
}
}
}
通過依賴注入將TodoContext
注入到當前類中。
1. 列出當天的所有代辦工作
ToDo.Server
TaskController.cs
中添加GetToDayTask
方法用於返回當前待辦數據。
[HttpGet]
public List<TaskDto> GetToDayTask()
{
var result = Context.Task.Where(x => x.PlanTime == DateTime.Now.Date);
return QueryToDto(result).ToList();
}
[NonAction]
private IQueryable<TaskDto> QueryToDto(IQueryable<Entity.Task> query)
{
return query.Select(x => new TaskDto()
{
TaskId = x.TaskId,
Title = x.Title,
Description = x.Description,
PlanTime = x.PlanTime,
Deadline = x.Deadline,
IsImportant = x.IsImportant,
IsFinish = x.IsFinish,
});
}
ToDo.Client
添加Pages\ToDay.razor.cs
類文件,VS會自動將ToDay.razor
與ToDay.razor.cs
摺疊到一起。
在類定義中增加partial
關鍵字,聲明類為局部類,你可以理解成ToDay.razor
與ToDay.razor.cs
中的程式碼都屬於同一個類,只是放在不同文件中,編譯器編譯時會將他們合併到一起後進行編譯。
public partial class ToDay
接著做一下程式碼遷移
- 將
Pages\ToDay.razor
文件@code{}
中的程式碼剪切到ToDay.razor.cs
- 將
Pages\ToDay.razor
文件@inject
程式碼採用[Inject] public MessageService MsgSrv { get; set; }
這樣的格式等價的遷移到ToDay.razor.cs
這樣做我們可以實現介面程式碼與業務程式碼分開在不同的文件中,方便整理程式碼,提高程式碼可讀性。
後續其他頁面我默認完成了創建局部類的操作,不再贅述。
ToDay.razor.cs
中添加HttpClient
的依賴注入,用於向服務端發起Http請求
[Inject] public HttpClient Http { get; set; }
項目中其他類如果使用到
HttpClient
,我默認完成了依賴注入,不再贅述。
廣告時間
Blazor
和Ant Design Blazor
中有很多服務,我們經常在不同的地方需要注入,為了編碼方便,我們提供了一個VS擴展快速插入常用服務程式碼段,安裝地址://marketplace.visualstudio.com/items?itemName=TimChen44.AntDesignBlazorSnippets
修改OnInitializedAsync
方法的程式碼
private List<TaskDto> taskDtos = new List<TaskDto>();
bool isLoading = true;
protected async override Task OnInitializedAsync()
{
isLoading = true;
taskDtos = await Http.GetFromJsonAsync<List<TaskDto>>("api/Task/GetToDayTask");
isLoading = false;
await base.OnInitializedAsync();
}
Http.GetFromJsonAsync<List<TaskDto>>
使用HttpGet
模式請求數據,這裡使用await
進行非同步等待,充分利用await
可以極大的簡化程式碼量。
isLoading
是載入狀態,網路通訊必定有延遲,避免白屏,我們在載入前後分別改變載入狀態,同時修改ToDay.razor
程式碼添加Spin
組件用於顯示載入效果。
<PageHeader Title="@("我的一天")" Subtitle="@DateTime.Now.ToString("yyyy年MM月dd日")"></PageHeader>
<Spin Spinning="isLoading"><!--插入程式碼-->
@foreach (var item in taskDtos)
<!--省略其他程式碼-->
<Input @bind-Value="@newTask.Title" OnkeyUp="OnInsert" />
</div>
</Spin><!--插入程式碼-->
2. 添加代辦
ToDo.Server
TaskController.cs
中添加SaveTask
方法用於保存新的待辦內容
[HttpPost]
public Guid SaveTask(TaskDto dto)
{
Entity.Task entity;
if (dto.TaskId == Guid.Empty)
{
entity = new Entity.Task();
entity.TaskId = Guid.NewGuid();
Context.Add(entity);
}
else
{
entity = Context.Task.FirstOrDefault(x => x.TaskId == dto.TaskId);
}
entity.Title = dto.Title;
entity.Description = dto.Description;
entity.PlanTime = dto.PlanTime;
entity.Deadline = dto.Deadline;
entity.IsImportant = dto.IsImportant;
entity.IsFinish = dto.IsFinish;
Context.SaveChanges();
return entity.TaskId;
}
我通過判斷dto.TaskId
的值,直接將新增與更新寫在一個介面中,這樣可以復用程式碼。
此處可以使用
AutoMapper
庫來簡化賦值過程,這將在將來的章節中詳細介紹
ToDo.Client
ToDay.razor.cs
文件修改OnInsert
方法相關的程式碼
TaskDto newTask = new TaskDto() { PlanTime = DateTime.Now.Date };
[Inject] public MessageService MsgSrv { get; set; }
bool isNewLoading = false;
async void OnInsert(KeyboardEventArgs e)
{
if (e.Code == "Enter")
{
if (string.IsNullOrWhiteSpace(newTask.Title))
{
MsgSrv.Error($"標題必須填寫");
return;
}
isNewLoading = true;
var result = await Http.PostAsJsonAsync<TaskDto>($"api/Task/SaveTask", newTask);
if (result.IsSuccessStatusCode)
{
newTask.TaskId = await result.Content.ReadFromJsonAsync<Guid>();
taskDtos.Add(newTask);
newTask = new TaskDto() { PlanTime = DateTime.Now.Date };
}
else
{
MsgSrv.Error($"請求發生錯誤 {result.StatusCode}");
}
isNewLoading = false;
StateHasChanged();
}
}
ToDay.razor
文件增加保存時等待組件
<Spin Spinning="isNewLoading"><!--插入程式碼-->
<div class="task-input">
<DatePicker Picker="@DatePickerType.Date" @bind-Value="@newTask.PlanTime" />
<Input @bind-Value="@newTask.Title" OnkeyUp="OnInsert" />
</div>
</Spin><!--插入程式碼-->
通過Http.PostAsJsonAsync
調用api/Task/SaveTask
將newTask
內容提交到後端並保存,返回的HttpResponseMessage
包含了狀態編碼等,如果成功就在介面上顯示新的待辦,失敗就提示錯誤
MessageService
全局展示操作回饋資訊。
組件文檔地址://ant-design-blazor.github.io/zh-CN/components/message
3. 編輯待辦
ToDo.Server
TaskController.cs
中添加GetTaskDto
方法用於獲取待辦資訊
public TaskDto GetTaskDto(Guid taskId)
{
var result = Context.Task.Where(x => x.TaskId == taskId);
return QueryToDto(result).FirstOrDefault();
}
ToDo.Client
TaskInfo.razor
文件中增加Spin
與@if
程式碼
<Spin Spinning="isLoading">
@if (taskDto != null)<!--頁面打開時taskDto並沒有值,所以直接綁定到Form會發生異常,所以這裡需要做一個不為空判斷-->
{
<Form OnFinish="OnSave" Model="taskDto" LabelColSpan="8"><!--當用戶點擊submit按鈕時會觸發OnFinish事件,所以通常會在這裡進行保存操作-->
<!--省略其他程式碼-->
<div>
<Button HtmlType="submit">保存</Button>
<Button OnClick="OnCancel">取消</Button>
</div>
</Form>
}
</Spin>
TaskInfo.razor.cs
添加下面程式碼
public partial class TaskInfo : DrawerTemplate<TaskDto, TaskDto>
{
[Inject]
public HttpClient Http { get; set; }
[Inject]
public MessageService MsgSvr { get; set; }
TaskDto taskDto;
bool isLoading = false;
protected override async Task OnInitializedAsync()
{
//通過api/Task/GetTaskDto介面獲得待辦內容
taskDto = await Http.GetFromJsonAsync<TaskDto>($"api/Task/GetTaskDto?taskId={base.Options.TaskId}");
await base.OnInitializedAsync();
}
async void OnSave()
{
var result = await Http.PostAsJsonAsync<TaskDto>($"api/Task/SaveTask", taskDto);
if (result.StatusCode == System.Net.HttpStatusCode.OK)
{
await base.CloseAsync(taskDto);//關閉抽屜,並返回當前待辦數據
}
else
{
MsgSvr.Error($"請求發生錯誤 {result.StatusCode}");
}
}
async void OnCancel()
{
await base.CloseAsync(null);//如果點擊了取消,那麼將null返回出去
}
}
ToDay.razor.cs
中的OnCardClick
方法更新
[Inject] public DrawerService DrawerSrv { get; set; }
async void OnCardClick(TaskDto task)
{
var result = await DrawerSrv.CreateDialogAsync<TaskInfo, TaskDto, TaskDto>(task, title: task.Title, width: 450);
if (result == null) return;
var index = taskDtos.FindIndex(x => x.TaskId == result.TaskId);
taskDtos[index] = result;
await InvokeAsync(StateHasChanged);
}
DrawerSrv.CreateDialogAsync
相對於DrawerSrv.CreateAsync
簡化了調用方法,默認將抽屜的CloseAsync
參數返回,這就簡化了每次使用抽屜時需要註冊CloseAsync
事件的麻煩,也讓程式碼更加清晰。
title: task.Title, width: 450
使用可選參數簡化對抽屜的參數配置。
DrawerService組件幫助文檔://ant-design-blazor.github.io/zh-CN/components/drawer
4. 修改重要程度
ToDo.Server
TaskController.cs
中添加SetImportant
方法用於修改IsImportant
欄位的值
[HttpPost]
public void SetImportant(SetImportantReq req)
{
var entity = Context.Task.FirstOrDefault(x => x.TaskId == req.TaskId);
entity.IsImportant = req.IsImportant;
Context.SaveChanges();
}
ToDo.Shared
添加SetImportantReq
類用於SetImportant
介面請求參數
public class SetImportantReq
{
public Guid TaskId { get; set; }
public bool IsImportant { get; set; }
}
ToDo.Client
ToDay.razor.cs
中的OnStar
方法更新
private async void OnStar(TaskDto task)
{
var req = new SetImportantReq()//ToDo.Shared項目中的類可以前後端公用,這就是Blazor優勢之一。
{
TaskId = task.TaskId,
IsImportant = !task.IsImportant,
};
var result = await Http.PostAsJsonAsync<SetImportantReq>("api/Task/SetImportant", req);
if (result.IsSuccessStatusCode)
{
task.IsImportant = req.IsImportant;//請求成功後需要修改本地重要狀態
StateHasChanged();//狀態改變,刷新頁面的顯示
}
}
5. 修改完成狀態
ToDo.Server
TaskController.cs
中添加SetFinish
方法用於修改IsFinish
欄位的值
[HttpPost]
public void SetFinish(SetFinishReq req)
{
var entity = Context.Task.FirstOrDefault(x => x.TaskId == req.TaskId);
entity.IsFinish = req.IsFinish;
Context.SaveChanges();
}
ToDo.Shared
添加SetFinishReq
類用於SetFinish
介面請求參數
public class SetFinishReq
{
public Guid TaskId { get; set; }
public bool IsFinish { get; set; }
}
ToDo.Client
ToDay.razor.cs
中的OnFinish
方法更新
private async void OnFinish(TaskDto task)
{
var req = new SetFinishReq()
{
TaskId = task.TaskId,
IsFinish = !task.IsFinish,
};
var result = await Http.PostAsJsonAsync<SetFinishReq>("api/Task/SetFinish", req);
if (result.IsSuccessStatusCode)
{
task.IsFinish = req.IsFinish;
StateHasChanged();
}
}
6. 刪除代辦
ToDo.Server
TaskController.cs
中添加DelTask
方法用於刪除待辦。
[HttpDelete]
public void DelTask(Guid taskId)
{
Context.Task.Remove(Context.Task.Find(taskId));
Context.SaveChanges();
}
ToDo.Client
ToDay.razor.cs
中的OnFinish
方法更新
[Inject] public ConfirmService ConfirmSrv { get; set; }
public async Task OnDel(TaskDto task)
{
if (await ConfirmSrv.Show($"是否刪除任務 {task.Title}", "刪除", ConfirmButtons.YesNo, ConfirmIcon.Info) == ConfirmResult.Yes)
{
taskDtos.Remove(task);
}
}
ConfirmService
可以快捷地彈出一個內置的確認框,類似於 Windows MessageBox。
ConfirmService組件幫助文檔://ant-design-blazor.github.io/zh-CN/components/modal
7. 查詢代辦
ToDo.Server
TaskController.cs
中添加GetSearch
方法用於修改SetFinish
欄位的值
[HttpPost]
public GetSearchRsp GetSearch(GetSearchReq req)
{
if (req.PageIndex == 0) req.PageIndex = 1;
var query = Context.Task.Where(x => x.Title.Contains(req.QueryTitle ?? ""));
foreach (var sort in req.Sorts)
{
if (sort.SortOrder == "descend")
query = query.OrderBy(sort.SortField + " DESC");
else
query = query.OrderBy(sort.SortField);
}
var result = new GetSearchRsp()
{
Data = QueryToDto(query.Skip(--req.PageIndex * req.PageSize).Take(req.PageSize)).ToList(),
Total = query.Count(),
};
return result;
}
if (req.PageIndex == 0) req.PageIndex = 1
吐槽開始:幾乎所有的UI框架頁碼都是從0開始,但是AntDesign規範的頁碼是從1開始的,然而沒有載入數據時又是返回0
,所以要特別注意。
OrderBy
使用了System.Linq.Dynamic.Core
擴展包,它提供了一些動態的Linq支援,比如此處排序我傳入的參數不是一個表達式,而是一個字元串,這樣可以讓程式碼靈活性大增。
ToDo.Shared
public class GetSearchReq
{
public string QueryTitle { get; set; }
public int PageIndex { get; set; }
public int PageSize { get; set; }
public List<SortFieldName> Sorts { get; set; }
}
public class SortFieldName
{
/// <summary>
/// 排序欄位
/// </summary>
public string SortField { get; set; }
/// <summary>
/// 排序方向
/// </summary>
public string SortOrder { get; set; }
}
public class GetSearchRsp
{
public List<TaskDto> Data { get; set; }
public int Total { get; set; }
}
ToDo.Client
TaskSearch.razor
文件的程式碼
@page "/search"
<PageHeader Title="@("全部待辦事項")" Subtitle="@($"數量:{total}")"></PageHeader>
<Search @bind-Value="queryTitle" OnSearch="OnSearch"></Search>
<Table Loading="@isLoading" DataSource="@datas" PageSize="10" Total="@total" OnChange="OnChange" TItem="TaskDto">
<AntDesign.Column @bind-Field="@context.Title" Sortable>
@context.Title
@if (context.IsImportant)
{
<Tag Color="orange">重要</Tag>
}
</AntDesign.Column>
<AntDesign.Column @bind-Field="@context.Description" />
<AntDesign.Column @bind-Field="@context.PlanTime" Sortable />
<AntDesign.Column @bind-Field="@context.Deadline" />
<AntDesign.Column @bind-Field="@context.IsFinish">
@if (context.IsFinish)
{
<Icon Type="check" Theme="outline" />
}
</AntDesign.Column>
</Table>
TaskSearch.razor.cs
文件的程式碼
[Inject] public HttpClient Http { get; set; }
private bool isLoading = false;
List<TaskDto> datas = new List<TaskDto>();
private string queryTitle;
private int total = 0;
//點擊查詢按鈕時檢索數據
private async Task OnSearch()
{
await OnQuery(1, 10, new List<SortFieldName>());
}
//當前頁碼,排序發生改變時調用查詢方法檢索數據
private async Task OnChange(AntDesign.TableModels.QueryModel<TaskDto> queryModel)
{
await OnQuery(
queryModel.PageIndex,
queryModel.PageSize,
queryModel.SortModel.Where(x => string.IsNullOrEmpty(x.SortType.Name) == false).OrderBy(x => x.Priority)
.Select(x => new SortFieldName() { SortField = x.FieldName, SortOrder = x.SortType.Name }).ToList()
);
}
//檢索數據
private async Task OnQuery(int pageIndex, int pageSize, List<SortFieldName> sort)
{
isLoading = true;
var req = new GetSearchReq()
{
QueryTitle = queryTitle,
PageIndex = pageIndex,
PageSize = pageSize,
Sorts = sort,
};
var httpRsp = await Http.PostAsJsonAsync<GetSearchReq>($"api/Task/GetSearch", req);
var result = await httpRsp.Content.ReadFromJsonAsync<GetSearchRsp>();
datas = result.Data;
total = result.Total;
isLoading = false;
}
Search
帶有查詢按鈕的文本框框
Search組件文檔地址://ant-design-blazor.github.io/zh-CN/components/input
查看詳細服務
在我的一天
與全部
頁面上均存在打開待辦詳情的功能需求,這時我們就可以自己做一個服務將兩邊的功能合併到一起。
添加TaskDetailServices.cs
文件,加入以下程式碼
namespace ToDo.Client
{
public class TaskDetailServices
{
public DrawerService DrawerSvr { get; set; }
public TaskDetailServices(DrawerService drawerSvr)
{
DrawerSvr = drawerSvr;
}
public async Task EditTask(TaskDto taskDto, List<TaskDto> datas)
{
var taskItem = await DrawerSvr.CreateDialogAsync<TaskInfo, TaskDto, TaskDto>(taskDto, title: taskDto.Title, width: 450);
if (taskItem == null) return;
var index = datas.FindIndex(x => x.TaskId == taskItem.TaskId);
datas[index] = taskItem;
}
}
}
TaskDetailServices(DrawerService drawerSvr)
只有razor
文件可以使用[Inject]
標記屬性進行注入服務,普通得類需要在構造函數中定義才能注入服務。
Program.cs
文件中註冊TaskDetailServices
builder.Services.AddScoped<TaskDetailServices>();
TaskSearch.razor
文件中添加詳情按鈕
<AntDesign.Column TData="object">
<Button OnClick="x=>OnDetail(context)">詳情</Button>
</AntDesign.Column>
TaskSearch.razor.cs
插入以下程式碼,我們注入自定義的服務,使用服務中的方法打開編輯介面。
[Inject] public TaskDetailServices TaskSrv { get; set; }
private async Task OnDetail(TaskDto taskDto)
{
await TaskSrv.EditTask(taskDto, datas);
}
次回預告
下一次我們要介紹Blazor的精髓,也是我個人認為Blazor框架體系中最優秀的特性——組件。我們通過幾個小實例展示Blazor的組件開發方法,敬請期待