目錄
【第一篇】ASP.NET MVC快速入門之數據庫操作(MVC5+EF6)
【第二篇】ASP.NET MVC快速入門之數據注解(MVC5+EF6)
【第三篇】ASP.NET MVC快速入門之安全策略(MVC5+EF6)
【第四篇】ASP.NET MVC快速入門之完整示例(MVC5+EF6)
【番外篇】ASP.NET MVC快速入門之免費jQuery控件庫(MVC5+EF6)
請關注三石的博客:http://cnblogs.com/sanshi
完善數據注解
到目前為止的表格頁面效果:
我們需要更多的數據注解,來限制各個屬性,以及提供顯示用的名稱(而不是英文字符串):
public class Student { public int ID { get; set; } [Display(Name = "姓名")] [Required] [StringLength(200, MinimumLength = 2)] public string Name { get; set; } [Display(Name = "性別")] [Required] [Range(0, 1)] public int Gender { get; set; } [Display(Name = "所學專業")] [Required] [StringLength(200)] public string Major { get; set; } [Display(Name = "入學日期")] [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime EntranceDate { get; set; } }
再次運行,表格頁面效果:
完善性別的顯示
表格頁面-性別列顯示為中文
這個比較簡單,將原來的:
@Html.DisplayFor(modelItem => item.Gender)
修改為:
@if (item.Gender == 1)
{
@:男
} else
{
@:女
}
新建編輯頁面-性別顯示為下拉列表
原來的編輯頁面:
性別字段的編輯框是通過如下方式生成的:
@Html.EditorFor(model => model.EntranceDate, new { htmlAttributes = new { @class = "form-control" } })
Html輔助方法EditorFor會查看模型屬性的類型,自動生成對應的表單輸入框。由於性別字段是整形,所以這里默認會生成一個數字輸入框。
為了更加友好的顯示,我們將性別改為下拉列表,並且僅允許用戶從下拉項中選擇。首先我們需要准備下拉列表選項的集合,並通過控制器傳遞給視圖使用:
定義獲取性別集合的函數,由於需要多個地方使用,所以提取成一個公共方法:
private List<SelectListItem> GetGenderList() { return new List<SelectListItem>() { new SelectListItem { Text = "男", Value = "1" },new SelectListItem { Text = "女", Value = "0" } }; }
通過ViewBag.GenderList傳入視圖:
// GET: Students/Edit/5 public ActionResult Edit(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } ViewBag.GenderList = GetGenderList(); return View(student); }
視圖中通過DropDownListFor強類型輔助方法,來顯示下拉列表以及選中項:
@Html.DropDownListFor(model => model.Gender, ViewBag.GenderList as IEnumerable<SelectListItem>, new { @class = "form-control" })
表單提交時的代碼和之前一樣,多了一個對GetGenderList的調用:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit([Bind(Include = "ID,Name,Gender,Major,EntranceDate")] Student student) { if (ModelState.IsValid) { db.Entry(student).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } ViewBag.GenderList = GetGenderList(); return View(student); }
這一點非常重要,雖然正常的提交操作不會再次返回當前視圖(RedirectToAction直接指定了頁面跳轉),但是在模型綁定失敗時(嘗試禁用JavaScript,姓名留空,然后提交表單),如果不重新設置ViewBag.GenderList參數就會出錯:
表單檢索
下面我們為表格頁面增加一個搜索表單,用來對表格數據進行過濾。
先增加一些記錄:
添加表單檢索字段:
@using (Html.BeginForm()) { @Html.AntiForgeryToken() <p> 所學專業: @Html.DropDownList("Major", ViewBag.MajorList as IEnumerable<SelectListItem>, "全部") 姓名: @Html.TextBox("Name") <input type="submit" value="檢索" /> </p> }
由於本示例比較簡單,沒有單獨的表來存儲所學專業,因此我們需要從用戶表中檢索,並存儲到ViewBag.MajorList中傳入視圖:
private List<SelectListItem> GetMajorList() { var majors = db.Students.OrderBy(m => m.Major).Select(m => m.Major).Distinct(); var items = new List<SelectListItem>(); foreach(string major in majors) { items.Add(new SelectListItem { Text = major, Value = major }); } return items; } // GET: Students public ActionResult Index() { ViewBag.MajorList = GetMajorList(); return View(db.Students.ToList()); }
頁面運行效果:
增加POST請求的處理方法:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Index(string Major, string Name) { var students = db.Students as IQueryable<Student>; if (!String.IsNullOrEmpty(Name)) { students = students.Where(m => m.Name.Contains(Name)); } if (!String.IsNullOrEmpty(Major)) { students = students.Where(m => m.Major == Major); } ViewBag.MajorList = GetMajorList(); return View(students.ToList()); }
此時的運行效果:
數據庫分頁
分頁工具條
首先改造視圖代碼,增加分頁工具條:
<div id="pagebar"> @for (var i = 0; i < ViewBag.PageCount; i++) { if (i == ViewBag.PageIndex) { <span class="currentpagenumber">@(i + 1)</span> } else { <a class="pagenumber" href="javascript:;">@(i + 1)</a> } } </div>
其中ViewBag.PageIndex和ViewBag.PageCount是由控制器傳入的分頁參數,我們需要這兩個數據來構造分頁鏈接,如果是當前分頁就顯示為文本,如果是其他頁就顯示為超鏈接,然后通過客戶端JavaScript來注冊點擊事件。
EF的數據庫分頁
后台控制器代碼:
private static readonly int PAGE_SIZE = 3; private int GetPageCount(int recordCount) { int pageCount = recordCount / PAGE_SIZE; if (recordCount % PAGE_SIZE != 0) { pageCount += 1; } return pageCount; } private List<Student> GetPagedDataSource(IQueryable<Student> students, int pageIndex, int recordCount) { var pageCount = GetPageCount(recordCount); if (pageIndex >= pageCount && pageCount >= 1) { pageIndex = pageCount - 1; } return students.OrderBy(m => m.Name) .Skip(pageIndex * PAGE_SIZE) .Take(PAGE_SIZE).ToList(); } // GET: Students public ActionResult Index() { var students = db.Students as IQueryable<Student>; var recordCount = students.Count(); var pageCount = GetPageCount(recordCount); ViewBag.PageIndex = 0; ViewBag.PageCount = pageCount; ViewBag.MajorList = GetMajorList(); return View(GetPagedDataSource(students, 0, recordCount)); }
EF為我們封裝了大部分的細節,所以上面的數據庫分頁代碼非常直觀和容易理解:
students
.OrderBy(m => m.Name)
.Skip(pageIndex * PAGE_SIZE)
.Take(PAGE_SIZE).ToList()
完成一個典型的數據庫分頁需要如下幾部:
1. OrderBy:指定排序列
2. Skip:跳過多少條記錄
3. Take:返回的最大記錄數
上面的OrderBy是必須指定的,否則就會報錯:
分頁SQL語句
完成上面的代碼,分頁效果已經出來了:
下面,我們使用第三方Express Profiler工具來檢查EF生成的數據庫分頁SQL語句。
首先下載工具:
http://expressprofiler.codeplex.com/
打開Express Profiler,在Server文本框中輸入(LocalDb)\MSSQLLocalDB,如果你使用的VS2013,這個字符串可能是:(LocalDb)\v11.0,點擊綠色的啟用按鈕:
運行我們的示例,轉到學生列表頁面,然后清空Express Profiler中的全部顯示,再點擊第二頁:
可以看到這里有3次SQL查詢,這個和我們的心理預期是一樣的:
1. 第一次SQL查詢:總記錄數
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[Students] AS [Extent1]
) AS [GroupBy1]
go
對應的C#代碼:
var students = db.Students as IQueryable<Student>;
var recordCount = students.Count();
2. 第二次SQL查詢:所學專業集合(去除重復)
SELECT
[Distinct1].[Major] AS [Major]
FROM ( SELECT DISTINCT
[Extent1].[Major] AS [Major]
FROM [dbo].[Students] AS [Extent1]
) AS [Distinct1]
go
對應的C#代碼:
var majors = db.Students.OrderBy(m => m.Major).Select(m => m.Major).Distinct();
3. 第三次SQL查詢:分頁數據
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[Gender] AS [Gender],
[Extent1].[Major] AS [Major],
[Extent1].[EntranceDate] AS [EntranceDate],
[Extent1].[Job] AS [Job]
FROM [dbo].[Students] AS [Extent1]
ORDER BY [Extent1].[Name] ASC
OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY
go
對應的C#代碼:
return students.OrderBy(m => m.Name)
.Skip(pageIndex * PAGE_SIZE)
.Take(PAGE_SIZE).ToList();
這個查詢順序也和前面的EF代碼的執行順序一模一樣,可以再回過頭看下控制器Index方法。
同時處理表單檢索和數據庫分頁
不過目前遇到點難題,我們希望實現如下兩個功能:
1. 點擊分頁鏈接時會發出HTTP POST請求,在請求參數中帶上表單檢索值。
2. 表單檢索時,在請求參數中帶上當前所在的分頁索引。
實現這兩個功能才算完善,否則表單檢索時如果丟失分頁參數,就會回到第一頁;而分頁時如果丟失表單參數,就會清空表單輸入框。
是不是開始懷念WebForms了,在WebForms中整個頁面都被包含在一個表單中,因此回發時根本不需要考慮哪些參數后台需要。而MVC中這個就需要我們操心了,畢竟在靈活性的面前,便利性就會有所打折。
我們采取的辦法是擴充前面的form標簽,加入PageIndex隱藏字段,然后點擊分頁鏈接時提交表單即可:
@using (Html.BeginForm("Index", "Students", FormMethod.Post, new { id = "searchForm" })) { @Html.AntiForgeryToken() <p> 所學專業: @Html.DropDownList("Major", ViewBag.MajorList as IEnumerable<SelectListItem>, "全部") 姓名: @Html.TextBox("Name") <input type="hidden" id="PageIndex" name="PageIndex" value="0" /> <input type="button" id="searchButton" value="檢索" /> </p> }
注冊JavaScript腳本來處理點擊[檢索]按鈕和分頁鏈接:
@section scripts { <script> function submitForm(pagenumber) { pagenumber = parseInt(pagenumber, 10); $('#PageIndex').val(pagenumber - 1); $('#searchForm').submit(); } $(function () { $('#searchButton').click(function () { submitForm($('#pagebar .currentpagenumber').text()); }); $('#pagebar .pagenumber').click(function () { submitForm($(this).text()); }); }); </script> }
現在看下效果,首先檢索所學專業:
然后點擊第二頁,會發出一個POST請求:
可以看到本次請求,上面的用戶輸入和PageIndex都發送到了控制器處理方法:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Index(string Major, string Name, int PageIndex) { var students = db.Students as IQueryable<Student>; if (!String.IsNullOrEmpty(Name)) { students = students.Where(m => m.Name.Contains(Name)); } if (!String.IsNullOrEmpty(Major)) { students = students.Where(m => m.Major == Major); } var recordCount = students.Count(); var pageCount = GetPageCount(recordCount); if (PageIndex >= pageCount && pageCount >= 1) { PageIndex = pageCount - 1; } students = students.OrderBy(m=>m.Name) .Skip(PageIndex * PAGE_SIZE).Take(PAGE_SIZE); ViewBag.PageIndex = PageIndex; ViewBag.PageCount = pageCount; ViewBag.MajorList = GetMajorList(); return View(students.ToList()); }
這里需要注意一點:先進行表單過濾,然后執行獲取總記錄數的查詢,最后再獲取分頁數據。這個順序不能變,因為表單過濾后總記錄才能確定下來。
小結
本篇文章對示例進行了完善,首先是添加更多的數據注解,然后將顯示的性別由數字改為字符串,對於編輯和新建頁面,將性別渲染為下拉列表。然后為表格頁面增加表單檢索功能,可以根據[所學專業]和[姓名]對表格過濾,最后完成了數據庫分頁功能。
本系列文章至此已經完成了,下面我們簡單總結下用到的關鍵詞:路由引擎、控制器向視圖傳值、強類型輔助方法、模型綁定、數據注解、數據遷移、客戶端驗證、服務器端模型驗證、模擬POST請求、表單身份驗證、跨站請求偽造、過多提交攻擊、表單檢索、數據庫分頁。