MVC3 新特性
摘要
MVC經過其1.0和2.0版本的發展,現在已經到了3.0的領軍時代,隨着技術的不斷改進,MVC也越來越成熟。使開發也變得簡潔人性化藝術化。
前言
ASP.NET MVC3 在 ASP.NET MVC 1 和 2 的基礎上,增加了大量的特性,使得代碼更加簡化,並且可以深度擴展。這篇文章提供包含在此次發布中的許多新特性的說明,分為以下部分:
- Razor 視圖引擎
- 支持多視圖引擎
- Controller 改進
- JavaScript 和 Ajax
- Model 驗證的改進
- 依賴注入 Dependency Injection 的改進
- 其他新特性
Razor 視圖引擎
ASP.NET MVC3 帶來了一種新的名為 Razor 的視圖引擎,提供了下列優點:
- Razor 的語法簡單且清晰,只需要最小化的輸入
- Razor 容易學習,語法類似於 C# 和 VB
- Visual Studio 對於 Razor 提供了智能提示和語法着色
- Razor 視圖不需要允許程序或者啟動 Web 服務器就可以進行測試
Razor 現在提供了一些新的特征:
- @model 用來指定傳到視圖的 Model 類型
- @* * 注釋語法
- 對於整個站點可以一次性設定默認項目,例如布局。
- Html.Raw 方法提供了沒有進行 HTML 編碼的輸出
- 支持在多個視圖之間共享代碼 ( _viewstart.cshtml 或者 _viewstart.vbhtml )->View的基類
Razor 還包含新的 HTML Helper,例如:
- Chart. 生成圖表
- WebGrid, 生成數據表格,支持完整的分頁和排序
- Crypto,使用 Hash 算法來創建 Hash 和加鹽的口令
- WebImage, 生成圖片(http://www.cnblogs.com/facingwaller/archive/2010/12/07/how_to_use_webimage_in_razor.html)
- WebMail, 發送電子郵件(http://www.cnblogs.com/facingwaller/archive/2010/12/07/how_to_send_mail_in_razor.html)
一、示例代碼:
1.MVC3與MVC2代碼比較:
View:
MVC3 |
MVC2 |
Index.cshtml |
Index.aspx |
@{ ViewBag.Title = "Home Page"; }
<h2>@ViewBag.Message</h2>
<p> To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>. </p>
|
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Home Page </asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2><%: ViewData["Message"] %></h2> <p> To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>. </p> </asp:Content>
|
Foreach循環 |
|
<h2>Products</h2> <ul> @foreach (var p in products) { <li>@p.ProductionName</li> } </ul>
|
<h2>Products</h2> <ul> <%foreach (dynamic p in Model) {%> <li><%:p.ProductName%></li> <%} %> </ul>
|
Controller:
MVC3 |
MVC2 |
public ActionResult Index() { ViewBag.Message = "Welcome to ASP.NET MVC!"; //ViewModel return View(); }
|
public ActionResult Index() { ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View(); }
|
關於 Razor 更多的資料,可以參考下面的資源:
- Scott Guthrie's blog post introducing Razor
- Scott Guthrie's blog post introducing the @model keyword
- Scott Guthrie's blog post introducing Razor layouts
- Razor API Quick Reference
- MVC 3 Release Notes
@model指示符提供了一個更加好用的功能,使我們在View中引用強類型數據模型的方式更加干凈整潔。
我們來看一個超級簡單的腳本例子,我們要實現一個或多個產品的URL,用來列出數據庫中產品分類:
以下是一個簡單的ProductsController,實現了產品列表的URL。可以遍歷數據庫中的產品類別,並傳遞到View文件中生成一個適當的HTML文件,響應瀏覽器的請求。
如果我們已經在ASP.NET MVC 3預覽第一版中使用過Razor,如果我們想要使Index.cshtml視圖文件中綁定到System.Web.Mvc.WebViewPage<TModel>這個類,則需要在頭部加上@inherits聲明。我們是想指明View文件和傳遞過來的強類型關聯起來。
This works (and is still supported with ASP.NET MVC 3) - but is a little verbose.
這樣是可行的(並且仍被ASP.NET MVC 3所支持)--但這有點冗長了。
我們已經在ASP.NET MVC 3測試版中加入了@model指示符,這樣可以更干凈簡潔地指明你在View文件中想引用一個強類型模型的類。現在,你只需要在文件首部寫上"@model 強類型模型類",也就不用再寫@inherits聲明或指定View所基於其上模型的類:
上面的語法和之前的語法在概念上並沒有什么兩樣(除了少得多的代碼)。這樣更有利於編寫和閱讀代碼。
參考:http://www.cnblogs.com/xiaxiazl/archive/2012/04/14/2447081.html
3、Razor基本語法
Razor關鍵字 |
意義 |
示例 |
@ |
代表開始一個Razor代碼塊,Razor會自動匹配代碼中的花括號 |
@if(p.Active){<li>@p.Name</li>} |
@{code} |
標識一個Razor多行代碼塊,相當於<% CODE %> |
1.賦值定義操作 @{ ViewBag.Title = "Home Page"; } 2.使用邏輯處理 @{ if (xx) { //do something } else { //do anything } } 3.在@{... }內部使用html標記 @{ <p>text</P> <div>div1</div> } 4. 在@{...}內部輸出文本 @{ @:This is some text @:This is text too @:@i 也可輸出變量 } 5. 利用<text />進行多行輸出 @{ <text> tomorrow is good some girl is nice </text> }
6. 在@{...}內部使用注釋 @{ //單行注釋 var i = 10; //defg }
@* 多行注釋 *@ @* 多行注釋 多行注釋 *@
@{ @* 多行注釋 多行注釋 *@ var i = 10; @* asdfasf *@ }
<!-- 同時也可以使用C#默認的/* ... */ -->
@{ /* 多行注釋 */ } 若在@{ ... }內部使用<!-- -->注釋,則會輸出到頁面之中,如果在<!-- -->內部使用@變量,則會被處理 <!-- time now: @DateTime.Now.ToString() -->
|
@model |
指定ViewModel |
@model MVCProject.UI.Models.ProductModel |
@section SectionName{} |
定義局部布局,類似於Master的ContentPlace,可以在布局模板中使用@RenderSection呈現 |
@section SubMenu{ 更多參考:http://www.cnblogs.com/dragon_mail/archive/2011/06/30/2094884.html |
@: |
指定當前按Content處理,但是可以有內嵌的Razor代碼 |
@{ @:This is some text @:This is text too @:@i 也可輸出變量 } |
<text>...</text> |
功能同@:,區別是還可以指定多行Content |
<text> tomorrow is good some girl is nice </text> |
@*....*@ |
注釋 |
見@{} |
@(expression) |
用於輔助Razor識別表達式 |
@for (int i = 10; i < 11; i++) { @:@i } |
Razor語法之類型轉換
AsInt(), IsInt()
AsBool(),IsBool()
AsFloat(),IsFloat()
AsDecimal(),IsDecimal()
AsDateTime(),IsDateTime()
ToString()
例子:
@{
var i = “10”;
}
<p> i = @i.AsInt() </p> <!-- 輸出 i = 10 -->
Razor語法之使用循環
<!--方式1-->
@for (int i = 10; i < 11; i++)
{
@:@i
}
<!--方式2-->
@{
for (int i = 10; i < 11; i++)
{
//do something
}
}
<!--while同理-->
Razor 還包含新的 HTML Helper
ASP.NET3為我們帶來了很多新特性,其中ChartHelper也是相當給力。比如我們要生成一張這樣的圖表:
我們需要在controller中這樣寫(這里不考慮通過ViewModel在頁面上通過Chart生成圖表的方式):
public ActionResult Chart2() {
var chart = new Chart(width: 500, height: 300, theme: ChartTheme.Blue)
.AddSeries(
chartType: "bar",
legend: "Rainfall",
xValue: new[] { "南京", "武漢", "上海", "蘇州", "沈陽" },
yValues: new[] { "95", "80", "70", "72", "92" })
.AddTitle("分公司年末預測率匯總")
.SetXAxis(title: "分公司")
.SetYAxis(title: "預測率(%)");
chart.Write();
return null;
}
然后頁面上用一個img標簽載入action生成的圖片
<img src="/Home/Chart2" alt="測試圖表2" />
但是那個return null是不是有點過分的ugly了?為此我們可以簡單編寫一個ChartResult:
public class ChartResult : ActionResult {
private readonly Chart _chart;
private readonly string _format;
public ChartResult(Chart chart, string format = "png") {
if (chart == null)
throw new ArgumentNullException("chart");
_chart = chart;
_format = format;
if (string.IsNullOrEmpty(_format))
_format = "png";
}
public Chart Chart {
get { return _chart; }
}
public string Format {
get { return _format; }
}
public override void ExecuteResult(ControllerContext context) {
_chart.Write(_format);
}
}
(注意這里用到了可選參數,如果您還在用.NET3.5,請自行調整)
最終controller中的代碼:
public ActionResult Chart2() {
var chart = new Chart(width: 500, height: 300, theme: ChartTheme.Blue)
.AddSeries(
chartType: "bar",
legend: "Rainfall",
xValue: new[] { "南京", "武漢", "上海", "蘇州", "沈陽" },
yValues: new[] { "95", "80", "70", "72", "92" })
.AddTitle("分公司年末預測率匯總")
.SetXAxis(title: "分公司")
.SetYAxis(title: "預測率(%)");
return new ChartResult(chart);
}
其他圖:
- WebGrid, 生成數據表格,支持完整的分頁和排序
aspx:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<List<WebGridAspx.Models.Products>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
產品列表
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script type="text/javascript">
function deleteRecord(a, b) {
alert("刪除:"+b);
}
</script>
<h2>產品列表</h2>
<div id="grid">
<% var grid = new WebGrid(source: Model, defaultSort: "ProductName", rowsPerPage: 5); %>
<%=grid.GetHtml(
tableStyle: "grid",
headerStyle: "head",
alternatingRowStyle: "alt",
columns: grid.Columns(
grid.Column(format: (item) => Html.ActionLink("Edit", "Edit", new { id = item.ProductID })),
grid.Column(format: (item) => Html.ActionLink("Delete", "Delete", null, new { onclick = string.Format("deleteRecord('Employee', '{0}')", item.ProductID), @class = "Delete", href = "JavaScript:void(0)" })),
grid.Column("ProductName","產品名稱"),
grid.Column("QuantityPerUnit","每單位數量"),
grid.Column("UnitPrice","單價"),
grid.Column("UnitsInStock", "庫存單位"),
grid.Column("UnitsOnOrder","訂單單位"),
grid.Column("ReorderLevel","重新排序級別"),
grid.Column("Discontinued","已停產")
)
)
%>
</div>
</asp:Content>
Razor:
代碼
@model List<WebGridRazor.Models.Products>
@{
View.Title = "產品列表";
}
<p>
<h2>產品列表</h2>
<div id="grid">
@{
var grid = new WebGrid(source: Model,
defaultSort: "ProductName",
rowsPerPage: 3);
}
@grid.GetHtml(
tableStyle: "grid",
headerStyle: "head",
alternatingRowStyle: "alt",
columns: grid.Columns(
grid.Column(format: (item) => Html.ActionLink("Edit", "Edit", new { id = item.ProductID })),
grid.Column(format: (item) => Html.ActionLink("Delete", "Delete", null, new { onclick = string.Format("deleteRecord('Product', '{0}')", item.ProductID), @class = "Delete", href = "JavaScript:void(0)" })),
grid.Column("ProductName","產品名稱"),
grid.Column("QuantityPerUnit","每單位數量"),
grid.Column("UnitPrice","單價"),
grid.Column("UnitsInStock", "庫存單位"),
grid.Column("UnitsOnOrder","訂單單位"),
grid.Column("ReorderLevel","重新排序級別"),
grid.Column("Discontinued","已停產")
)
)
</div>
</p>
支持多視圖引擎
在 ASP.NET MVC3 中,增加視圖的對話框中允許你選擇你希望的視圖引擎,在新建項目對話框中,你可以指定項目默認的視圖引擎,可以選擇 WebForm,Razor,或者開源的視圖引擎,例如:Spark, NHaml, 或者 NDjango.
選擇視圖引擎:
控制器的改進
全局的 Action 過濾器
有的時候你希望能夠在在一個 Action 方法執行之前或者執行之后執行一些處理邏輯,在 ASP.NET MVC2 中,提供了 Action 過濾器,允許對特定控制器的 Action 方法進行處理,實際上,有時候你希望對所有的 Action 都進行類似的處理,MVC3 允許你將過濾器加入到 GlobalFilters 集合中來創建全局的過濾器,
先寫好Filter:
再將寫好的Filter注冊到全局Global.asax中:
最后直接運用在Controller中:
附:一些常用於Action中的系統Filter:
AcceptVerbs
規定頁面的訪問形式,如
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Example(){
return View();
}
ActionName
規定Action的名稱。
應用場景:如果不想用方法名做為Action名,或Action名為關鍵字的話,如
[ActionName("class")]
public ActionResult Example(){
return View();
}
NonAction
當前方法僅是普通方法不解析為Action
OutputCache
為Action添加緩存
[OutputCache(Duration = 60, VaryByParam = "*")]
public ActionResult Example()
{
return View();
}
ValidateInput
該Action可以接受Html等危險代碼(ASP.NET MVC在aspx中設置<%@ Page 的屬性無法完成等同任務。)
[ValidateInput(false)]
public ActionResult Example()
{
return View();
}
ValidateAntiForgeryTokenAttribute
用於驗證服務器篡改。
[ValidateAntiForgeryToken]
public ActionResult Example()
{
return View();
}
詳細的介紹,參考下列資源:
- Scott Guthrie's blog on the MVC 3 Preview
- Filtering in ASP.NET MVC
- http://www.cnblogs.com/chsword/archive/2009/03/12/zd_mvc6.html
新的 ViewBag 屬性
MVC2 中的控制器支持 ViewData 屬性,允許通過后綁定的字典將數據傳送給視圖模板,在 MVC3 中,你可以通過 ViewBag 來更加簡單的完成。例如,對於 ViewData["Message"] = "text",你可以通過 ViewBag.Message = "text" 來完成。你不需要通過類來定義任何強類型的屬性,因為這是動態屬性,在內部,ViewBag 屬性以名-值對的形式保存在 ViewData 字典中。注意,在許多預發布版本中,這個屬性被稱為 ViewModel。
新的 ActionResult 類型
下面的 ActionResult 類型在 MVC3 中是新增的或者被擴展的。
- . 向客戶端返回 404 HTTP 狀態. HttpNotFoundResult
- . 基於一個布爾型的參數,返回一個臨時的重定向 (HTTP 302 status code) 或者持久的重定向 (HTTP 301 status code), 結合這個改進,, 提供了三個方法來支持持久的重定向: , , 和 . 這些方法返回一個 屬性為真的 對象實例。RedirectResultControllerRedirectPermanentRedirectToRoutePermanentRedirectToActionPermanentPermanentRedirectResult
- . 返回用戶指定的 HTTP 狀態碼。 HttpStatusCodeResult
JavaScript 和 Ajax 改進
默認情況下,在 MVC3 中,Ajax 和驗證使用不引人注目的 unobtrusive 的 JavaScript 方式。unobtrusive 不會在 HTML 中插入行內的 JavaScript ,這使得 HTML 更加精簡和更少干擾,也使得更加容易被替換和定制 JavaScript 庫,在 MVC3 中,驗證助手默認使用 jQuery.Validate 插件完成,如果你希望使用 MVC2 的行為,你可以在 web.config 中通過配置來關閉 unobtrusive ,
或者用代碼手動開啟/關閉:HtmlHelper.UnobtrusiveJavaScriptEnabled = true/false;
更多的信息參考下列資源:
- Basic introduction to unobtrusive JavaScript on the Wikipedia site
- Brad Wilson's Unobtrusive JavaScript Post
- Brad Wilson's Unobtrusive JavaScript Validation Post
- (tutorial on the ASP.NET site) Creating a MVC 3 Application with Razor and Unobtrusive JavaScript
- MVC 3 Release Notes
默認啟用了客戶端驗證
在早先版本的 MVC 中,你需要在視圖中顯式調用 Html.EnableClientValidation 方法來啟用客戶端驗證。在 MVC3 中,已經不再需要了,因為默認就會啟用客戶端驗證。可以在 web.config 中關閉。
為了使得客戶端驗證工作,你仍然需要在網站中加入對 jQuery 和 jQuery.Validation 庫的引用,你可以在自己的網站中提供,或者使用 Microsoft 或者 Google 的 CDN 服務器。
遠程驗證
ASP.NET 3 通過一個新的標簽 RemoteAttribute 對 jQuery Validation 插件的遠程驗證提供支持。這允許客戶端的驗證庫自動調用一個你定義在服務器上的自定義的方法來完成只能在服務器上完成的驗證邏輯。
在下面的例子中,Remote 標簽指定了通過一個定義在 UsersController 中名為 UserNameAvailable 的方法來驗證用戶名字段。
public class User
{
[Remote("UserNameAvailable", "Users")]
public string UserName { get; set; }
}
下面的代碼定義在控制器中
public class UsersController
{
public bool UserNameAvailable(string username)
{
if(MyRepository.UserNameExists(username))
{
return "false";
}
return "true";
}
}
關於 Remote 屬性的更多資源,參考 How to: Implement Remote Validation in ASP.NET MVC
JSON 綁定支持
ASP.NET MVC3 包含內置的 JSON 綁定支持,允許 Action 方法接收 JSON 編碼的數據並且模型化為 Action 的參數。這個能力經常被用於客戶端的模板和數據綁定中。客戶端模板允許你通過客戶端的模板來格式化和顯示一個或者多個數據,MVC3 允許你簡單的連接客戶端模板和服務器端的 Action 方法,通過 JSON 來發送和接收數據,
示例:
ASP.NET MVC 3 中內置了對 JSON 的綁定支持,使得接收從客戶端傳遞過來的 JSON 格式的數據變得非常簡單。本篇還是以 Android 博客項目中的留言小功能來簡單的說明一下具體的使用方法。先看看 Razor 視圖引擎下的 HTML代碼,這塊主要用來顯示留言的數據列表:
<div>
<script id="userTemplate" type="text/html">
<div><a href="${Website}">${UserName}</a> Says: ${Content}</div>
</script>
<div id="Contact"></div>
<input type="button" id="create" value="Create" />
</div>
當我們點擊 Create 按鈕時提交留言(這里為了簡單,留言內容固定了),執行 jQuery Ajax 傳遞 JSON 格式數據,如下:
$("#create").click(function () {
var contact = {
UserName: "I love jQuery",
Website: "http://www.google.com",
Content: "Nice to meet you."
};
$.ajax({
url: "/Contact",
type: "POST",
data: contact,
dataType: "json",
success: function (req) {
$("#userTemplate").render(req).appendTo("#Contact");
}
});
});
如果你是純 ASP.NET 開發者,沒有接觸過 jQuery ,那么我要強烈的建議你趕快學習 jQuery ,jQuery 在 ASP.NET MVC 3 中起着非常重要的作用,它會越來越流行。現在,你可以參考下jQuery學習大總結(五)jQuery Ajax。這樣將留言內容以JSON 結構通過 POST 方式向后台提交,ASP.NET MVC 3 Controller 中接收代碼如下:
[HttpPost]
public ActionResult Index(Contact contact)
{
if (ModelState.IsValid)
{
android.Contact.AddObject(contact);
android.SaveChanges();
}
var contacts = from c in android.Contact
where c.IsValid == 1
select c;
return Json(contacts);
}
更多的信息參考:
Scott Guthrie's MVC 3 Preview blog post.
http://www.jquery001.com/pass-json-data-in-asp.net-mvc3.html
Model 驗證的改進
DataAnnotations 元數據標簽
ASP.NET MVC3 支持 DataAnnotations 元數據標簽,例如:DisplayAttribute。
ValidationAttribute 類
在 .NET Framework4 中被改進 的ValidationAttribute 類支持新的 IsValid 重載,提供關於當前驗證上下文的更多信息,例如什么對象被驗證了。這允許你基於 Model 的其他屬性來驗證當前值,例如,新的 CompareAttribute 就允許你比較 Model 的兩個屬性的值,在下面的例子中,ComparePassword 屬性必須匹配 Password 字段來同通過驗證。
public class User
{
[Required]
public string Password { get; set; }
[Required, Compare("Password")]
public string ComparePassword { get; set; }
}
驗證接口
IValidatableObject 接口允許執行 Model 水平的驗證,並且允許你提供整個模型狀態的驗證錯誤信息,或者基於 Model 的兩個屬性。當 Model 綁定的時候,MVC3 從 IValidatableObject 接收錯誤信息,在視圖中使用內建的 HTML 助手時,將會自動標識或者高亮受影響的字段。
IClientValidatable 接口允許 ASP.NET MVC 在運行時發現支持的客戶端驗證器,這個接口被用來支持集成不同的驗證框架。
更加關於驗證接口的內容,參考 Scott Guthrie's MVC 3 Preview blog post 中 Model Validation Improvements 一節。
依賴注入Dependency Injection 的改進
ASP.NET MVC3 提供了更好的 DI 和 IoC 支持,在下面的地方支持 DI:
- 控制器 (registering and injecting controller factories, injecting controllers).
- 視圖 (registering and injecting view engines, injecting dependencies into view pages).
- Action 過濾器 (locating and injecting filters).
- Model 綁定器 (registering and injecting).
- Model 驗證提供器 (registering and injecting).
- Model 元數據提供器 (registering and injecting).
- Value 提供器 (registering and injecting).
MVC3 支持 Common Service Locator 庫和任何支持這個庫的 IServiceLocator 接口的 DI 容器。也支持新的容易集成到 DI 框架的 IDependencyResolver 接口。
依賴注入:英文是Dependency Injection。有時候也稱為反轉控制(Ioc)吧。不管名詞怎么講,它的大致意思是,讓我們的應用程序所依賴的一些外部服務,可以根據需要動態注入,而不是預先在應用程序中明確地約束。這種思想,在當前的軟件開發領域,為了保證架構的靈活性,應該還是很有意義的。
在MVC這個框架中,為依賴注入的設計提供了先天的支持。結合一些我們熟知的DI組件,例如NInject,我們可以較為容易地實現上述提到的功能。
場景介紹
我們的應用程序,需要支持各種不同的數據源,而且我們希望日后可以很容易地切換,不會因為數據源的變化而導致對Contoller或者Model,或者View做修改。
本文完整源代碼,請通過這里下載 MvcApplicationDISample.rar
演練步驟
第一步:准備一個MVC項目(選擇空白模板)
第二步:准備一個業務實體類型
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplicationDISample.Models
{
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
第三步:准備一個數據訪問的接口定義
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MvcApplicationDISample.Models;
namespace MvcApplicationDISample.Services
{
public interface IDataService
{
Employee[] GetEmployee();
}
}
第四步:創建一個HomeController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplicationDISample.Services;
using MvcApplicationDISample.Models;
namespace MvcApplicationDISample.Controllers
{
public class HomeController : Controller
{
IDataService DataService;
public HomeController(IDataService service)
{
DataService = service;
}
//
// GET: /Home/
public ActionResult Index()
{
var data = DataService.GetEmployee();
return View(data);
}
}
}
注意,這里需要為HomeController添加一個特殊的構造函數,傳入IDataService這個接口。通常,所有的DI組件都是通過這樣的方式注入的。
在設計HomeController的時候,我們不需要關心到底日后會用具體的哪種DataService,我們只是要求要傳入一個IDataService的具體實現就可以了,這就是DI的本質了。
到這里為止,我們該做的准備工作基本就緒了。下面來看看如何結合DI組件來實現我們的需求
第五步:引入NInject組件
這是我比較喜歡的一個DI組件。它還針對MVC3專門有一個擴展
添加這個組件之后,除了自動添加了很多引用之外,還有一個特殊的文件App_Start\NinjectMVC3.cs
[assembly: WebActivator.PreApplicationStartMethod(typeof(MvcApplicationDISample.App_Start.NinjectMVC3), "Start")]
[assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(MvcApplicationDISample.App_Start.NinjectMVC3), "Stop")]
namespace MvcApplicationDISample.App_Start
{
using System.Reflection;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
using Ninject;
using Ninject.Web.Mvc;
public static class NinjectMVC3
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestModule));
DynamicModuleUtility.RegisterModule(typeof(HttpApplicationInitializationModule));
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
bootstrapper.ShutDown();
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
RegisterServices(kernel);
return kernel;
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
}
}
}
這個類型很有意思,WebActivator.PreApplicationStartMethod這個方法其實是注冊了一個在MVC程序啟動之前運行的方法。這些代碼大家應該能看懂,它在CreateKernel中,添加一個新的Kernel(用來做注入的容器)。
第六步:創建一個IDataService的具體實現
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MvcApplicationDISample.Models;
namespace MvcApplicationDISample.Services
{
public class SampleDataService:IDataService
{
#region IDataService Members
public Employee[] GetEmployee()
{
return new[]{
new Employee(){ID=1,FirstName="ares",LastName="chen"}};
}
#endregion
}
}
作為舉例,我們這里用了一個硬編碼的方式實現了該服務。
第七步:實現注入
回到App_Start\NinjectMVC3.cs這個文件,修改RegisterServices方法如下
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<Services.IDataService>().To<Services.SampleDataService>();
}
第八步:測試Controller的功能
我們可以看到,數據已經展現出來了。這說明,HomeController中的Index方法,確實調用了我們后期插入的這個SampleDataService。而通過下圖,則可以更加清楚看到這一點
到這里為止,我們就結合Ninject組件實現了一個簡單的依賴注入的實例。Ninject 針對MVC 3有這么一個特殊的文件,可以極大地方便我們的編程。但即便沒有這個文件,我們也可以通過另外一些方法來實現需求。
下面介紹兩種比較傳統的,通過擴展MVC組件實現的方式
第一種:實現自定義ControllerFactory
我們都知道,Controller其實都是由ControllerFactory來生成的,那么,為了給所有新創建從Controller都自動注入我們的服務,那么就可以從ControllerFactory這個地方動動腦筋了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Ninject;
using MvcApplicationDISample.Services;
namespace MvcApplicationDISample.Extensions
{
public class InjectControllerFactory:DefaultControllerFactory
{
private IKernel kernel;
public InjectControllerFactory()
{
kernel = new StandardKernel();
kernel.Bind<IDataService>().To<SampleDataService>();
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
return (IController)kernel.Get(controllerType);
}
}
}
要使用這個自定義的 ControllerFactory,我們需要修改Global.ascx文件中的Application_Start方法,添加下面的粗體部分代碼
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new Extensions.InjectControllerFactory());
}
這樣做好之后,我們可以測試HomeController中的Index這個Action,我們發現它還是能正常工作。
第二種:實現自定義的DependencyResolver
顧名思義,這就是MVC框架里面專門來處理所謂的依賴項的處理器。可以說這是MVC專門為DI准備的一個后門。下面是我寫好的一個例子
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Ninject;
using MvcApplicationDISample.Services;
namespace MvcApplicationDISample.Extensions
{
public class InjectDependencyResolver:IDependencyResolver
{
private IKernel kernel;
public InjectDependencyResolver()
{
kernel = new StandardKernel();
kernel.Bind<IDataService>().To<SampleDataService>();
}
#region IDependencyResolver Members
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
#endregion
}
}
那么,如何使用這個自定義的處理器呢?
很簡單,我們仍然是修改Global.asax文件中的Application_Start方法
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
//ControllerBuilder.Current.SetControllerFactory(new Extensions.InjectControllerFactory());
DependencyResolver.SetResolver(new Extensions.InjectDependencyResolver());
}
請注意,之前那個設置ControllerFactory的代碼,我們可以注釋掉了
這個解決方案的最終效果和之前是一樣的。
更多關於 DI 的信息,參考:
- Brad Wilson's series of blog posts on Service Location
- MVC 3 Release Notes
- http://www.csharpwin.com/dotnetspace/13049r3161.shtml
其他新特性
NuGet 集成
ASP.NET MVC3 自動安裝和啟用 NuGet ,NUGet 是免費開源的一個包管理器,使得在你的項目中容易發現,安裝,和使用 .NET 庫。它可以和所有的 Visual Studio 項目類型一起工作,包括 ASP.NET WebForm 和 MVC。
NuGet 允許開發者維護開源項目,例如,像 Moq 項目,NHibernate 等等,可以注冊它們到一個在線的網站中。
更多信息參考:
NuGet documentation on the CodePlex site.
http://www.cnblogs.com/lzrabbit/archive/2012/05/01/2477873.html
部分頁的輸出緩存
ASP.NET MVC 從版本1 開始支持整頁緩存,MVC3 還提供了部分頁緩存。這可以允許你容易地緩存輸出的一個區域或者片斷,更多地內容參考 Scott Guthrie's blog post on the MVC 3 release candidate 中 Partial Page Output Caching 段落,還有 MVC 3 Release Notes 中 Child Action Output Caching 段落。
在請求驗證中的粒度控制
ASP.NET MVC 內建了請求驗證機制來自動幫助處理類似跨站攻擊和 HTML 注入等等。實際上,有時你希望能夠顯式關閉請求的驗證,例如你希望允許用戶提交 HTML 內容,例如在內容管理系統中,現在你可以通過增加 AllowHtml 標簽到 Model 或者視圖的 Model 來支持在綁定的時候基於一個屬性關閉請求驗證。更多地資料參考:
- 中 一節. Scott Guthrie's blog post on the MVC 3 release candidateUnobtrusive JavaScript and Validation
- MVC 3 Release Notes
可擴展的新建項目對話框
在 MVC3 中,你可以增加項目模板,視圖引擎,單元測試項目框架到新建項目對話框中。
腳手架的改進
MVC3 中的腳手架對於主鍵提供了更好的支持,例如,腳手架的模板不會將主鍵加入的編輯表單中了。
默認情況下,創建和編輯的腳手架現在使用 Html.EditorFor 助手來替代 Html.TextBoxFor 助手,這個改進在增加視圖對話框生成一個視圖的時候,支持模型中的元數據標簽。
對於 Html.LabelFor 和 Html.LabelForModel 的新重載
對於 LabelFor 和 LabelForModel 增加了新的方法重載,允許指定或者重寫 Label 文本。
無 Session 的控制器支持
MVC3 中可以指定控制器是否使用 Session 狀態,進而,Session 是否是讀寫還是只讀。
新的 AdditionalMetadataAttribute 類
可以通過 AdditionalMetadataAttribute 標簽對 Model 的一個屬性訪問 ModelMetadata.AdditionalValues 字典,例如,如果模型的某個屬性僅僅支持管理員顯示,你可以如下設置:
public class ProductViewModel
{
[AdditionalMetadata("AdminOnly", true)]
public string RefundCode {get; set;}
}
當使用產品的 Model 來生成的時候,這個元數據將被任何顯示或者編輯模板使用,這允許你來解釋元數據信息。
Word下載: