【第四篇】ASP.NET MVC快速入門之完整示例(MVC5+EF6)
目錄
【第一篇】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()
完成一個典型的數據庫分頁需要如下幾部:
- OrderBy:指定排序列
- Skip:跳過多少條記錄
- Take:返回的最大記錄數
上面的OrderBy是必須指定的,否則就會報錯:
分頁SQL語句
完成上面的代碼,分頁效果已經出來了:
下面,我們使用第三方Express Profiler工具來檢查EF生成的數據庫分頁SQL語句。
首先下載工具:
http://expressprofiler.codeplex.com/
打開Express Profiler,在Server文本框中輸入(LocalDb)\MSSQLLocalDB,如果你使用的VS2013,這個字符串可能是:(LocalDb)\v11.0,點擊綠色的啟用按鈕:
運行我們的示例,轉到學生列表頁面,然後清空Express Profiler中的全部顯示,再點擊第二頁:
可以看到這裏有3次SQL查詢,這個和我們的心理預期是一樣的:
- 第一次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();
- 第二次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();
- 第三次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方法。
同時處理表單檢索和數據庫分頁
不過目前遇到點難題,我們希望實現如下兩個功能:
- 點擊分頁鏈接時會發出HTTP POST請求,在請求參數中帶上表單檢索值。
- 表單檢索時,在請求參數中帶上當前所在的分頁索引。
實現這兩個功能才算完善,否則表單檢索時如果丟失分頁參數,就會回到第一頁;而分頁時如果丟失表單參數,就會清空表單輸入框。
是不是開始懷念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請求、表單身份驗證、跨站請求偽造、過多提交攻擊、表單檢索、數據庫分頁。
下載示例源代碼
文章出處:http://www.cnblogs.com/sanshi/p/6211259.html
【第四篇】ASP.NET MVC快速入門之完整示例(MVC5+EF6)