MVC3的新特性介绍
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( 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( 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 来生成的时候,这个元数据将被任何显示或者编辑模板使用,这允许你来解释元数据信息。