在學習 C# 與資料庫的互動方式,有一個常見且實用的教學就是會員登入、註冊與修改會員資料等範例,學習過程中會用到資料庫新增、修改與查詢動作,是理解程式與資料庫互動的常見程式碼。 當學會了這個範例,將來為客戶開發系統的時候,馬上可以派上用場。
此篇文章是繼上一篇文章: 前台會員登入範例 #CH2 接續教學。
範例內容主要以 ASP.NET MVC 為核心,前端使用 Vue.js 框架,而後端使用 SQL Server 當資料庫。
Vue.js 是前端3 大主流框架的其中之一,目標是透過簡單的 API 提供開發者實作資料綁定與操作網頁上的元件,Vue.js 的核心把焦點關注在狀態與畫面的同步層級上,適合與其他 JavsScript 函式庫整合,同時也適合當作 ASP.NET MVC 的前端框架。
SQL Server 是微軟推出的關聯式資料庫,使用 SQL 語言就可以輕鬆操作資料庫。
編寫此教學文章是為了幫助更多新加入的軟體工程師們,有更簡單實用的範例,可以快速學習程式語言。 這次我將會簡化這個基礎必學的前端會員範例,適合剛接觸 C# 與資料庫程式的新手學習。 文末有提供此操作範例的完整程式碼下載,有需要可以自行下載瀏覽。
目錄
1 在 MemberController 增加修改個人資料頁面
1.1 增加修改個人資料頁面 View
2 編寫修改個人資料 View 語法
2.1 加入 Vue.js 控制元件
3 編寫Controller 語法
3.1 取得個人資料 Controller 語法
3.2 修改個人資料 Controller 語法
3.3 修改密碼 Controller 語法
3.4 增加修改個人資料 Model
4 測試修改個人資料功能
5 重點整理
在 MemberController 增加修改個人資料頁面
在上一篇已經完成了 MemberController.cs 的建立。 這裡要新增一個修改個人資料的畫面,在 MemberController 類別內,增加 EditProfile() 是呈現畫面的 Action。
// GET: 修改個人資料頁面
public ActionResult EditProfile()
{
return View();
}
增加修改個人資料頁面 View
編寫修改個人資料 View 語法
在 Bootstrap 3 的官方範例,有提供表單的範例、面版的範例及按鈕的範例。 我從 Bootstrap 3 範例中語法組合變成我的修改個人資料畫面。
<!--使用 Bootstrap 設計登入表單-->
<div class="panel panel-primary">
<div class="panel-heading">修改個人資料範例</div>
<div class="panel-body">
<div class="form-group">
<label>帳號</label>
<p class="form-control-static"></p>
</div>
<div class="form-group">
<label>姓名</label>
<input type="text" class="form-control">
</div>
<div class="form-group">
<label>Email</label>
<input type="text" class="form-control">
</div>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-primary">修改個人資料</button>
</div>
</div>
<!--使用 Bootstrap 設計登入表單-->
<div class="panel panel-primary">
<div class="panel-heading">修改密碼範例</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>修改密碼</label>
<input type="password" class="form-control">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>確認新密碼</label>
<input type="password" class="form-control">
</div>
</div>
</div>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-primary">修改密碼</button>
</div>
</div>
加入 Vue.js 控制元件
我們在前面 _Layout.cshtml 已經加了 Vue.js 的底層元件,所以這頁面,就可以套用 Vue.js 的寫法。 我將剛剛的 HTML 修改一下,加入了 Vue.js 語法,並增加 GetUserProfile()
,DoEditProfile()
,DoEditPwd()
3 個方法,可傳送表單到 Controller 頁面。 我額外增加了 Bootstrap 的 modal 樣式,來顯示後端執行時的錯誤,這樣方便 Debug。 以下程式碼可以整個取代 EditProfile.cshtml 內容。
<div id="VuePage">
<!--使用 Bootstrap 設計登入表單-->
<div class="panel panel-primary">
<div class="panel-heading">修改個人資料範例</div>
<div class="panel-body">
<div class="form-group">
<label>帳號</label>
<p class="form-control-static">{{form.UserID}}</p>
</div>
<div class="form-group">
<label>姓名</label>
<input type="text" class="form-control" v-model="form.UserName">
</div>
<div class="form-group">
<label>Email</label>
<input type="text" class="form-control" v-model="form.UserEmail">
</div>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-primary" v-on:click="DoEditProfile()">修改個人資料</button>
</div>
</div>
<!--使用 Bootstrap 設計登入表單-->
<div class="panel panel-primary">
<div class="panel-heading">修改密碼範例</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>修改密碼</label>
<input type="password" class="form-control" v-model="form.NewUserPwd">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>確認新密碼</label>
<input type="password" class="form-control" v-model="form.CheckUserPwd">
</div>
</div>
</div>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-primary" v-on:click="DoEditPwd()">修改密碼</button>
</div>
</div>
<!--使用 Bootstrap Modal 樣式,當執行有錯誤時,顯示錯誤訊息-->
<div class="modal fade" id="ErrorAlert" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">錯誤訊息</h4>
</div>
<div class="modal-body" id="ErrorMsg" style="overflow-x:auto;width:100%;">
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>
@section scripts {
<script>
var VuePage = new Vue({
el: '#VuePage'
, data: function () {
var data = {
form: {}
};
// 設定表單初始值
data.form = {
UserID: ""
, UserName: ""
, UserEmail:""
}
return data;
}
// Vue 實體與掛載完成
, mounted: function () {
var self = this;
// 當 Vue 掛載完成,取得個人資料
self.GetUserProfile();
}
, methods: {
// 前端驗證權杖
GetToken: function () {
var token = '@Html.AntiForgeryToken()';
token = $(token).val();
return token;
}
// 取得個人資料
, GetUserProfile: function () {
var self = this;
var postData = {};
// 使用 jQuery Ajax 傳送至後端
$.ajax({
url:'@Url.Content("~/Member/GetUserProfile")',
method:'POST',
dataType:'json',
data: { inModel: postData },
success: function (datas) {
if (datas.ErrMsg) {
alert(datas.ErrMsg);
return;
}
self.form.UserID = datas.UserID;
self.form.UserName = datas.UserName;
self.form.UserEmail = datas.UserEmail;
},
error: function (err) {
$('#ErrorMsg').html(err.responseText);
$('#ErrorAlert').modal('toggle');
},
});
}
// 修改個人資料
, DoEditProfile: function () {
var self = this;
// 組合表單資料
var postData = {};
postData['UserName'] = self.form.UserName;
postData['UserEmail'] = self.form.UserEmail;
// 使用 jQuery Ajax 傳送至後端
$.ajax({
url:'@Url.Content("~/Member/DoEditProfile")',
method:'POST',
dataType:'json',
data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
success: function (datas) {
if (datas.ErrMsg) {
alert(datas.ErrMsg);
return;
}
alert(datas.ResultMsg);
},
error: function (err) {
$('#ErrorMsg').html(err.responseText);
$('#ErrorAlert').modal('toggle');
},
});
}
// 修改密碼
, DoEditPwd: function () {
var self = this;
// 組合表單資料
var postData = {};
postData['NewUserPwd'] = self.form.NewUserPwd;
postData['CheckUserPwd'] = self.form.CheckUserPwd;
// 使用 jQuery Ajax 傳送至後端
$.ajax({
url:'@Url.Content("~/Member/DoEditPwd")',
method:'POST',
dataType:'json',
data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
success: function (datas) {
if (datas.ErrMsg) {
alert(datas.ErrMsg);
return;
}
alert(datas.ResultMsg);
},
error: function (err) {
$('#ErrorMsg').html(err.responseText);
$('#ErrorAlert').modal('toggle');
},
});
}
}
})
</script>
}
我使用 Vue.js 生命週期中的 mounted 事件,當 Vue 掛載完成,就呼叫 GetUserProfile()
方法取得個人資料,取得後端會員資料後,再放到前端畫面上。 當使用者修改資料時,就呼叫 DoEditProfile()
或 DoEditPwd()
方法,將前端資料往後端送。 關於 Vue.js 的教學語法,可以到官網上面查詢,官網有完整的教學。
編寫Controller 語法
取得個人資料 Controller 語法
剛剛 View 在頁面載入完成時會呼叫 ~/Member/GetUserProfile
方法,以下是 GetUserProfile()
的 Controller 寫法。
/// <summary>
/// 取得個人資料
/// </summary>
/// <returns></returns>
public ActionResult GetUserProfile()
{
GetUserProfileOut outModel = new GetUserProfileOut();
// 檢查會員 Session 是否存在
if (Session["UserID"] == null || Session["UserID"].ToString() == "")
{
outModel.ErrMsg = "無會員登入記錄";
return Json(outModel);
}
// 取得連線字串
string connStr = System.Web.Configuration.WebConfigurationManager.ConnectionStrings["ConnDB"].ConnectionString;
// 當程式碼離開 using 區塊時,會自動關閉連接
using (SqlConnection conn = new SqlConnection(connStr))
{
// 資料庫連線
conn.Open();
// 取得會員資料
string sql = "select * from Member where UserID = @UserID";
SqlCommand cmd = new SqlCommand();
cmd.CommandText = sql;
cmd.Connection = conn;
// 使用參數化填值
cmd.Parameters.AddWithValue("@UserID", Session["UserID"]);
// 執行資料庫查詢動作
SqlDataAdapter adpt = new SqlDataAdapter();
adpt.SelectCommand = cmd;
DataSet ds = new DataSet();
adpt.Fill(ds);
DataTable dt = ds.Tables[0];
if (dt.Rows.Count > 0)
{
// 將資料回傳給前端
outModel.UserID = dt.Rows[0]["UserID"].ToString();
outModel.UserName = dt.Rows[0]["UserName"].ToString();
outModel.UserEmail = dt.Rows[0]["UserEmail"].ToString();
}
else
{
outModel.ErrMsg = "查無會員資料";
}
}
// 回傳 Json 給前端
return Json(outModel);
}
這方法主要是取得 Session 中的 UserID,將 UserID 查詢資料庫 Member 表中的 UserID 欄位,將資料表中的資料,回傳到前端去。
修改個人資料 Controller 語法
當在畫面上執行「修改個人資料」按鈕,View 會呼叫 ~/Member/DoEditProfile
,以下是 DoEditProfile()
的 Controller 寫法。
/// <summary>
/// 修改個人資料
/// </summary>
/// <param name="inModel"></param>
/// <returns></returns>
[ValidateAntiForgeryToken]
public ActionResult DoEditProfile(DoEditProfileIn inModel)
{
DoEditProfileOut outModel = new DoEditProfileOut();
// 檢查個人資料是否有輸入
if (string.IsNullOrEmpty(inModel.UserName) || string.IsNullOrEmpty(inModel.UserEmail))
{
outModel.ErrMsg = "請輸入資料";
return Json(outModel);
}
// 檢查會員 Session 是否存在
if (Session["UserID"] == null || Session["UserID"].ToString() == "")
{
outModel.ErrMsg = "無會員登入記錄";
return Json(outModel);
}
// 取得連線字串
string connStr = System.Web.Configuration.WebConfigurationManager.ConnectionStrings["ConnDB"].ConnectionString;
// 當程式碼離開 using 區塊時,會自動關閉連接
using (SqlConnection conn = new SqlConnection(connStr))
{
// 資料庫連線
conn.Open();
// 修改個人資料至資料庫
string sql = @"UPDATE Member SET UserName = @UserName, UserEmail = @UserEmail WHERE UserID = @UserID";
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = sql;
// 使用參數化填值
cmd.Parameters.AddWithValue("@UserID", Session["UserID"]);
cmd.Parameters.AddWithValue("@UserName", inModel.UserName);
cmd.Parameters.AddWithValue("@UserEmail", inModel.UserEmail);
// 執行資料庫更新動作
int Ret = cmd.ExecuteNonQuery();
if (Ret > 0)
{
outModel.ResultMsg = "修改個人資料完成";
}
else
{
outModel.ErrMsg = "無異動資料";
}
}
// 回傳 Json 給前端
return Json(outModel);
}
這次方法特別在開頭加一個 [ValidateAntiForgeryToken] 驗證,這是防止跨網站偽造要求的攻擊,也稱為 CSRF (Cross-Site Request Forgery) 攻擊,這是對 MVC 網頁提升安全性的做法。
在前端的 DoEditProfile()
方法內在 Ajax 傳送資料時,需要加一段 __RequestVerificationToken: self.GetToken()
參數,將前端驗證碼往後端傳送,後端才能驗證來源是合法來源。
修改資料時的 SQL 都是使用參數化填值方式,這是為了防止 SQL 注入攻擊。
此功能是先經過登入才能呈現的畫面,在登入成功時,已經帳號存入 Session 內,在此頁面帳號由 Session 來取得,就不需要由前端傳送了。
修改密碼 Controller 語法
當在畫面上執行「修改密碼」按鈕,View 會呼叫 ~/Member/DoEditPwd
,以下是 DoEditPwd()
的 Controller 寫法。
/// <summary>
/// 修改密碼
/// </summary>
/// <param name="inModel"></param>
/// <returns></returns>
[ValidateAntiForgeryToken]
public ActionResult DoEditPwd(DoEditPwdIn inModel)
{
DoEditPwdOut outModel = new DoEditPwdOut();
// 檢查是否有輸入密碼
if (string.IsNullOrEmpty(inModel.NewUserPwd))
{
outModel.ErrMsg = "請輸入修改密碼";
return Json(outModel);
}
if (string.IsNullOrEmpty(inModel.CheckUserPwd))
{
outModel.ErrMsg = "請輸入確認新密碼";
return Json(outModel);
}
if (inModel.NewUserPwd != inModel.CheckUserPwd)
{
outModel.ErrMsg = "新密碼與確認新密碼不相同";
return Json(outModel);
}
// 檢查會員 Session 是否存在
if (Session["UserID"] == null || Session["UserID"].ToString() == "")
{
outModel.ErrMsg = "無會員登入記錄";
return Json(outModel);
}
// 將新密碼使用 SHA256 雜湊運算(不可逆)
string salt = Session["UserID"].ToString().Substring(0, 1).ToLower(); //使用帳號前一碼當作密碼鹽
SHA256 sha256 = SHA256.Create();
byte[] bytes = Encoding.UTF8.GetBytes(salt + inModel.NewUserPwd); //將密碼鹽及新密碼組合
byte[] hash = sha256.ComputeHash(bytes);
StringBuilder result = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
result.Append(hash[i].ToString("X2"));
}
string NewPwd = result.ToString(); // 雜湊運算後密碼
// 取得連線字串
string connStr = System.Web.Configuration.WebConfigurationManager.ConnectionStrings["ConnDB"].ConnectionString;
// 當程式碼離開 using 區塊時,會自動關閉連接
using (SqlConnection conn = new SqlConnection(connStr))
{
// 資料庫連線
conn.Open();
// 修改個人資料至資料庫
string sql = @"UPDATE Member SET UserPwd = @UserPwd WHERE UserID = @UserID";
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = sql;
// 使用參數化填值
cmd.Parameters.AddWithValue("@UserID", Session["UserID"]);
cmd.Parameters.AddWithValue("@UserPwd", NewPwd);
// 執行資料庫更新動作
int Ret = cmd.ExecuteNonQuery();
if (Ret > 0)
{
outModel.ResultMsg = "修改密碼完成";
}
else
{
outModel.ErrMsg = "無異動資料";
}
}
// 回傳 Json 給前端
return Json(outModel);
}
修改密碼建議使用者輸入 2 次,以確保沒有手誤,同時在後端驗證 2 次密碼是否相同。 資料庫內的密碼建議經過雜湊運算後再儲存,以防止被盜取密碼時,使用者重要密碼外洩。 此功能是先經過登入才能呈現的畫面,登入帳號由 Session 來取得,就不需要由前端傳送了,修改資料時的會員帳號也是由 Session 內的值提供。
增加修改個人資料 Model
剛剛在 Controller 建立時,新增了一些新參數類別及新回傳類別,打開 MemberModel.cs 檔案,在裡面增加新增的類別。
/// <summary>
/// 取得個人資料回傳
/// </summary>
public class GetUserProfileOut
{
public string ErrMsg { get; set; }
public string UserID { get; set; }
public string UserName { get; set; }
public string UserEmail { get; set; }
}
/// <summary>
/// 修改個人資料參數
/// </summary>
public class DoEditProfileIn
{
public string UserName { get; set; }
public string UserEmail { get; set; }
}
/// <summary>
/// 修改個人資料回傳
/// </summary>
public class DoEditProfileOut
{
public string ErrMsg { get; set; }
public string ResultMsg { get; set; }
}
/// <summary>
/// 修改密碼參數
/// </summary>
public class DoEditPwdIn
{
public string NewUserPwd { get; set; }
public string CheckUserPwd { get; set; }
}
/// <summary>
/// 修改密碼回傳
/// </summary>
public class DoEditPwdOut
{
public string ErrMsg { get; set; }
public string ResultMsg { get; set; }
}
關於 Controller 與 View 之間的資料傳遞物件都定義在 Model 裡面
測試修改個人資料功能
之前做登入的時候,登入成功只是 alert 訊息而已,這次在 alert 之後,增加導向到「修改個人資料」頁面。
打開 \Views\Member\Login.cshtml 頁面,在 DoLogin() 方法內增加以下語法。
window.location = '@Url.Content("~/Member/EditProfile")';
重點整理
在 Controller 增加 EditProfile() 顯示修改個人資料畫面
使用 Bootstrap 樣式可快速製作美觀的表單
在 View 增加取得個人資料、修改個人資料及修改密碼的方法
在 Controller 後端增加對應的 3 個方法
Model 定義 Controller 與 View 之間的資料傳遞物件