這裡筆記一下 Blazor 透過 WebAPI,針對北風資料庫的 Shippers 進行CRUD(新增修改刪除查詢)的範例
緣起
這裡記錄一下 Blazor,透過 WebAPI 存取資料庫(非同步)的範例程式。資料庫採用北風資料庫的 Shippers,提供網友與自己參考。
解決方案、專案準備與說明
建立一個空白解決方案,在裡面新增兩個專案,一個是 WebAPI ,另一個是 Blazor 。資料庫的部分,以北風資料庫的 Shippers,資料的存取透過 Dapper 進行存取。
文章中不會把專案中所有的檔案程式內容都放上,如果有缺漏看不懂的部分,請提出我再補充。
WebAPI : Model
首先是 DTO 用的 ShipperAM (ApiModel):
namespace PNorthWindAPI.Models.ApiModel
{
public class ShipperAM
{
public int ShipperID { get; set; } = 0;
public string CompanyName { get; set; } = "";
public string Phone { get; set; } = "";
}
}
再來是用來傳回結果的 JsonRltInfo
namespace PCATAPI.Models.Infos
{
public class JsonRltInfo
{
public int rltCode { get; set; } = 0;
public string rltMsg { get; set; } = "";
}
}
用以傳回錯誤的 ErrMsgInfo
using System;
using System.Net;
using System.Net.Http;
namespace PCATAPI.Models.Infos
{
public class ErrMsgInfo
{
private string m_ErrCode = "";
private string m_ErrMsg = "";
private string m_ErrTime = "";
private string m_ErrJSON = "";
private Exception m_Ex = null;
private HttpResponseMessage m_RepMsg = null;
public Exception ex
{
set
{
m_Ex = value;
if (m_Ex != null)
{
m_ErrCode = m_Ex.HResult.ToString();
m_ErrMsg = m_Ex.Message;
GenErrJSON();
}
}
}
public HttpResponseMessage RepMsg
{
get
{
return m_RepMsg;
}
}
private void GenErrJSON()
{
//throw new NotImplementedException();
if (m_ErrCode != "" && m_ErrMsg != "")
{
m_ErrJSON = "{\"ErrCode\":" + m_ErrCode + ",\"ErrMsg\":" + m_ErrMsg + "}";
m_RepMsg = new HttpResponseMessage(HttpStatusCode.ExpectationFailed);
m_RepMsg.Content = new StringContent(m_ErrJSON);
m_ErrTime = DateTime.Now.ToString("yyyyMMddHHmmss");
m_RepMsg.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
}
}
}
}
連接字串暫時用一個類別存放 ConnStrInfo
namespace PNorthWindAPI.Models.Info
{
public class ConnStrInfo
{
public string ConnStr { get; set; } = "Data Source=.\\SQLExpress;Initial Catalog=NorthwindChinese;Integrated Security=True";
}
}
接下來是比較重頭戲的 CRUD 的 DAO 啦
using Dapper;
using PNorthWindAPI.Models.ApiModel;
using PNorthWindAPI.Models.Info;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace PNorthWindAPI.Models.DAOs
{
public class ShipperDao
{
public List<ShipperAM> getShipprs()
{
List<ShipperAM> oShippers = new List<ShipperAM>();
try
{
string ConnStr = GetConnStr();
using(SqlConnection Conn = new SqlConnection(ConnStr))
{
Conn.Open();
string SqlTxt = "";
SqlTxt += " SELECT * ";
SqlTxt += " FROM [dbo].[Shippers] (NOLOCK) ";
SqlTxt += " ";
oShippers = Conn.Query<ShipperAM>(SqlTxt).ToList();
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return oShippers;
}
public ShipperAM getShipper(int ShipperID)
{
ShipperAM oShipper = new ShipperAM();
try
{
string ConnStr = GetConnStr();
using(SqlConnection Conn = new SqlConnection(ConnStr))
{
Conn.Open();
string SqlTxt = "";
SqlTxt += " SELECT * ";
SqlTxt += " FROM [dbo].[Shippers] (NOLOCK) ";
SqlTxt += " WHERE ShipperID = @ShipperID ";
SqlTxt += " ";
oShipper = Conn.Query<ShipperAM>(SqlTxt, new { ShipperID = ShipperID }).FirstOrDefault();
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return oShipper;
}
public string InsertShipper(ShipperAM oShipper)
{
string rc = "";
using (var scope = new TransactionScope())
{
try
{
string ConnStr = GetConnStr();
using (SqlConnection Conn = new SqlConnection(ConnStr))
{
Conn.Open();
string SqlTxt = "";
SqlTxt += " INSERT INTO [dbo].[Shippers] ";
SqlTxt += " (CompanyName, Phone) ";
SqlTxt += " VALUES (@CompanyName, @Phone) ";
SqlTxt += " ";
Conn.Execute(SqlTxt, oShipper);
rc = "Success";
}
scope.Complete();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
return rc;
}
public string ChgShipper(ShipperAM oShipper)
{
string rc = "";
using (var scope = new TransactionScope())
{
try
{
string ConnStr = GetConnStr();
using (SqlConnection Conn = new SqlConnection(ConnStr))
{
Conn.Open();
string SqlTxt = "";
SqlTxt += " UPDATE [dbo].[Shippers] ";
SqlTxt += " SET CompanyName = @CompanyName ";
SqlTxt += " , Phone = @Phone ";
SqlTxt += " WHERE ShipperID = @ShipperID ";
SqlTxt += " ";
Conn.Execute(SqlTxt, oShipper);
rc = "Success";
}
scope.Complete();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
return rc;
}
public string DelShipper(int ShipperID)
{
string rc = "";
using (var scope = new TransactionScope())
{
try
{
string ConnStr = GetConnStr();
using (SqlConnection Conn = new SqlConnection(ConnStr))
{
Conn.Open();
string SqlTxt = "";
SqlTxt += " DELETE Shippers ";
SqlTxt += " WHERE ShipperID = @ShipperID ";
SqlTxt += " ";
Conn.Execute(SqlTxt, new { ShipperID = ShipperID });
rc = "Success";
}
scope.Complete();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
return rc;
}
private string GetConnStr()
{
string ConnStr = "";
ConnStrInfo oConnStr = new ConnStrInfo();
ConnStr = oConnStr.ConnStr;
return ConnStr;
}
}
}
WebAPI : Controller
直接來看程式碼:
using PCATAPI.Models.Infos;
using PNorthWindAPI.Models.ApiModel;
using PNorthWindAPI.Models.DAOs;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Web.Http;
namespace PNorthWindAPI.Controllers.WebAPI
{
public class ShipperController : ApiController
{
[HttpGet]
[Route("api/Shipper")]
public List<ShipperAM> getShippers()
{
List<ShipperAM> oShippers = new List<ShipperAM>();
ShipperDao dao = new ShipperDao();
try
{
oShippers = dao.getShipprs();
}
catch (Exception ex)
{
ErrMsgInfo oErr = new ErrMsgInfo();
oErr.ex = ex;
HttpResponseMessage RepMsg = oErr.RepMsg;
throw new HttpResponseException(RepMsg);
}
return oShippers;
}
[HttpGet]
[Route("api/Shipper/{ShipperID}")]
public ShipperAM getShipper(int ShipperID = 0)
{
ShipperAM oShipper = new ShipperAM();
ShipperDao dao = new ShipperDao();
try
{
oShipper = dao.getShipper(ShipperID);
}
catch (Exception ex)
{
ErrMsgInfo oErr = new ErrMsgInfo();
oErr.ex = ex;
HttpResponseMessage RepMsg = oErr.RepMsg;
throw new HttpResponseException(RepMsg);
}
return oShipper;
}
[HttpPost]
[Route("api/Shipper/Insert")]
public JsonRltInfo InsertItem([FromBody]ShipperAM oShipper)
{
JsonRltInfo oRlt = new JsonRltInfo();
ShipperDao dao = new ShipperDao();
try
{
string rc = dao.InsertShipper(oShipper);
if (rc == "Success")
{
oRlt.rltCode = 0;
oRlt.rltMsg = "新增成功";
}
}
catch (Exception ex)
{
oRlt.rltCode = 417;
oRlt.rltMsg = ex.Message;
}
return oRlt;
}
[HttpPost]
[Route("api/Shipper/Update")]
public JsonRltInfo UpdateItem([FromBody]ShipperAM oShipper)
{
JsonRltInfo oRlt = new JsonRltInfo();
ShipperDao dao = new ShipperDao();
try
{
string rc = dao.ChgShipper(oShipper);
if (rc == "Success")
{
oRlt.rltCode = 0;
oRlt.rltMsg = "更新成功";
}
}
catch (Exception ex)
{
oRlt.rltCode = 417;
oRlt.rltMsg = ex.Message;
}
return oRlt;
}
[HttpGet]
[Route("api/Shipper/Del/{ShipperID}")]
public JsonRltInfo DelItem(int ShipperID)
{
JsonRltInfo oRlt = new JsonRltInfo();
ShipperDao dao = new ShipperDao();
try
{
string rc = dao.DelShipper(ShipperID);
if (rc == "Success")
{
oRlt.rltCode = 0;
oRlt.rltMsg = "刪除成功";
}
}
catch (Exception ex)
{
oRlt.rltCode = 417;
oRlt.rltMsg = ex.Message;
}
return oRlt;
}
}
}
以上就是 WebAPI 相關程式碼的部分。
Blazor
接下來就要看看Blazor的部分。一樣的,我這邊不會把所有的Blazor的每一隻程式碼都放上來,只放我覺得必要的。一樣,如果網友們覺得看不懂,需要我補充,請留言通知一下。
Blazor : Data
首先是 ShipperVM 的類別。
namespace BlazorCRUDLifeCycle.Data
{
public class ShipperVM
{
public int ShipperID { get; set; } = 0;
public string CompanyName { get; set; } = "";
public string Phone { get; set; } = "";
}
}
WebAPI Domain 的名稱,就存在一個類別 UriWebAPI 中
namespace PCAT_Blazor.Data
{
public class UriWebAPI
{
private readonly string _uriServer = "http://localhost:55056";
public string UriServer => _uriServer;
}
}
用來接收 WebAPI 回傳回來的內容
namespace PCAT_Blazor.Data
{
public class JsonRltInfo
{
public int rltCode { get; set; } = 0;
public string rltMsg { get; set; } = "";
}
}
用來處理 Bootstrap Modal 顯示訊息的類別
namespace BlazorCRUDLifeCycle.Data
{
public class MsgInfo
{
public string Msg { get; set; } = "";
public string Title { get; set; } = "";
public bool showMsg { get; set; } = false;
}
}
Blazor : DAOs
這裡把透過 HttpClient 呼叫 WebAPI 進行 CRUD 的部分集中在這個類別中
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using BlazorCRUDLifeCycle.Data;
using PCAT_Blazor.Data;
namespace BlazorCRUDLifeCycle.DAOs
{
public class ShipperDao
{
private string urlServer = "";
public ShipperDao()
{
UriWebAPI oUriWebAPI = new UriWebAPI();
urlServer = oUriWebAPI.UriServer;
}
#region 查詢
public async Task<List<ShipperVM>> GetShippersAsync()
{
List<ShipperVM> oShippers = new List<ShipperVM>();
try
{
HttpClient client = new HttpClient();
string sUrl = urlServer + "/api/Shipper";
HttpResponseMessage response = await client.GetAsync(sUrl);
string jData = await response.Content.ReadAsStringAsync();
oShippers = JsonSerializer.Deserialize<List<ShipperVM>>(jData);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return oShippers;
}
public async Task<ShipperVM> GetShipperAsync(int ShipperID)
{
ShipperVM oShipper = new ShipperVM();
try
{
HttpClient client = new HttpClient();
string sUrl = urlServer + "/api/Shipper/" + ShipperID.ToString(); ;
HttpResponseMessage response = await client.GetAsync(sUrl);
string jData = await response.Content.ReadAsStringAsync();
oShipper = JsonSerializer.Deserialize<ShipperVM>(jData);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return oShipper;
}
#endregion
#region 維護
public async Task<string> InsertItemAsync(ShipperVM oShipper)
{
string rc = "";
try
{
string sUrl = urlServer + "/api/Shipper/Insert";
string jsonData = JsonSerializer.Serialize<ShipperVM>(oShipper);
HttpClient http = new HttpClient();
HttpContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.PostAsync(sUrl, content);
if (response.IsSuccessStatusCode)
{
string jRlt = await response.Content.ReadAsStringAsync();
JsonRltInfo oRlt = JsonSerializer.Deserialize<JsonRltInfo>(jRlt);
if (oRlt.rltCode == 0)
{
rc = oRlt.rltMsg;
}
else
{
throw new Exception(oRlt.rltMsg);
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return rc;
}
public async Task<string> UpdateItemAsync(ShipperVM oShipper)
{
string rc = "";
try
{
string sUrl = urlServer + "/api/Shipper/Update";
string jsonData = JsonSerializer.Serialize<ShipperVM>(oShipper);
HttpClient http = new HttpClient();
HttpContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.PostAsync(sUrl, content);
if (response.IsSuccessStatusCode)
{
string jRlt = await response.Content.ReadAsStringAsync();
JsonRltInfo oRlt = JsonSerializer.Deserialize<JsonRltInfo>(jRlt);
if (oRlt.rltCode == 0)
{
rc = oRlt.rltMsg;
}
else
{
throw new Exception(oRlt.rltMsg);
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return rc;
}
public async Task<string> DelItemAsync(int ShipperID)
{
string rc = "";
try
{
HttpClient client = new HttpClient();
string sUrl = urlServer + "/api/Shipper/Del/" + ShipperID.ToString();
HttpResponseMessage response = await client.GetAsync(sUrl);
string jData = await response.Content.ReadAsStringAsync();
JsonRltInfo oRlt = JsonSerializer.Deserialize<JsonRltInfo>(jData);
if (oRlt.rltCode == 0)
{
rc = oRlt.rltMsg;
}
else
{
throw new Exception(oRlt.rltMsg);
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return rc;
}
#endregion
}
}
在 Startup.cs 將DAO宣告成可相依注入的服務
接著開啟 Startup.cs ,在 ConfigureServices 裡面將類別家入為服務。加入【services.AddSingleton<ShipperDao>();】如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddSingleton<ShipperDao>();
}
Blazor : Component
先新增一個用 Bootstrap Modal 來顯示訊息的 Component 。
【ShowMsg.razor】:
@if (showMsg)
{
<!-- Modal -->
<div class="modal fade show" id="exampleModal" tabindex="-1" style="display:block" role="dialog" aria-modal="true" aria-labelledby="exampleModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">@Title</h5>
<button type="button" class="close" aria-label="Close" @onclick="CloseMsg">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
@((MarkupString)Msg)
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary" @onclick="CloseMsg">關閉</button>
</div>
</div>
</div>
</div>
<!--End Modal-->
}
@code {
[Parameter]
public bool showMsg { get; set; } = false;
[Parameter]
public string Title { get; set; } = "";
[Parameter]
public string Msg { get; set; } = "";
public override async Task SetParametersAsync(ParameterView parameters)
{
await base.SetParametersAsync(parameters);
}
private void CloseMsg()
{
showMsg = false;
}
}
接著是重頭戲了,Shipper.razor 如下:
@page "/Shipper"
@using BlazorCRUDLifeCycle.Data
@using BlazorCRUDLifeCycle.DAOs
@inject ShipperDao ShipperService
@inject IJSRuntime ijJS
@inject NavigationManager ijNavigationManager
<p>@NowTime</p>
@if (showList)
{
<div class="row" id="divList">
<div class="col-12">
<button class="btn btn-outline-primary" @onclick="btnAddNew">新增</button>
<table class="table table-bordered table-hover table-striped">
<thead>
<tr class="bg-primary text-white">
<th>功能</th>
<th>ShipperID</th>
<th>ComponyName</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
@foreach (var tShipper in oShippers)
{
<tr>
<td>
<button class="btn btn-outline-primary" @onclick="(e=>editItm(tShipper))">編</button>
</td>
<td>@tShipper.ShipperID</td>
<td>@tShipper.CompanyName</td>
<td>@tShipper.Phone</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
@if (showEdit)
{
<div class="row" id="divEdit">
<div class="col-sm-1"></div>
<div class="col-sm-10">
<div class="card border-primary mb-3">
<div class="card-header">
<h3>Shipper維護</h3>
</div>
<div class="card-body">
<fieldset>
<div class="form-group row">
<label for="staticEmail" class="col-sm-2 col-form-label">ShipperID</label>
<div class="col-sm-10">
<input type="text" readonly class="form-control" id="txtShipperID" @bind="oShipper.ShipperID">
</div>
</div>
<div class="form-group row">
<label for="txtCompanyName" class="col-sm-2 col-form-label">CompanyName</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="txtCompanyName" @bind="oShipper.CompanyName">
</div>
</div>
<div class="form-group row">
<label for="txtPhone" class="col-sm-2 col-form-label">Phone</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="txtPhone" @bind="oShipper.Phone">
</div>
</div>
</fieldset>
</div>
<div class="card-footer">
@if (showBtnInsert)
{
<button class="btn btn-outline-primary" @onclick="InsertItem">新增</button>
}
@if (showBtnUpdate)
{
<button class="btn btn-outline-primary" @onclick="UpdateItem">修改</button>
}
@if (showBtnDel)
{
<button class="btn btn-outline-secondary" @onclick="DelItem">刪除</button>
}
<button class="btn btn-outline-secondary" @onclick="btnCancelClick">取消</button>
</div>
</div>
</div>
<div class="col-sm-1"></div>
</div>
}
<ShowMsg Title="@oMsg.Title" Msg="@oMsg.Msg" showMsg="@oMsg.showMsg"></ShowMsg>
@code {
#region 變數設定
private List<ShipperVM> oShippers = new List<ShipperVM>();
private ShipperVM oShipper = new ShipperVM();
private MsgInfo oMsg = new MsgInfo();
private string NowTime = "";
private bool showList = true;
private bool showEdit = false;
private bool showBtnInsert = false;
private bool showBtnUpdate = false;
private bool showBtnDel = false;
private string ErrMsg = "";
#endregion
#region 初始化
protected async override Task OnInitializedAsync()
{
oShippers = await ShipperService.GetShippersAsync();
await base.OnInitializedAsync();
}
private void EditInit()
{
oShipper = new ShipperVM();
showBtnInsert = false;
showBtnUpdate = false;
showBtnDel = false;
}
#endregion
#region 檢查輸入
private bool ChkInput()
{
bool rc = true;
ErrMsg = "";
if(oShipper.CompanyName == "")
{
ErrMsg += "請輸入CompanyName<br>";
rc = false;
}
if(oShipper.Phone == "")
{
ErrMsg += "請輸入Phone<br>";
rc = false;
}
return rc;
}
#endregion
#region 開啟編輯區
private void btnAddNew()
{
showList = false;
showEdit = true;
EditInit();
showBtnInsert = true;
}
private void editItm(ShipperVM tShipper)
{
showList = false;
EditInit();
oShipper = tShipper;
showEdit = true;
showBtnUpdate = true;
showBtnDel = true;
}
#endregion
#region 編輯區按鈕
/// <summary>
/// 新增
/// </summary>
public async void InsertItem()
{
if (ChkInput())
{
string rc = await ShipperService.InsertItemAsync(oShipper);
if (rc != "")
{
EditInit();
showEdit = false;
showList = true;
oShippers = await ShipperService.GetShippersAsync();
oMsg.Title = "訊息:";
oMsg.Msg = rc;
oMsg.showMsg = true;
StateHasChanged();
}
else
{
oMsg.Title = "訊息:";
oMsg.Msg = "新增失敗";
oMsg.showMsg = true;
StateHasChanged();
}
}
else
{
oMsg.Title = "訊息:";
oMsg.Msg = ErrMsg;
oMsg.showMsg = true;
StateHasChanged();
}
}
/// <summary>
/// 修改
/// </summary>
public async void UpdateItem()
{
if (ChkInput())
{
string rc = await ShipperService.UpdateItemAsync(oShipper);
if (rc != "")
{
EditInit();
showEdit = false;
showList = true;
oShippers = await ShipperService.GetShippersAsync();
oMsg.Title = "訊息:";
oMsg.Msg = rc;
oMsg.showMsg = true;
StateHasChanged();
}
else
{
oMsg.Title = "訊息:";
oMsg.Msg = "修改失敗";
oMsg.showMsg = true;
StateHasChanged();
}
}
else
{
oMsg.Title = "訊息:";
oMsg.Msg = ErrMsg;
oMsg.showMsg = true;
StateHasChanged();
}
}
/// <summary>
/// 刪除
/// </summary>
public async void DelItem()
{
bool confirmed = await ijJS.InvokeAsync<bool>("confirm", "您確定要刪除此資料嗎?");
if (confirmed)
{
string rc = await ShipperService.DelItemAsync(oShipper.ShipperID);
if (rc != "")
{
EditInit();
showEdit = false;
showList = true;
oShippers = await ShipperService.GetShippersAsync();
oMsg.Title = "訊息:";
oMsg.Msg = rc;
oMsg.showMsg = true;
StateHasChanged();
}
}
else
{
oMsg.Title = "訊息:";
oMsg.Msg = ErrMsg;
oMsg.showMsg = true;
StateHasChanged();
}
}
/// <summary>
/// 取消
/// </summary>
private async void btnCancelClick()
{
bool confirmed = await ijJS.InvokeAsync<bool>("confirm", "您確定要放棄維護嗎?");
if (confirmed)
{
EditInit();
showEdit = false;
showList = true;
oShippers = await ShipperService.GetShippersAsync();
StateHasChanged();
}
}
#endregion
}
這裡提醒一下,由於 WebAPI 透過非同步的方式處理,從 Blazor的生命週期中 ,畫面的更新會有一般的更新與非同步兩個部分。小喵在測試的時候,非同步後的畫面異動相關,都會慢一步,後來查文件得知,資料非同步取得後,要通知Blazor進行狀態修改後的處理,記得要呼叫【StateHasChanged();】來啟動 Server 端通知畫面的處理與更新。
問題
在完成這一篇後,小喵在測試的過程中,發現了一個詭異的問題。
當小喵按下新增,資料都不輸入的情況下,按下新增資料,那麼程式在 ChkInput 會檢查輸入,傳回錯誤資料沒有輸入,然後啟動對話視窗,顯示錯誤訊息。一切看似美好~
但~
一樣都不輸入,再次按下新增,理論上會再次顯示對話視窗,再次提示錯誤訊息。然而卻一點動靜都沒有!!即使是逐步的執行,該給的資料都有,也都oMsg.showMsg=true;
一樣接下來沒有動靜。
這個問題與解決方式,就留下一篇來說明。
[筆記][Blazor]使用對話視窗Component無法動作與解決方式
末記
其實整個過程與 Vue 的開發類似,唯一不一樣的是非同步存取會影響畫面雙向綁定的即時性,記得要透過【StateHasChanged();】來通知 Blazor 通知 Server 端狀態已經改變,該重新處理畫面了。
相關的程式碼,小喵放到 GitHub 中,相關網址如下:
topcattw/Blazor-CRUD-with-WebAPI-Sample: Blazor CRUD with WebAPI Sample (github.com)
以下是簽名:
- 歡迎轉貼本站的文章,不過請在貼文主旨上加上【轉貼】,並在文章中附上本篇的超連結與站名【topcat姍舞之間的極度凝聚】,感恩大家的配合。
- 小喵大部分的文章會以小喵熟悉的語言VB.NET撰寫,如果您需要C#的Code,也許您可以試著用線上的工具進行轉換,這裡提供幾個參考
Microsoft MVP Visual Studio and Development Technologies (2005~2019/6) | topcat Blog:http://www.dotblogs.com.tw/topcat |