C# 6 與 .NET Core 1.0 高級編程 - 41 ASP.NET MVC(中) Professional C# 6 and .NET Core 1.0 - Chapter 41 ASP.N

 譯文,我的原創,轉載請註明出處(C# 6 與 .NET Core 1.0 高級編程 - 41 ASP.NET MVC(中)),不對的地方歡迎指出與交流。 

章節出自《Professional C# 6 and .NET Core 1.0》。水平有限,各位閱讀時仔細分辨,惟望莫誤人子弟。

附英文版原文:Professional C# 6 and .NET Core 1.0 - Chapter 41 ASP.NET MVCcss

C# 6 與 .NET Core 1.0 高級編程 - 41 ASP.NET MVC(上)html

C# 6 與 .NET Core 1.0 高級編程 - 41 ASP.NET MVC(下)編程

------------------------------------  

從客戶端提交數據

直到如今,只使用來自客戶端的 HTTP GET 請求從服務器檢索HTML代碼。從客戶端發送表單數據怎麼辦?瀏覽器

爲了提交表單數據,爲控制器 SubmitData 建立視圖 CreateMenu。該視圖包含一個HTML表單元素,用於定義應將哪些數據發送到服務器。 form方法被聲明爲 HTTP POST 請求。定義輸入字段的 input 元素都具備與Menu類型的屬性對應的名稱(代碼文件 MVCSampleApp/Views/SubmitData/CreateMenu.cshtml):服務器

@{
  ViewBag.Title ="Create Menu";
}
<h2>Create Menu</h2>
<form action="/SubmitData/CreateMenu" method="post">
<fieldset>
  <legend>Menu</legend>
  <div>Id:</div>
  <input name="id" />
  <div>Text:</div>
  <input name="text" />
  <div>Price:</div>
  <input name="price" />
  <div>Category:</div>
  <input name="category" />
  <div></div>
  <button type="submit">Submit</button>
</fieldset>
</form>

圖41.7顯示了瀏覽器中打開的頁面。異步

圖41.7async

在 SubmitData 控制器中,建立兩個 CreateMenu 操做方法:一個用於 HTTP GET 請求,另外一個用於 HTTP POST 請求。由於C#容許不一樣的方法有相同的名稱,只須要參數號或類型不一樣。固然,這個要求與操做方法是一致。 Action方法也須要與HTTP請求方法不一樣。默認請求方法是GET,當你應用屬性 HttpPost 時,請求方法是POST。要讀取HTTP POST數據,能夠從 Request 對象使用信息。可是,定義有參數的 CreateMenu 方法要簡單得多。參數與表單字段的名稱相匹配(代碼文件MVCSampleApp/Controllers/SubmitDataController.cs):編輯器

public IActionResult Index() => View();
public IActionResult CreateMenu() => View(); [HttpPost] public IActionResult CreateMenu(int id, string text, double price, string category) { var m = new Menu { Id = id, Text = text, Price = price }; ViewBag.Info = $"menu created: {m.Text}, Price: {m.Price}, category: {m.Category}"; return View("Index"); }

要顯示結果,能夠顯示ViewBag.Info的值(代碼文件MVCSampleApp/Views/SubmitData/Index.cshtml):ide

@ViewBag.Info

模型綁定器

不只能夠在action方法使用多個參數,還還可使用包含與傳入字段名稱匹配的屬性的類型(代碼文件 MVCSampleApp/Controllers/SubmitDataController.cs):函數

[HttpPost]
public IActionResult CreateMenu2(Menu m) { ViewBag.Info = $"menu created: {m.Text}, Price: {m.Price}, category: {m.Category}"; return View("Index"); }

用戶使用表單提交數據時,將調用一個 CreateMenu 方法,顯示帶有提交的菜單數據的Index視圖,如圖41.8所示。

圖41.8

模型綁定器負責從 HTTP POST 請求傳輸數據。模型綁定器實現接口 IModelBinder。默認狀況下 FormCollectionModelBinder 類用於將輸入字段綁定到模型。這個binder支持基本類型,模型類(例如 Menu 類型)和實現  ICollection<T> , IList<T> 和  IDictionary<TKey, TValue> 的集合。

若是不是全部參數類型的屬性都要填充模型綁定器,則可使用 Bind 屬性。使用此屬性,能夠指定應包含在綁定中的屬性名稱列表。

還可使用沒有參數的操做方法將輸入數據傳遞到模型,以下一個代碼段所示。建立一個新的Menu類實例,並將此實例傳遞給 Controller 基類的 TryUpdateModelAsync 方法。若是更新後的模型在更新後不處於有效狀態,TryUpdateModelAsync 將返回false:

[HttpPost]
public async Task<IActionResult> CreateMenu3Result() { var m = new Menu(); bool updated = await TryUpdateModelAsync<Menu>(m); if (updated) { ViewBag.Info = $"menu created: {m.Text}, Price: {m.Price}, category: {m.Category}"; return View("Index"); } else { return View("Error"); } }

註釋和驗證

能夠向模型類型添加一些註釋,這些註釋用於更新數據的驗證。命名空間 System.ComponentModel.DataAnnotations 包含可用於客戶端上指定的信息數據並可用於驗證的特性類型。(譯者注:爲了區分 Attribute 和 property,將 Attribute 譯爲「特性」,property 譯爲「屬性」,兩者雖然含有屬性的意思,其實仍是有些區別的,感興趣的讀者能夠查閱相關資料,此處不做詳細介紹。)

這些添加的特性能夠在Menu類做更改(代碼文件MVCSampleApp/Models/Menu.cs):

public class Menu
{
  public int Id { get; set; } [Required, StringLength(50)] public string Text { get; set; } [Display(Name="Price"), DisplayFormat(DataFormatString="{0:C}")] public double Price { get; set; } [DataType(DataType.Date)] public DateTime Date { get; set; } [StringLength(10)] public string Category { get; set; } }

用於驗證的經常使用的特性類型有,CompareAttribute 用於比較不一樣的屬性,CreditCardAttribute用於驗證有效的信用卡號,EmailAddressAttribute 用於驗證電子郵件地址,EnumDataTypeAttribute用於將輸入與枚舉值進行比較,PhoneAttribute用於驗證電話號碼。

還可使用其餘特性來獲取顯示和錯誤消息的值,例如 DataTypeAttribute 和 DisplayFormatAttribute。

要使用驗證屬性,如下所示的操做方法中使用 ModelState.IsValid 可用於驗證模型的狀態(代碼文件MVCSampleApp/Controllers/SubmitDataController.cs):

[HttpPost]
public IActionResult CreateMenu4(Menu m) { if (ModelState.IsValid) { ViewBag.Info = $"menu created: {m.Text}, Price: {m.Price}, category: {m.Category}"; } else { ViewBag.Info ="not valid"; } return View("Index"); }

若是使用工具生成的模型類,您可能認爲很難向屬性添加特性。因爲工具生成的類被定義爲部分類,能夠經過添加屬性和方法,實現額外的接口,以及實現由工具生成的類使用的部分方法來擴展類。若是沒法更改類型的源代碼,則沒法向現有屬性和方法添加特性,但對於這種狀況有幫助!假設Menu類是由工具生成的部分類。而後,不一樣名稱的新類(例如,MenuMetadata)能夠定義與實體類相同的屬性,並添加註釋,以下所示:

public class MenuMetadata
{
  public int Id { get; set; } [Required, StringLength(25)] public string Text { get; set; } [Display(Name="Price"), DisplayFormat(DataFormatString="{0:C}")] public double Price { get; set; } [DataType(DataType.Date)] public DateTime Date { get; set; } [StringLength(10)] public string Category { get; set; } }

MenuMetadata類必須連接到Menu類。使用工具生成的部分類,能夠在同一命名空間中建立另外一個部分類型,以將MetadataType特性添加到建立鏈接的類型定義中:

[MetadataType(typeof(MenuMetadata))]
public partial class Menu { }

HTML Helper方法還能夠利用註釋向客戶端添加信息。

使用HTML Helpers

HTML Helpers 是建立HTML代碼的助手。在視圖中Razor語法能夠直接使用它們。

Html 是視圖基類 RazorPage 的屬性,屬於 IHtmlHelper 類型。 HTML Helper 方法被實現爲擴展方法來擴展 IHtmlHelper 接口。

類 InputExtensions 定義了 HTML 輔助方法來建立複選框、密碼控件、單選按鈕和文本框控件。 Action 和 RenderAction 輔助方法由 ChildActionExtensions 類定義。顯示的輔助方法由 DisplayExtensions 類定義。 HTML表單的輔助方法由類FormExtensions定義。

如下部分介紹使用HTML Helpers的一些示例。

使用簡單助手

如下代碼段使用HTML助手方法 BeginForm,Label和CheckBox。 BeginForm啓動一個表單元素。還有一個EndForm用於結束表單元素。該示例使用從BeginForm方法返回的MvcForm實現的IDisposable接口(來釋放資源)。釋放 MvcForm 時,會調用EndForm。這樣,BeginForm方法能夠被一個using語句包圍,以便在關閉的大括號中結束表單。方法 DisplayName 直接返回參數中的內容,方法CheckBox是一個type 特性設置爲checkbox的 input 元素(代碼文件MVCSampleApp/Views/HelperMethods/SimpleHelper.cshtml):

@using (Html.BeginForm()) {
  @Html.DisplayName("Check this (or not)")
  @Html.CheckBox("check1")
}

下一個代碼段顯示生成的HTML代碼。 CheckBox方法建立兩個具備相同名稱的輸入元素,一個設置爲 hidden 。這種行爲有一個重要的緣由:若是複選框的值爲false,瀏覽器不會將表單內容的此信息傳遞給服務器。只將所選的複選框的值傳遞到服務器。此HTML特性會自動綁定動做方法的參數,從而產生問題。一個簡單的解決方案是經過CheckBox助手方法執行的。該方法建立相同名稱並設置爲false的隱藏輸入元素。若是未選中該複選框,則隱藏的輸入元素將傳遞到服務器,而且能夠綁定false值。若是選中此複選框,則會向服務器發送兩個具備相同名稱的輸入元素。第一個輸入元素設置爲true;第二個設置爲false。使用自動綁定,僅選擇第一個輸入元素進行綁定:

<form action="/HelperMethods/SimpleHelper" method="post">
  Check this (or not)
  <input id="FileName_check1" name="check1" type="checkbox" value="true" />
  <input name="check1" type="hidden" value="false" />
</form>

使用模型數據

能夠對模型數據使用輔助方法。示例建立一個 Menu 對象。這個類型在本章前面在 Models 目錄中已聲明,並將一個樣例菜單做爲模型傳遞給視圖(代碼文件MVCSampleApp/Controllers/HTMLHelpersController.cs):

public IActionResult HelperWithMenu() => View(GetSampleMenu());
private Menu GetSampleMenu() =>
  new Menu { Id = 1, Text ="Schweinsbraten mit Kn&ouml;del und Sauerkraut", Price = 6.9, Date = new DateTime(2016, 10, 5), Category ="Main" };

視圖的模型定義爲 Menu 類型。 HTML Helper 的DisplayName 從參數返回文本,如上一個示例所示。 Display方法使用一個表達式做爲參數,其中屬性名稱能夠以字符串格式傳遞。這種方式下,屬性嘗試查找具備此名稱的屬性,並訪問屬性訪問器以返回屬性的值(代碼文件MVCSampleApp/Views/HTMLHelpers/HelperWithMenu.cshtml):

@model MVCSampleApp.Models.Menu
@{
    ViewBag.Title ="HelperWithMenu";
}
<h2>Helper with Menu</h2> @Html.DisplayName("Text:") @Html.Display("Text") <br /> @Html.DisplayName("Category:") @Html.Display("Category")

生成的HTML代碼,能夠視爲調用DisplayName和Display方法的輸出:

Text:
Schweinsbraten mit Kn&#246;del und Sauerkraut
<br /> Category: Main 

注意 助手方法還提供強類型變量來訪問模型的成員。有關詳細信息,請參閱「使用強類型助手」部分。

定義HTML屬性

大多數HTML Helper方法都有重載,能夠傳遞任何HTML屬性。例如,如下TextBox方法建立一個類型爲 text 的輸入元素。第一個參數定義名稱,第二個參數定義使用文本框設置的值。 TextBox方法的第三個參數是對象類型,它容許傳遞一個匿名類型,其中每一個屬性都被更改成HTML元素的特性。這裏輸入元素的結果是required 特性設置爲required,maxlength特性設置爲15,class特性設置爲CSSDemo。由於類是一個C#關鍵字,它不能直接設置爲屬性。可是它以@爲前綴以生成CSS樣式的類屬性:

@Html.TextBox("text1","input text here",
  new { required="required", maxlength=15, @class="CSSDemo" });

生成的HTML輸出以下所示:

<input class="Test" id="FileName_text1" maxlength="15" name="text1" 
required="required"
  type="text" value="input text here" />

建立列表

爲了顯示列表,存在相似 DropDownList和ListBox的輔助方法。這些方法建立HTML選擇元素。

在控制器中,首先建立一個包含鍵和值的字典。而後,字典將使用自定義擴展方法 ToSelectListItems 轉換爲 SelectListItem 的列表。 DropDownList 和 ListBox 方法使用 SelectListItem 集合(代碼文件MVCSampleApp/Controllers/HTMLHelpersController.cs):

public IActionResult HelperList()
{
  var cars = new Dictionary<int, string>(); cars.Add(1,"Red Bull Racing"); cars.Add(2,"McLaren"); cars.Add(3,"Mercedes"); cars.Add(4,"Ferrari"); return View(cars.ToSelectListItems(4)); }

自定義擴展方法 ToSelectListItems 在類SelectListItemsExtensions中定義,它擴展了來自cars集合的 IDictionary<int, string>  類型。在實現中,字典中的每一個項目返回一個新的 SelectListItem 對象(代碼文件MVCSampleApp/Extensions/SelectListItemsExtensions.cs):

public static class SelectListItemsExtensions
{
  public static IEnumerable<SelectListItem> ToSelectListItems( this IDictionary<int, string> dict, int selectedId) { return dict.Select(item => new SelectListItem { Selected = item.Key == selectedId, Text = item.Value, Value = item.Key.ToString() }); } }

在視圖中幫助方法 DropDownList 直接訪問從控制器返回的模型(代碼文件MVCSampleApp/Views/HTMLHelpers/HelperList.cshtml):

@{
    ViewBag.Title ="Helper List"; } @model IEnumerable<SelectListItem> <h2>Helper2</h2> @Html.DropDownList("carslist", Model)

生成的HTML建立一個 select 元素,其中包含從 SelectListItem 建立的選項子元素,並定義從控制器返回的所選項:

<select id="FileName_carslist" name="carslist">
  <option value="1">Red Bull Racing</option>
  <option value="2">McLaren</option>
  <option value="3">Mercedes</option>
  <option selected="selected" value="4">Ferrari</option>
</select>

使用強類型助手

HTML Helper方法提供強類型方法來訪問從控制器傳遞的模型。這些方法都以名稱For後綴。例如,可使用TextBoxFor方法代替TextBox方法。

如下示例再次使用控制器返回單個實體(代碼文件MVCSampleApp/Controllers/HTMLHelpersController.cs):

public IActionResult StronglyTypedMenu() => View(GetSampleMenu()); 

視圖使用 Menu 類型做爲模型,所以方法 DisplayNameFor 和 DisplayFor 能夠直接訪問 Menu 屬性。默認狀況下,DisplayNameFor返回屬性的名稱(在這個例子中,它是Text屬性),DisplayFor 返回屬性的值(代碼文件 MVCSampleApp/Views/HTMLHelpers/StronglyTypedMenu.cshtml):

@model MVCSampleApp.Models.Menu
@Html.DisplayNameFor(m => m.Text)
<br /> @Html.DisplayFor(m => m.Text)

一樣,可使用返回一個輸入元素的  Html.TextBoxFor(m => m.Text) 能夠設置模型的Text屬性。此方法還使用添加到 Menu 類型的Text屬性的註釋。 Text屬性添加了 Required 和 MaxStringLength 特性,這就是從TextBoxFor方法返回 data-val-length、data-val-length-max和data-val-required屬性的緣由:

<input data-val="true"
  data-val-length="The field Text must be a string with a maximum length of 
50."
  data-val-length-max="50"
  data-val-required="The Text field is required."
  id="FileName_Text" name="Text"
  type="text"
  value="Schweinsbraten mit Kn&ouml;del und Sauerkraut" />

使用編輯器擴展

EditorExtensions 類中的輔助方法爲類型的全部屬性提供了一個編輯器,而不是爲每一個屬性使用至少一個幫助方法。

使用與以前相同的 Menu 模型,方法 Html.EditorFor(m => m) 用於構建編輯菜單的完整用戶界面(UI)。該方法調用的結果如圖41.9所示。

圖41.9

可使用Html.EditorForModel 而不是使用  Html.EditorFor(m => m) 。方法 EditorForModel 使用視圖的模型,不須要明確指定它。 EditorFor在使用其餘數據源(例如,模型提供的屬性)方面具備更大的靈活性,EditorForModel 須要添加的參數更少。

實現模板

從HTML Helpers 擴展結果的一個好方法是使用模板。模板是經過HTML Helper方法隱式或顯式使用的簡單視圖。模板存儲在特殊文件夾中。顯示模板存儲在視圖文件夾(例如Views/HelperMethods)或共享文件夾(Shared/DisplayTemplates)中的DisplayTemplates文件夾中。全部視圖均可使用共享文件夾,特定視圖文件夾僅由此文件夾中的視圖使用。編輯器模板存儲在文件夾 EditorTemplates 中。

如今看看一個例子。Menu類型的 Date屬性的註釋DataType的值爲 DataType.Date。默認狀況下,指定該特性時 DateTime類型不顯示爲日期和時間,而是隻顯示短日期格式(代碼文件MVCSampleApp/Models/Menu.cs):

public class Menu
{
  public int Id { get; set; } [Required, StringLength(50)] public string Text { get; set; } [Display(Name="Price"), DisplayFormat(DataFormatString="{0:c}")] public double Price { get; set; } [DataType(DataType.Date)] public DateTime Date { get; set; } [StringLength(10)] public string Category { get; set; } }

如今建立日期的模板。使用該模板,將使用長日期字符串格式D返回模型,該日期字符串格式D嵌入在CSS類 markRed 的 div 標記中(代碼文件MVCSampleApp/Views/HTMLHelpers/DisplayTemplates/Date.cshtml):

<div class="markRed">
  @string.Format("{0:D}", Model)
</div> The markRed CSS class is defined within the style sheet to set the color red (code file MVCSampleApp/wwwroot/styles/Site.css): .markRed { color: #f00; }

如今,可使用 DisplayForModel 等 HTML 輔助程序的顯示來使用定義的模板。該模型是Menu類型,所以 DisplayForModel 方法顯示 Menu 類型的全部屬性。對於日期,它找到了模板Date.cshtml,所以該模板用於以CSS樣式顯示長日期格式的日期(代碼文件  MVCSampleApp/Views/HTMLHelpers/Display.cshtml):

@model MVCSampleApp.Models.Menu
@{
    ViewBag.Title ="Display";
}
<h2>@ViewBag.Title</h2>

若是單個類型在同一視圖中具備不一樣的表示,則能夠爲模板文件使用其餘名稱。而後可使用 UIHint 特性指定模板名稱,或者可使用輔助方法的模板參數指定模板。

瞭解標籤助手

ASP.NET MVC 6提供了一種新的技術,能夠用來代替HTML Helpers:Tag Helpers(標籤助手)。有了標籤助手,不用編寫混合HTML的C#代碼,而是使用在服務器上解析的HTML特性和元素。如今許多JavaScript庫都使用本身的屬性(例如Angular)擴展HTML,所以使用服務器端技術來自定義HTML特性很是方便。許多ASP.NET MVC標籤助手有前綴asp-,因此能夠很容易地看到在服務器上解決的特性。這些特性不會發送到客戶端,而是在服務器上解析生成HTML代碼。

激活標籤助手

爲了使用ASP.NET MVC標籤助手,須要經過調用addTagHelper激活標籤。第一個參數定義要使用的類型(a *打開裝配體的全部標籤助手),第二個參數定義了標籤助手的程序集。使用removeTagHelper,標籤助手將會再次被停用。停用標記助手可能很重要,例如,不會與腳本庫發生命名衝突。使用內置的標籤助手與asp-前綴不太可能形成衝突,但與其餘標籤助手能夠具備相同的名稱做爲其餘標籤助手或用於腳本庫的HTML屬性,卻極可能發生衝突。

要使標籤助手可用於全部視圖,將addTagHelper語句添加到共享文件_ViewImports.cshtml(代碼文件 MVCSampleApp/Views/_ViewImports.cshtml)中:

@addTagHelper *, Microsoft.AspNet.Mvc.TagHelpers

使用Anchor Tag Helpers

讓咱們從擴展錨點元素的標籤助手開始。標籤助手的示例控制器是TagHelpersController。 Index動做方法返回一個視圖,用於顯示錨標籤助手(代碼文件MVCSampleApp/Controllers/TagHelpersController.cs):

public class TagHelpersController : Controller
{
  public IActionResult Index() => View(); // etc. } 

錨標記助手定義了asp-controller和asp-action特性。使用這些特性,控制器和動做方法可用於構建錨元素的URL。第二個和第三個例子不須要控制器,由於視圖來自相同的控制器(代碼文件MVCSampleApp/Views/TagHelpers/Index.cshtml):

<a asp-controller="Home" asp-action="Index">Home</a>
<br />
<a asp-action="LabelHelper">Label Tag Helper</a>
<br />
<a asp-action="InputTypeHelper">Input Type Tag Helper</a>

如下代碼段顯示生成的HTML代碼。 asp-controller和asp-action特性爲 a 元素生成 href 特性。第一個示例訪問Home控制器中的Index操做方法,由於它們都是路由定義的默認值,因此在結果中須要一個 "/" 的 href。指定 asp-Action LabelHelper時,href指向 /TagHelpers/LabelHelper時,由於操做方法LabelHelper在當前控制器中:

<a href="/">Home</a>
<br />
<a href="/TagHelpers/LabelHelper">Label Tag Helper</a>
<br />
<a href="/TagHelpers/InputTypeHelper">Input Type Tag Helper</a>

使用標籤助手

在如下代碼片斷中,演示了標籤助手的功能,操做方法LabelHelper將一個Menu對象傳遞給視圖(代碼文件MVCSampleApp/Controllers/TagHelpersController.cs):

private Menu GetSampleMenu() =>
  new Menu
  {
    Id = 1, Text ="Schweinsbraten mit Kn&ouml;del und Sauerkraut", Price = 6.9, Date = new DateTime(2016, 10, 5), Category ="Main" }; }

Menu類有一些應用於影響標籤助手的結果的數據註釋。看看Text屬性的 Display 特性。它將Display特性的Name屬性設置爲「Menu」(代碼文件MVCSampleApp/Models/Menu.cs):

public class Menu
{
  public int Id { get; set; } [Required, StringLength(50)] [Display(Name ="Menu")] public string Text { get; set; } [Display(Name ="Price"), DisplayFormat(DataFormatString ="{0:C}")] public double Price { get; set; } [DataType(DataType.Date)] public DateTime Date { get; set; } [StringLength(10)] public string Category { get; set; } }

視圖使用應用於 label 控件的 asp-for 特性。用於該特性的值是視圖模型的屬性。在Visual Studio 2015中可使用IntelliSense 訪問Text,Price 和 Date 屬性(代碼文件MVCSampleApp/Views/TagHelpers/LabelHelper.cshtml):

@model MVCSampleApp.Models.Menu
@{
  ViewBag.Title ="Label Tag Helper";
}
<h2>@ViewBag.Title</h2>
<label asp-for="Text"></label>
<br/>
<label asp-for="Price"></label>
<br />
<label asp-for="Date"></label>

生成的HTML代碼裏能夠看到 for 特性,該特性引用與屬性名稱相同的元素,內容做爲屬性名稱或Display特性的值。還可使用該特性來本地化值:

<label for="Text">Menu</label>
<br/>
<label for="Price">Price</label>
<br />
<label for="Date">Date</label>

使用輸入標籤輔助程序

HTML 標籤一般與 input 元素相關聯。如下代碼片斷能夠了解使用標籤助手的輸入元素生成的內容:

<label asp-for="Text"></label>
<input asp-for="Text"/>
<br/>
<label asp-for="Price"></label>
<input asp-for="Price" />
<br />
<label asp-for="Date"></label>
<input asp-for="Date" />

檢查生成的HTML代碼的結果顯示,輸入類型標籤輔助程序根據屬性的類型建立type特性,而且它們還應用DateType特性。屬性Price的類型爲double,即結果是數字輸入類型。由於Date屬性在DataType.Date值應用了DataType,因此輸入類型是日期。除此以外,還能夠看到因爲註釋而建立的data-val-length,data-val-length-max和data-val-required特性:

<label for="Text">Menu</label>
<input type="text" data-val="true"
  data-val-length=
    "The field Menu must be a string with a maximum length of 50."
  data-val-length-max="50"
  data-val-required="The Menu field is required."
  id="FileName_Text" name="Text"
  value="Schweinsbraten mit Kn&ouml;del und Sauerkraut" />
<br/>
<label for="Price">Price</label>
<input type="number" data-val="true"
  data-val-required="The Price field is required."
  id="FileName_Price" name="Price" value="6.9" />
<br />
<label for="Date">Date</label>
<input type="date" data-val="true"
  data-val-required="The Date field is required."
  id="FileName_Date" name="Date" value="10/5/2016" />

現代瀏覽器中HTML 5輸入控件還有些特殊的樣式,如日期控件。 Microsoft Edge的輸入日期控制如圖41.10所示。

圖41.10

使用帶驗證的表單

爲了將數據發送到服務器,輸入字段須要由表單相聯繫起來。表單的標記助手使用asp-method 和 asp-controller定義了action特性。對於輸入控件,已經瞭解過驗證信息由這些控件定義。可是驗證錯誤信息須要顯示,所以驗證消息標籤輔助程序使用 asp-validation-for 擴展了span元素(代碼文件MVCSampleApp/Views/TagHelpers/FormHelper.cs):

<form method="post" asp-method="FormHelper">
  <input asp-for="Id" hidden="hidden" />
  <hr />
  <label asp-for="Text"></label>
  <div>
    <input asp-for="Text" />
    <span asp-validation-for="Text"></span>
  </div>
  <br />
  <label asp-for="Price"></label>
  <div>
    <input asp-for="Price" />
    <span asp-validation-for="Price"></span>
  </div>
  <br />
  <label asp-for="Date"></label>
  <div>
    <input asp-for="Date" />
    <span asp-validation-for="Date"></span>
  </div>
  <label asp-for="Category"></label>
  <div>
    <input asp-for="Category" />
    <span asp-validation-for="Category"></span>
  </div>
  <input type="submit" value="Submit" />
</form>

控制器經過檢查 ModelState 來驗證接收的數據是否正確。若是不正確,將再次顯示相同的視圖(代碼文件MVCSampleApp/Controllers/TagHelpersController.cs):

public IActionResult FormHelper() => View(GetSampleMenu());
[HttpPost]
public IActionResult FormHelper(Menu m) { if (!ModelState.IsValid) { return View(m); } return View("ValidationHelperResult", m); }

運行應用程序能夠看到如圖41.11所示的錯誤信息。

圖41.11

建立自定義標籤助手

除了使用預約義的標籤助手外,還能夠建立自定義標籤助手。在本節中構建的示例自定義標記助手擴展HTML表格元素,以便爲列表中的每一個項目顯示一行,每一個屬性顯示一列。

控制器實現 CustomHelper 方法返回Menu對象列表(代碼文件MVCSampleApp/Controllers/TagHelpersController.cs):

public IActionResult CustomHelper() => View(GetSampleMenus());
private IList<Menu> GetSampleMenus() =>
  new List<Menu>() { new Menu { Id = 1, Text ="Schweinsbraten mit Kn&ouml;del und Sauerkraut", Price = 8.5, Date = new DateTime(2016, 10, 5), Category ="Main" }, new Menu { Id = 2, Text ="Erd&auml;pfelgulasch mit Tofu und Geb&auml;ck", Price = 8.5, Date = new DateTime(2016, 10, 6), Category ="Vegetarian" }, new Menu { Id = 3, Text ="Tiroler Bauerngr&ouml;st'l mit Spiegelei und Krautsalat", Price = 8.5, Date = new DateTime(2016, 10, 7), Category ="Vegetarian" } };

如今進入標記助手。自定義實現須要如下命名空間:

using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers; using System.Collections.Generic; using System.Linq; using System.Reflection;

自定義標記助手派生自基類 TagHelper。TargetElement 特性定義標記助手所擴展的HTML元素。該標記助手擴展了 table 元素;所以字符串「table」被傳遞給元素的構造函數。使用 Attributes 屬性,能夠定義分配給標記助手使用的HTML元素的特性列表。標籤助手使用 items 特性。標籤助手能夠經過如下語法使用: <table items=「Model」></table>  ,其中 Model 是能夠迭代的列表。若是要建立一個被多個HTML元素一塊兒使用的標籤助手,只須要應用特性 TargetElement 屢次。要將 items 特性的值自動分配給 Items 屬性,將特性 HtmlAttributeName 分配給該屬性(代碼文件MVCSampleApp/Extensions/TableTagHelper.cs):

[TargetElement("table", Attributes = ItemsAttributeName)]
public class TableTagHelper : TagHelper { private const string ItemsAttributeName ="items"; [HtmlAttributeName(ItemsAttributeName)] public IEnumerable<object> Items { get; set; } // etc. }

標記助手的核心是在方法 Process 中。該方法須要建立從輔助程序返回的HTML代碼。使用 Process 方法的參數接收 TagHelperContext 。該上下文包含應用標記助手的HTML元素的屬性和全部子元素。表格元素裏已經定義行和列,能夠將結果與現有內容合併。但在示例中,這些被忽略了,只是特性被放到結果中。結果須要寫入第二個參數:TagHelperOutput 對象。爲了建立HTML代碼,使用 TagBuilder  類型。 TagBuilder幫助建立有特性的HTML元素,而且處理元素的關閉。要向 TagBuilder 添加特性,請使用方法 MergeAttributes 。該方法須要全部特性名稱及其值的字典。這個字典是經過使用LINQ擴展方法 ToDictionary 建立的。Where方法獲取表元素的全部現有屬性(除了items屬性)。 items 特性用於使用標記助手定義項目,但稍後客戶端不須要它:

public override void Process(TagHelperContext context, TagHelperOutput output)
{
  TagBuilder table = new TagBuilder("table"); table.GenerateId(context.UniqueId,"id"); var attributes = context.AllAttributes .Where(a => a.Name != ItemsAttributeName).ToDictionary(a => a.Name); table.MergeAttributes(attributes); // etc. }

注意 若是須要在Tag Helper實現中調用異步方法,則能夠覆蓋ProcessAsync方法,而不是Process方法。

注意 在第13章「語言集成查詢」中解釋了LINQ。

接下來建立表格的第一行。此行包含一個tr元素做爲table元素的子元素,而且每一個屬性包含td元素。要獲取全部屬性名稱,能夠調用 First 方法來檢索集合的第一個對象。可使用反射訪問此實例的屬性,調用Type對象上的GetProperties方法,並將屬性的名稱寫入到HTML th 元素的內部文本:

  // etc.
  var tr = new TagBuilder("tr");
  var heading = Items.First(); PropertyInfo[] properties = heading.GetType().GetProperties(); foreach (var prop in properties) { var th = new TagBuilder("th"); th.InnerHtml.Append(prop.Name); th.InnerHtml.AppendHtml(th); } table.InnerHtml.AppendHtml(tr); // etc.

注意 反射在第16章中做了解釋。

Process 方法的最後一部分遍歷集合的全部項目,併爲每一個項目建立更多行(tr)。對於每一個屬性,添加一個td元素,並將該屬性的值寫爲內部文本。最後,將建立的 table 元素的內部HTML代碼寫入輸出:

foreach (var item in Items)
{
  tr = new TagBuilder("tr"); foreach (var prop in properties) { var td = new TagBuilder("td"); td.InnerHtml.Append(prop.GetValue(item).ToString()); td.InnerHtml.AppendHtml(td); } table.InnerHtml.AppendHtml(tr); } output.Content.Append(table.InnerHtml);

建立標記助手後,建立視圖變得很是簡單。定義模型後,能夠經過 addTagHelper 傳遞程序集名稱來引用標記助手。使用 item 特性定義HTML表時,標記助手自己將被實例化(代碼文件MVCSampleApp/Views/TagHelpers/CustomHelper.cshtml):

@model IEnumerable<Menu>
@addTagHelper"*, MVCSampleApp"
<table items="Model" class="sample"></table>

運行應用程序時,看到的表應該看起來像 圖41.12。建立標記助手後,很是易於使用。全部 使用CSS定義的格式化仍像HTML表定義的全部特性在生成的HTML輸出中那樣應用便可。

圖41.12

------未完待續

相關文章
相關標籤/搜索