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

譯文,我的原創,轉載請註明出處(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(下)jquery

------------------------------------ 正則表達式

本章內容

  • ASP.NET MVC 6的特性
  • 路由
  • 建立控制器
  • 建立視圖
  • 驗證用戶輸入
  • 使用過濾器
  • 使用HTML和標籤助手
  • 建立數據驅動的Web應用程序
  • 實現認證和受權

Wrox.com網站下載本章代碼
wrox.com中關於本章的代碼下載位於 http://www.wrox.com/go/professionalcsharp6 的「下載代碼」選項卡上。本章的代碼主要有如下主要示例:數據庫

  • MVC Sample App
  • Menu Planner

設置 ASP.NET MVC 6 服務 

第40章「ASP.NET Core」展現了ASP.NET MVC的基礎:ASP.NET Core 1.0 第40章展現了中間件以及依賴注入如何與ASP.NET結合使用。本章經過注入ASP.NET MVC服務來使用依賴注入。編程

ASP.NET MVC是基於MVC(模型 - 視圖 - 控制器)模式。如 圖41.1所示,這種標準模式(一個文檔中描述的設計模式:由Gang of Four [Addison-Wesley Professional,1994]的可重複使用的面向對象軟件的元素)定義了一個實現數據實體和數據訪問的模型,向用戶展現信息的視圖,以及利用模型向視圖發送數據的控制器。控制器從瀏覽器接收請求並返回響應。要建立響應,控制器可使用模型提供一些數據,以及一個視圖來定義返回的HTML。
設計模式

 

圖41.1
ASP.NET MVC 的控制器和模型一般使用運行服務器端的C#和.NET代碼建立。視圖是帶有JavaScript的HTML代碼,視圖中有時也會有少許用於訪問服務器端信息的C#代碼。數組

MVC模式中的這種分離的最大優勢是,可使用單元測試輕鬆測試功能。控制器只包含具備參數和返回值的方法,這些參數和返回值能夠經過單元測試輕鬆遍歷。瀏覽器

如今開始爲ASP.NET MVC 6設置服務。ASP.NET Core 1.0的依賴注入是深刻集成的,如第40章中所示。能夠選擇ASP.NET Core 1.0模板「Web應用程序」建立一個ASP.NET MVC 6項目。該模板已經包括ASP.NET MVC 6所需的NuGet包以及幫助組織應用程序的目錄結構。可是,這裏將從空模板開始(相似於第40章),因此你能夠看到建立一個ASP.NET MVC 6項目所須要的東西,不會有項目可能不須要的額外的東西。服務器

建立的第一個項目名爲 MVCSampleApp。要在 Web應用程序MVCSampleApp 使用ASP.NET MVC,須要添加NuGet包 Microsoft.AspNet.Mvc 。該包準備就緒後,經過在ConfigureServices方法中調用擴展方法AddMvc來添加MVC服務(代碼文件MVCSampleApp/Startup.cs):

using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; // etc.
namespace MVCSampleApp { public class Startup { // etc.
    public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // etc.
 } // etc.
    public static void Main(string[] args) { var host = new WebHostBuilder() .UseDefaultConfiguration(args) .UseStartup<Startup>() .Build(); host.Run(); } } }

AddMvc 擴展方法添加和配置幾個 ASP.NET MVC核心服務,如配置功能(IConfigureOptions與MvcOptions和RouteOptions);控制器工廠和控制器觸發器(IControllerFactory,IControllerActivator)、動做方法選擇器,調用者和約束提供者(IActionSelector,IActionInvokerFactory,IActionConstraintProvider)、參數綁定器和模型驗證器(IControllerActionArgumentBinder,IObjectModelValidator)、和過濾器提供程序(IFilterProvider)。
它除了增長核心服務,AddMvc方法還添加ASP.NET MVC服務以支持受權,CORS,數據註釋,視圖,Razor視圖引擎等。

定義路由

第40章解釋了 IapplicationBuilder 的 Map 擴展方法如何定義一個簡單的路由。本章展現了 ASP.NET MVC 路由如何基於映射提供了一種靈活的路由機制,用於將URL映射到控制器和操做方法。

控制器是基於路由選擇的。建立默認路由的一種簡單方法是在啓動類中調用UseMvcWithDefaultRoute方法(代碼文件MVCSampleApp/Startup.cs):

public void Configure(IApplicationBuilder app) { // etc.
 app.UseIISPlatformHandler(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); // etc.
}

注意 擴展方法 UseStaticFiles 已在第40章討論。此方法須要添加 Microsoft.AspNet.StaticFiles  NuGet包。

使用默認路由後,控制器類型的名稱(不帶Controller後綴)和方法名稱組成路由,例如  http://server[:port]/controller/action 。還可使用名爲id的可選參數,例如:  http://server[:port]/controller/action/id  。控制器的默認名稱爲 Home ; action方法的默認名稱爲Index。
如下代碼段顯示了指定相同默認路由的另外一種方法。 UseMvc方法能夠接收類型 Action<IRouteBuilder> 的參數。此IRouteBuilder接口包含映射的路由列表。使用 MapRoute 擴展方法定義路由:

app.UseMvc(routes =< with => routes.MapRoute( name:"default", template:"{controller}/{action}/{id?}", defaults: new {controller ="Home", action ="Index"} ));

此路由定義與默認路由定義相同。template 參數定義URL,id? 定義了該參數是可選的; defaults 參數定義URL的控制器和動做部分的默認值。

讓咱們看看這個URL:

http://localhost:[port]/UseAService/GetSampleStrings

這個URL 中 UseAService 映射到控制器的名稱,由於 Controller 後綴是自動添加的,類型名稱爲UseAServiceController,GetSampleStrings 是操做,它表示 UseAServiceController 類中的一個方法。

添加路由

添加或更改路由有幾個緣由。例如,能夠修改路由去使用連接操做,將Home定義爲默認控制器,向連接添加實體或使用多個參數。

能夠定義路由讓用戶使用連接(例如 http://<server>/About )在Home控制器中尋址「關於」操做方法,而不傳遞控制器名稱,如如下代碼段所示。請注意,控制器名稱不在URL中。 controller關鍵字對於路由是必需的,但能夠爲其提供默認值:

app.UseMvc(routes => routes.MapRoute(
    name:"default",
    template:"{action}/{id?}",
    defaults: new {controller ="Home", action ="Index"}
  ));

更改路由的另外一個方案如如下代碼段所示。在此代碼段中,向路徑中添加變量 language 。該變量設置爲URL中位於服務器名稱以後並放置在控制器以前,例如   http://server/en/Home/About   。可使用它來指定語言:

app.UseMvc(routes => routes.MapRoute(
    name:"default",
    template:"{controller}/{action}/{id?}",
    defaults: new {controller ="Home", action ="Index"}
  ).MapRoute(
    name:"language",
    template:"{language}/{controller}/{action}/{id?}",
    defaults: new {controller ="Home", action ="Index"}
);

若是一個路由匹配而且找到了控制器和動做方法,則採起路由,不然選擇下一條路由,直到找到一條路由匹配。

使用路由約束

映射路由時,能夠指定約束。這種狀況下,不符合約束定義的URL 是不能被解析的。如下約束定義  language 參數使用正則表達式(en)|(de)來指定只能是 en或de。例如 只有  http://<server>/en/Home/About  或  http://<server>/de/Home/About  的URL有效:

app.UseMvc(routes => routes.MapRoute(
  name:"language",
  template:"{language}/{controller}/{action}/{id?}",
  defaults: new {controller ="Home", action ="Index"},
  constraints: new {language = @"(en)|(de)"}
));

若是連接只應啓用數字(例如,要訪問具備產品編號的產品),正則表達式  \d+ 匹配任意數量的數字,但必須至少匹配一個數字:

app.UseMvc(routes => routes.MapRoute(
  name:"products",
  template:"{controller}/{action}/{productId?}",
  defaults: new {controller ="Home", action ="Index"},
constraints: new {productId = @"\d+"}
)); 

如今已經看到了路由如何指定使用的控制器和控制器的動做。下一節「建立控制器」介紹了控制器的詳細信息。

建立控制器

控制器對來自用戶的請求作出反應併發送響應。本節所講述的內容,能夠不須要視圖。

使用ASP.NET MVC時有一些約定要優於配置。控制器也會看到一些約定。能夠在目錄 Controllers 中找到控制器,而且控制器類的名稱必須以名稱 Controller 爲後綴。

在建立第一個控制器以前,先建立Controllers目錄。而後,能夠經過在解決方案資源管理器中選擇此目錄來建立控制器,從上下文菜單中選擇 Add -> New Item,並選擇MVC控制器類項目模板。 HomeController是爲指定的路由建立的。

生成的代碼包含從基類Controller派生的HomeController類。此類還包含與 Index 操做對應的 Index 方法。當請求由路由定義的操做時,將調用控制器中的方法(代碼文件MVCSampleApp/Controllers/HomeController.cs):

  public class HomeController : Controller
  {
    public IActionResult Index() => View();
  }

瞭解操做方法

控制器包含操做(譯者注: action,也譯做 動做)方法。如下代碼片斷有一個簡單的動做 Hello 方法(代碼文件MVCSampleApp/Controllers/HomeController.cs):

public string Hello() =>"Hello, ASP.NET MVC 6"; 

可使用連接 http://localhost:5000/Home/Hello 在Home控制器中調用Hello操做。固然,端口號取決於設置,可使用項目設置中的網絡屬性進行配置。從瀏覽器打開此連接時,控制器只返回字符串「Hello, ASP.NET MVC 6」,沒有HTML - 只是一個字符串。瀏覽器能夠顯示字符串。

動做能夠返回任何內容,例如,圖像,視頻,XML或JSON數據的字節,固然,也能夠是HTML。視圖對於返回HTML有很大的幫助。

使用參數

可使用參數聲明動做方法,以下面的代碼片斷(代碼文件MVCSampleApp/Controllers/HomeController.cs):

public string Greeting(string name) =>
  HtmlEncoder.Default.Encode($"Hello, {name}");

注意 HtmlEncoder須要NuGet包System.Text.Encodings.Web。

有了這個聲明,能夠調用Greeting操做方法去請求如下的URL,在URL中傳遞帶有name參數的值:http://localhost:18770/Home/Greeting?name=Stephanie

要使用更容易記住的連接,可使用路由信息來指定參數。 Greeting2操做方法指定名爲id的參數。

public string Greeting2(string id) =>
  HtmlEncoder.Default.Encode($"Hello, {id}");

這與默認路由{controller}/{action}/{id?}匹配,其中id被指定爲可選參數。如今使用下面的連接,id參數包含字符串Matthias:http://localhost:5000/Home/Greeting2/Matthias

還可使用任意數量的參數聲明操做方法。例如,可使用兩個參數將「Add」操做方法添加到Home控制器,以下所示:

public int Add(int x, int y) => x + y;

 

可使用URL  http://localhost:18770/Home/Add?x=4&y=5 調用此操做以填充x和y參數。

使用多個參數,還能夠定義一個路由以使用不一樣的連接傳遞值。如下代碼片斷顯示了在路由表中定義的一個附加路由,以指定填充變量x和y的多個參數(代碼文件MVCSampleApp/Startup.cs):

app.UseMvc(routes =< routes.MapRoute(
    name:"default",
    template:"{controller}/{action}/{id?}",
    defaults: new {controller ="Home", action ="Index"}
  ).MapRoute(
    name:"multipleparameters",
    template:"{controller}/{action}/{x}/{y}",
    defaults: new {controller ="Home", action ="Add"},
    constraints: new {x = @"\d", y = @"\d"}
  ));

如今,可使用此URL調用與以前相同的操做:http://localhost:18770/Home/Add/7/2

注意 本章後面的「將數據傳遞到視圖」部分中,將看到可使用自定義類型的參數,以及客戶端的數據如何映射到屬性。

返回數據

目前爲止,只從控制器返回字符串值。一般,返回實現接口 IActionResult 的對象。

如下是 ResultController 類的幾個示例。第一個代碼片斷使用 ContentResult 類返回簡單的文本內容。可使用來自基類Controller 的方法來返回 ActionResults,而不是建立 ContentResult 類的實例並返回該實例。在如下示例中,方法 Content 用於返回文本內容。 Content方法容許指定內容,MIME類型和編碼(代碼文件MVCSampleApp/Controllers/ResultController.cs):

public IActionResult ContentDemo() =>
  Content("Hello World","text/plain");

要返回JSON格式的數據,可使用Json方法。如下示例代碼建立一個Menu對象:

public IActionResult JsonDemo()
{
  var m = new Menu
  {
    Id = 3,
    Text ="Grilled sausage with sauerkraut and potatoes",
    Price = 12.90,
    Date = new DateTime(2016, 3, 31),
    Category ="Main"
  };
  return Json(m);
}

Menu類在Models目錄中定義,它定義了一個簡單的僅有一些屬性的POCO 類(代碼文件MVCSampleApp/Models/Menu.cs):

public class Menu
{
  public int Id {get; set;}
  public string Text {get; set;}
  public double Price {get; set;}
  public DateTime Date {get; set;}
  public string Category {get; set;}
}

客戶端在響應正文中看到此JSON數據。 JSON數據能夠輕鬆地做爲JavaScript對象使用:

{"Id":3,"Text":"Grilled sausage with sauerkraut and potatoes",
"Price":12.9,"Date":"2016-03-31T00:00:00","Category":"Main"}

使用Controller類的Redirect方法,客戶端接收HTTP重定向請求。在接收到重定向請求後,瀏覽器請求它接收的連接。 Redirect方法返回一個RedirectResult(代碼文件MVCSampleApp/Controllers/ResultController.cs):

public IActionResult RedirectDemo() => 
Redirect("http://www.cninnovation.com");

還能夠經過指定重定向到另外一個控制器和操做來爲客戶端生成重定向請求。 RedirectToRoute 返回一個 RedirectToRouteResult,用於指定路由名稱,控制器,操做和參數。這將構建一個使用HTTP重定向請求返回到客戶端的連接:

public IActionResult RedirectRouteDemo() =>
  RedirectToRoute(new {controller ="Home", action="Hello"});

Controller基類的File方法定義了返回不一樣類型的不一樣重載。此方法能夠返回FileContentResult,FileStreamResult和VirtualFileResult。不一樣的返回類型取決於所使用的參數 - 例如,VirtualFileResult的字符串,FileStreamResult的Stream和FileContentResult的字節數組。

下一個代碼段返回一個圖像。建立 Images 文件夾並添加一個JPG文件。要使下一個代碼段工做,請在 wwwroot 目錄中建立一個Images文件夾,並添加文件Matthias.jpg。示例代碼返回一個VirtualFileResult,它在第一個參數指定文件名,第二個參數指定MIME類型爲image/jpeg的contentType參數:

public IActionResult FileDemo() =>
  File("~/images/Matthias.jpg","image/jpeg");

下一節顯示如何返回不一樣的ViewResult。

使用控制器基類和POCO控制器

到目前爲止,全部建立的控制器都是從基類 Controller 派生的。 ASP.NET MVC 6還支持不從這個基類派生的控制器 - 被稱爲POCO(普通舊CLR對象)控制器。這樣,能夠用自定義的基類來定義控制器類型層次結構。

從Controller基類中獲得什麼?這個基類能使控制器直接訪問基類的屬性。下表描述了這些屬性及其功能。 

屬性 說明
ActionContext 此屬性包裝其餘的屬性。這裏能夠獲取有關操做描述符的信息,其中包含操做的名稱,控制器,過濾器和方法信息; HttpContext能夠從Context屬性直接訪問;能夠從ModelState屬性直接訪問的模型的狀態以及能夠從RouteData屬性直接訪問路由信息​​。
Context 此屬性返回HttpContext。經過它能夠訪問 ServiceProvider 以訪問註冊了依賴注入(ApplicationServices屬性)的服務,身份驗證和用戶信息,請求和響應信息 均可以從 Request 和 Response屬性直接訪問的,以及Web套接字(若是它們正在使用)。
BindingContext 此屬性能夠訪問將接收到的數據綁定到操做方法的參數的綁定器。將綁定請求信息綁定到自定義類型將在本章後面的「從客戶端提交數據」一節中討論。
MetadataProvider 使用綁定器綁定參數。綁定器可使用與模型相關聯的元數據。MetadataProvider屬性能夠訪問有關配置爲處理元數據信息的提供程序的信息。
ModelState ModelState屬性能夠知道模型綁定是成功仍是有錯誤。若是出現錯誤,能夠閱讀有關致使錯誤的屬性的信息。
Request

此屬性能夠訪問有關 HTTP請求的全部信息:標題和正文信息,查詢字符串,表單數據和Cookie。 頭信息包含一個User-Agent字符串,提供有關瀏覽器和客戶端平臺的信息。

Response  此屬性保存返回給客戶端的信息。能夠發送cookie,更改標題信息,並直接寫入正文。 在本章前面的章節「啓動」中,已經看到了如何使用Response屬性將一個簡單的字符串返回給客戶端。
Resolver Resolver屬性返回ServiceProvider,能夠在其中訪問註冊了依賴注入的服務。
RouteData RouteData屬性提供有關在啓動代碼中註冊的完整路由表的信息。
ViewBag 可使用這些屬性向視圖發送信息。這將在稍後的「將數據傳遞到視圖」部分中解釋。
ViewData  
TempData 此屬性寫入在多個請求之間共享的用戶狀態(而寫入ViewBag和ViewData的數據能夠寫入以在單個請求中共享視圖和控制器之間的信息)。默認狀況下,TempData將信息寫入會話狀態。
User User屬性返回有關已驗證用戶的信息,包括身份和聲明。

POCO控制器沒有Controller基類,但訪問這些信息仍然很重要。如下代碼片斷定義了從對象基類派生的POCO控制器(固然您可使用自定義類型做爲基類)。要使用POCO類建立一個ActionContext,能夠建立此類型的屬性。 POCO Controller類使用ActionContext 做爲此屬性的名稱,這相似於Controller類的方式。可是,有一個屬性不會自動設置。須要應用ActionContext屬性。使用此屬性注入實際的ActionContext。 Context屬性直接從ActionContext訪問HttpContext屬性。 Context屬性用於從UserAgentInfo 操做方法訪問並返回請求中的User-Agent頭信息(代碼文件MVCSampleApp/Controllers/POCOController.cs):

public class POCOController
{
  public string Index() =>
    "this is a POCO controller";
  [ActionContext]
  public ActionContext ActionContext {get; set;}
  public HttpContext Context => ActionContext.HttpContext;
  public ModelStateDictionary ModelState => ActionContext.ModelState;
  public string UserAgentInfo()
  {
    if (Context.Request.Headers.ContainsKey("User-Agent"))
    {
     return Context.Request.Headers["User-Agent"];
    }
    return"No user-agent information";
  }
} 

建立視圖

返回到客戶端的HTML代碼最好使用視圖來指定。本節中的示例將建立 ViewsDemoController 。視圖都在Views文件夾中定義。 ViewsDemo 控制器的視圖須要一個 ViewsDemo子目錄。這是視圖的約定(代碼文件MVCSampleApp/Controllers/ViewsDemoController.cs):

public ActionResult Index() => View();

注意 另外一個搜索視圖的地方是  Shared  目錄。能夠將多個控制器(以及多個視圖使用的特殊部分視圖)要使用的視圖放入 Shared 目錄。

在Views目錄中建立 ViewsDemo 目錄以後,可使用Add -> New Item並選擇 「MVC View Page」 項目模板來建立視圖。由於action方法具備名稱Index,因此視圖文件被命名爲Index.cshtml。

操做方法Index使用沒有參數的View方法,所以視圖引擎將在ViewsDemo目錄中搜索與操做名稱相同名稱的視圖文件。在控制器中使用的View方法有容許傳遞不一樣視圖名稱的重載。在這種狀況下,視圖引擎將查找傳遞給View方法的名稱的視圖。

視圖包含混合了一些服務器端代碼的HTML代碼,以下面的代碼段(代碼文件 VCSampleApp/Views/ViewsDemo/Index.cshtml)所示:

@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Index</title>
</head>
<body>
  <div>
  </div>
</body>
</html>

服務器端代碼使用 @符號 寫入,它會啓動 Razor 語法,本章後面將討論。在介紹Razor語法的詳細信息以前,下一節將介紹如何將數據從控制器傳遞到視圖。

傳遞數據到視圖

控制器和視圖在同一進程中運行。視圖直接從控制器內建立。這使得將數據從控制器傳遞到視圖變得容易。爲了傳遞數據,可使用ViewDataDictionary。此字典將鍵存儲爲字符串,並啓用對象值。能夠將 ViewDataDictionary 與 Controller 類的 ViewData 屬性一塊兒使用 - 例如,能夠將字符串傳遞到使用鍵值 MyData 的字典: ViewData[「MyData」] =「Hello」  。一個更簡單的語法是使用ViewBag屬性。 ViewBag 是一個動態類型,能夠分配任何屬性名稱來傳遞數據到視圖(代碼文件MVCSampleApp/Controllers/SubmitDataController.cs):

public IActionResult PassingData()
{
  ViewBag.MyData ="Hello from the controller";
  return View();
} 

注意 使用動態類型的優勢是沒有從視圖到控制器的直接依賴。動態類型在第16章「反射,元數據和動態編程」中有詳細說明。

在視圖中,能夠用相似於控制器的方式訪問從控制器傳遞來的數據。視圖的基類(WebViewPage)定義了一個ViewBag屬性(代碼文件MVCSampleApp/Views/ViewsDemo/PassingData.cshtml):

<div>
  <div>@ViewBag.MyData</div>
</div>

瞭解 Razor 語法

如上所述的視圖,視圖包含HTML和服務器端代碼。ASP.NET MVC 中可使用Razor語法在視圖中編寫C#代碼。 Razor使用@字符 做爲轉換字符。從@後面就是C#代碼。

使用Razor須要區分返回值的語句和不返回值的方法。能夠直接使用返回的值。例如,ViewBag.MyData 返回一個字符串。該字符串直接放在HTML div標籤之間,以下所示:

<div>@ViewBag.MyData</div>

當調用返回 void 的方法或指定一些不返回值的其餘語句時,須要一個Razor代碼塊。如下代碼塊定義了一個字符串變量:

@{
  string name ="Angela";
}

如今可使用簡單語法的變量,只需使用轉換字符@訪問變量:

<div>@name</div>

使用Razor語法,引擎會在找到HTML元素時自動檢測C#代碼的結束。在某些狀況下可能沒法自動檢測到C#代碼的結束。可使用括號來解決此問題,如如下示例所示,以標記變量,而後正常文本繼續:

<div>@(name), Stephanie</div>

另外一種啓動Razor代碼塊的方法是使用foreach語句:

@foreach(var item in list)
{
  <li>The item name is @item.</li>
}

注意 一般 Razor 自動檢測文本內容,例如,Razor檢測到打開的尖括號或帶有變量的括號。也有一些狀況下不起做用。這裏能夠明確使用  @:  來定義文本的開始。

建立強類型視圖

將數據傳遞給視圖,咱們已經看到了操做中的ViewBag。有另外一種方法來傳遞數據到視圖 - 傳遞模型到視圖。使用模型容許您建立強類型視圖。

如今 ViewsDemoController 使用動做方法PassingAModel擴展。如下示例建立一個新的菜單項列表,並將此列表做爲模型傳遞給Controller基類的View方法(代碼文件MVCSampleApp/Controllers/ViewsDemoController.cs):

public IActionResult PassingAModel()
{
  var menus = new List<Menu>
  {
    new Menu
    {
      Id=1,
      Text="Schweinsbraten mit Kn&ouml;del und Sauerkraut",
      Price=6.9,
      Category="Main"
    },
    new Menu
    {
      Id=2,
      Text="Erd&auml;pfelgulasch mit Tofu und Geb&auml;ck",
      Price=6.9,
      Category="Vegetarian"
    },
    new Menu
    {
      Id=3,
      Text="Tiroler Bauerngr&ouml;st'l mit Spiegelei und Krautsalat",
      Price=6.9,
      Category="Main"
    }
  };
  return View(menus);
}

當模型信息從動做方法傳遞到視圖時,能夠建立強類型視圖。強類型視圖使用 model 關鍵字聲明。傳遞給視圖的模型類型必須與模型指令的聲明相匹配。在下面的代碼片斷中,強類型視圖聲明類型  IEnumerable<Menu>  ,它與模型類型匹配。由於 Menu 類在命名空間 MVCSampleApp.Models 中定義,因此這個命名空間使用 using 關鍵字打開。

從.cshtml文件建立的視圖的基類派生自基類 RazorPage 。有了一個模型,基類是 RazorPage<TModel>  類型;如下代碼片斷基類是 RazorPage<IEnumerable<Menu>> 。此通用參數依次定義類型爲 IEnumerable<Menu> 的Model屬性。代碼片斷基類的Model屬性用於經過@foreach 遍歷 Menu 項,並顯示每一個菜單的列表項(代碼文件MVCSampleApp/ViewsDemo/PassingAModel.cshtml):

@using MVCSampleApp.Models
@model IEnumerable<Menu>
@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>PassingAModel</title>
</head>
<body>
  <div>
    <ul>
      @foreach (var item in Model)
      {
        <li>@item.Text</li>
      }
    </ul>
  </div>
</body>
</html>

能夠將任何對象做爲模型傳遞 - 任何視圖須要的均可以。例如,當編輯單個Menu對象時,可使用類型爲Menu的模型。顯示或編輯列表時,可使用  IEnumerable<Menu> 。

運行應用程序顯示定義視圖時,會在瀏覽器中看到菜單列表,如圖41.2所示。

 

圖41.2

定義佈局

一般,許多網頁應用程序共享相同的內容,例如版權信息、徽標和主導航結構。目前爲止,全部視圖都包含完整的HTML內容,可是有一個更簡單的方法來管理共享的內容。這是佈局頁面發揮做用的地方。

要定義佈局,要設置視圖的 Layout 屬性。要定義全部視圖的默認屬性,能夠建立視圖起始頁。將此文件放入Views文件夾,可使用項目模板 MVC View Start Page 來建立它。建立文件_ViewStart.cshtml(代碼文件MVCSampleApp/Views/_ViewStart.cshtml):

@{
  Layout ="_Layout";
}

對於不須要佈局的全部視圖,能夠將Layout屬性設置爲null:

@{
  Layout = null;
}

使用默認佈局頁

可使用項目模板 MVC View Layout Page 建立默認佈局頁面。在共享文件夾中建立此頁面,以便它可用於來自不一樣控制器的全部視圖。項目模板 MVC View Layout Page 建立如下代碼:

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>@ViewBag.Title</title>
</head>
<body>
  <div>
    @RenderBody()
  </div>
</body>
</html>

佈局頁面包含使用此佈局頁面的全部頁面通用的HTML內容(例如,頁眉,頁腳和導航)。您已經瞭解了視圖和控制器如何用ViewBag通訊。佈局頁面可使用相同的機制。在內容頁面中定義 ViewBag.Title 的值,在佈局頁面,上述代碼段中它顯示在HTML標題元素中。基類 RazorPage 的 RenderBody 方法呈現內容頁面的內容,從而定義內容應該放置的位置。

如下代碼段中,生成的佈局頁面將更新爲引用樣式表,並向每一個頁面添加頁眉,頁腳和導航部分。environment,asp-controller和asp-action 是建立HTML元素的標籤助手。標籤助手將在本章後面的「助手」部分中討論(代碼文件MVCSampleApp/Views/Shared/_Layout.cshtml):

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <environment names="Development">
    <link rel="stylesheet" href="~/css/site.css" />
  </environment>
  <environment names="Staging,Production">
    <link rel="stylesheet" href="~/css/site.min.css"
      asp-append-version="true" />
  </environment>
  <title>@ViewBag.Title - My ASP.NET Application</title>
</head>
<body>
  <div class="container">
    <header>
      <h1>ASP.NET MVC Sample App</h1>
    </header>
    <nav>
      <ul>
        <li><a asp-controller="ViewsDemo" asp-action="LayoutSample">
          Layout Sample</a></li>
        <li><a asp-controller="ViewsDemo" asp-action="LayoutUsingSections">
          Layout Using Sections</a></li>
      </ul>
    </nav>
    <div>
      @RenderBody()
    </div>
    <hr />
    <footer>
      <p>
        <div>Sample Code for Professional C#</div>
        &copy; @DateTime.Now.Year - My ASP.NET Application
      </p>
    </footer>
  </div>
</body>
</html>

爲LayoutSample 操做 建立視圖(代碼文件MVCSampleApp/Views/ViewsDemo/LayoutSample.cshtml)。該視圖不設置Layout屬性,所以使用默認佈局。如下代碼段設置ViewBag.Title,它在佈局中的HTML標題元素中使用:

@{
  ViewBag.Title ="Layout Sample";
}
<h2>LayoutSample</h2>
<p>
  This content is merged with the layout page
</p>

運行應用程序時,將合併佈局和視圖中的內容,如圖41.3所示。

圖41.3

使用部件

渲染正文和使用ViewBag不是在佈局和視圖之間交換數據僅有的方法。部件區域能夠定義命名內容應放置在視圖中的位置。如下代碼片斷使用名爲PageNavigation的部件。默認狀況下,這些部件是必需的,若是未定義部件,則加載視圖將失敗。當required參數設置爲false時,該部件變爲可選(代碼文件MVCSampleApp/Views/Shared/_Layout.cshtml):

<!-- etc. -->
<div>
  @RenderSection("PageNavigation", required: false)
</div>
<div>
  @RenderBody()
</div>
<!-- etc. -->

在視圖頁面中,section關鍵字定義了該部件。放置該部件的位置徹底獨立於其餘內容。視圖不定義頁面中的位置,這是由佈局定義的(代碼文件MVCSampleApp/Views/ViewsDemo/LayoutUsingSections.cshtml):

@{
    ViewBag.Title ="Layout Using Sections";
}
<h2>Layout Using Sections</h2>
Main content here
@section PageNavigation
{
  <div>Navigation defined from the view</div>
  <ul>
    <li>Nav1</li>
    <li>Nav2</li>
  </ul>
}

運行應用程序時,視圖和佈局中的內容將根據佈局定義的位置合併,如圖41.4所示。

圖41.4

注意 部件不只用於在HTML頁面的正文中放置某些內容,它們也可用於視圖在頭部中放置某些東西 - 例如,來自頁面的元數據。

部分視圖定義內容

佈局爲Web應用程序中的多個頁面提供了一個總體定義,也可使用部分視圖來定義視圖中的內容。部分視圖沒有佈局。

除此以外,部分視圖相似於正常視圖。部分視圖使用與普通視圖相同的基類,它們都有一個模型。

如下是部分視圖的示例。這裏首先須要一個模型,該模型包含由 EventsAndMenusContext 類定義的獨立集合,事件和菜單的屬性(代碼文件MVCSampleApp/Models/EventsAndMenusContext.cs):

public class EventsAndMenusContext
{
  private IEnumerable<Event> events = null;
  public IEnumerable<Event> Events
  {
    get
    {
      return events ?? (events = new List<Event>()
      {
        new Event
        {
          Id=1,
          Text="Formula 1 G.P. Australia, Melbourne",
          Day=new DateTime(2016, 4, 3)
        },
        new Event
        {
          Id=2,
          Text="Formula 1 G.P. China, Shanghai",
          Day = new DateTime(2016, 4, 10)
        },
        new Event
        {
          Id=3,
          Text="Formula 1 G.P. Bahrain, Sakhir",
          Day = new DateTime(2016, 4, 24)
        },
        new Event
        {
          Id=4,
          Text="Formula 1 G.P. Russia, Socchi",
          Day = new DateTime(2016, 5, 1)
        }
      });
    }
  }
  private List<Menu> menus = null;
  public IEnumerable<Menu> Menus
  {
    get
    {
      return menus ?? (menus = new List<Menu>()
      {
        new Menu
        {
          Id=1,
          Text="Baby Back Barbecue Ribs",
          Price=16.9,
          Category="Main"
        },
        new Menu
        {
          Id=2,
          Text="Chicken and Brown Rice Piaf",
          Price=12.9,
          Category="Main"
        },
        new Menu
        {
          Id=3,
          Text="Chicken Miso Soup with Shiitake Mushrooms",
          Price=6.9,
          Category="Soup"
        }
      });
    }
  }
}

上下文類已註冊到依賴注入啓動代碼,以使用控制器構造函數注入類型(代碼文件MVCSampleApp/Startup.cs):

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<EventsAndMenusContext>();
}

如今該模型將在如下代碼中用於部分視圖示例,從服務器端代碼加載的部分視圖以及使用客戶端上的JavaScript代碼請求的視圖。

使用來自服務器端代碼的部分視圖

在 ViewsDemoController 類中,構造函數被修改成注入 EventsAndMenusContext 類型(代碼文件MVCSampleApp/Controllers/ViewsDemoController.cs):

public class ViewsDemoController : Controller
{
  private EventsAndMenusContext _context;
  public ViewsDemoController(EventsAndMenusContext context)
  {
    _context = context;
  }
  // etc.

UseAPartialView1動做方法將 EventsAndMenus的 一個實例傳遞給視圖(代碼文件MVCSampleApp/Controllers/ViewsDemoController.cs):

public IActionResult UseAPartialView1() => View(_context);

視圖頁面被定義爲使用 EventsAndMenusContext 類型的模型。可使用HTML Helper 的方法 Html.PartialAsync 顯示部分視圖,該方法返回  Task<HtmlString>。後面的示例代碼中,該字符串使用 Razor 語法做爲 div 元素的內容寫入。 PartialAsync方法的第一個參數接受部分視圖的名稱,第二個參數能夠傳遞模型。若是沒有傳遞模型,則部分視圖能夠訪問與視圖相同的模型。這裏視圖使用類型 EventsAndMenusContext 的模型,部分視圖只是使用類型  IEnumerable<Event>的一部分(代碼文件MVCSampleApp/Views/ViewsDemo/UseAPartialView1.cshtml):

@model MVCSampleApp.Models.EventsAndMenusContext
@{
  ViewBag.Title ="Use a Partial View";
  ViewBag.EventsTitle ="Live Events";
}
<h2>Use a Partial View</h2>
<div>this is the main view</div>
<div>
  @await Html.PartialAsync("ShowEvents", Model.Events)
</div>

若是不使用異步方法,可使用同步變量Html.Partial。這是一個返回 HtmlString 的擴展方法。

在視圖中渲染局部視圖的另外一種方法是使用 HTML Helper 方法Html.RenderPartialAsync,它被定義爲返回 Task 。此方法直接將部分視圖內容寫入響應流。這樣一能夠在Razor代碼塊中使用 RenderPartialAsync 。

用建立正常視圖的方式去建立局部視圖便可,能夠訪問模型以及經過使用ViewBag屬性訪問的字典。部分視圖接收字典的副本以接收可使用的相同字典數據(代碼文件MVCSampleApp/Views/ViewsDemo/ShowEvents.cshtml):

@using MVCSampleApp.Models
@model IEnumerable<Event>
<h2>
  @ViewBag.EventsTitle
</h2>
<table>
  @foreach (var item in Model)
  {
    <tr>
      <td>@item.Day.ToShortDateString()</td>
      <td>@item.Text</td>
    </tr>
  }
</table>

運行應用程序時,將渲染視圖,局部視圖和佈局,如圖41.5所示。

圖41.5

從控制器返回部分視圖

目前爲止部分視圖都是直接加載,而im有與控制器交互,但也可使用控制器返回部分視圖。

如下代碼片斷中,在 ViewsDemoController 類中定義了兩個操做方法。第一個動做方法 UsePartialView2 返回正常視圖,第二個動做方法 ShowEvents 使用基類方法PartialView返回部分視圖。局部視圖 ShowEvents 以前已經建立和使用,並在此處使用。 PartialView方法將包含事件列表的模型將傳遞到部分視圖(代碼文件 MVCSampleApp/Controllers/ViewDemoController.cs):

public ActionResult UseAPartialView2() => View();
public ActionResult ShowEvents()
{
  ViewBag.EventsTitle ="Live Events";
  return PartialView(_context.Events);
}

從控制器提供部分視圖時,能夠直接從客戶端代碼調用部分視圖。如下代碼片斷使用jQuery:事件處理程序連接到按鈕的 click 事件。在事件處理程序內,使用jQuery load 函數向服務器發出GET請求,以請求 /ViewsDemo/ShowEvents 。該請求返回部分視圖,部分視圖的結果放在名爲 events 的 div 元素中(代碼文件MVCSampleApp/Views/ViewsDemo/UseAPartialView2.cshtml):

@model MVCSampleApp.Models.EventsAndMenusContext
@{
  ViewBag.Title ="Use a Partial View";
}
<script src="~/lib/jquery/dist/jquery.js"></script>
<script>
  $(function () {
    $("#getEvents").click(function () {
      $("#events").load("/ViewsDemo/ShowEvents");
    });
  });
</script>
<h2>Use a Partial View</h2>
<div>this is the main view</div>
<button id="FileName_getEvents">Get Events</button>
<div id="FileName_events">
</div>

使用視圖組件

ASP.NET MVC 6 提供了一個新的替代部分視圖的方式:視圖組件。視圖組件與局部視圖很是相似,主要區別是視圖組件與控制器無關。這使得它們易於與多個控制器一塊兒使用。視圖組件很是有用的示例是菜單的動態導航,登陸面板或博客中的側邊欄內容。這些場景獨立於單個控制器是很是有用的。

與控制器和視圖同樣,視圖組件有兩個部分。視圖組件中控制器功能由從ViewComponent(或具備ViewComponent屬性的POCO類)派生的類接管。用戶界面相似於視圖定義,可是調用視圖組件的方法不一樣。

如下代碼片斷定義了從基類 ViewComponent 派生的視圖組件。該類使用先前在 Startup 類中註冊的 EventsAndMenusContext 類型,以即可用於依賴注入。這與構造函數注入的控制器相似。 InvokeAsync 方法定義爲從顯示視圖組件的視圖中調用。該方法能夠有任意數量和類型的參數,由於由 IViewComponentHelper 接口定義的方法使用 params關鍵字定義了靈活數量的參數。不要使用異步方法實現,而是同步實現此方法返回 IViewComponentResult  而不是  Task<IViewComponentResult>  。然而,一般異步方式是最好的使用,例如,用於訪問數據庫。視圖組件須要存儲在 ViewComponents 目錄中。這個目錄自己能夠放置在項目中的任何地方(代碼文件MVCSampleApp/ViewComponents/EventListViewComponent.cs):

public class EventListViewComponent : ViewComponent
{
  private readonly EventsAndMenusContext _context;
  public EventListViewComponent(EventsAndMenusContext context)
  {
    _context = context;
  }
  public Task<IViewComponentResult> InvokeAsync(DateTime from, DateTime to)
  {
    return Task.FromResult<IViewComponentResult>(
      View(EventsByDateRange(from, to)));
  }
  private IEnumerable<Event> EventsByDateRange(DateTime from, DateTime to)
  {
    return _context.Events.Where(e => e.Day >= from && e.Day <= to);
  }
}

視圖組件的用戶界面在如下代碼段中定義。可使用項目模板 MVC View Page 建立視圖組件的視圖,它使用相同的 Razor 語法。具體來講,它必須放在 Components/[viewcomponent] 文件夾中,例如 Components/EventList 。要使視圖組件可用於全部控件,須要在視圖的 Shared 文件夾中建立Components文件夾。僅使用來自一個特定控制器的視圖組件時,能夠將其放入視圖控制器文件夾中。這個視圖不同凡響的地方是它須要被命名爲default.cshtml。也能夠建立其餘視圖名稱,須要使用從InvokeAsync方法返回的View方法的參數指定這些視圖(代碼文件MVCSampleApp/Views/Shared/Components/EventList/default.cshtml):

@using MVCSampleApp.Models;
@model IEnumerable<Event>
<h3>Formula 1 Calendar</h3>
<ul>
  @foreach (var ev in Model)
  {
    <li><div>@ev.Day.ToString("D")</div><div>@ev.Text</div></li>
  }
</ul>

視圖組件完成後,能夠經過調用InvokeAsync方法顯示它。Component是視圖動態建立的屬性,返回實現IViewComponentHelper的對象。 IviewComponentHelper容許您調用同步或異步方法,如Invoke,InvokeAsync,RenderInvoke和RenderInvokeAsync。固然,只能調用由視圖組件實現的這些方法,而且要使用相應的參數(代碼文件MVCSampleApp/Views/ViewsDemo/UseViewComponent.cshtml):

@{
  ViewBag.Title ="View Components Sample";
}
<h2>@ViewBag.Title</h2>
<p>
  @await Component.InvokeAsync("EventList", new DateTime(2016, 4, 10),
    new DateTime(2016, 4, 24))
</p>

運行應用程序,您能夠看到呈現的視圖組件,如圖41.6所示。

圖41.6

在視圖中使用依賴注入

若是直接從視圖中須要服務,可使用 inject 關鍵字注入它:

@using MVCSampleApp.Services
@inject ISampleService sampleService
<p>
    @string.Join("*", sampleService.GetSampleStrings())
</p>

要這樣作時,最好使用AddScoped方法註冊服務。如上所述,以這種方式註冊服務意味着它只對一個HTTP請求實例化一次。使用AddScoped,在控制器和視圖中注入相同的服務,它只爲請求實例化一次。

導入多個視圖的命名空間

全部之前的視圖示例都使用了using關鍵字打開全部須要的命名空間。其實可使用Visual Studio項目模板 MVC View Imports Page 來建立文件(_ViewImports.cshml)定義全部使用聲明,而不是在每一個視圖打開命名空間(代碼文件MVCSampleApp/Views/_ViewImports.cshtml):

@using MVCSampleApp.Models
@using MVCSampleApp.Services

有了這個文件,不須要在全部視圖中都添加 using 關鍵字。 

 

---------------------(未完待續)

相關文章
相關標籤/搜索