1. 程式人生 > >【第四篇】ASP.NET MVC快速入門之完整示例(MVC5+EF6)

【第四篇】ASP.NET MVC快速入門之完整示例(MVC5+EF6)

redirect name php sql語句 rop 方法 輸入框 一次 編輯

目錄

【第一篇】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();

  1. 第二次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();

  1. 第三次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請求、表單身份驗證、跨站請求偽造、過多提交攻擊、表單檢索、數據庫分頁。

下載示例源代碼

文章出處:http://www.cnblogs.com/sanshi/p/6211259.html

【第四篇】ASP.NET MVC快速入門之完整示例(MVC5+EF6)