ASP.NET MVC 4 最佳实践宝典

ASP.NET MVC最佳实践

本文档提供了一套旨在帮助创建最佳优化ASP.NET MVC应用程序的 开发人员编码准则。 当然,由您来选择这些优化准则。

模型的建议

Model是定义业务领域相关的对象,应该包含业务逻辑(对象如何动作和关联),验证逻辑(验证对象的有效值),数据逻辑(数据对象如何持久化),和会话逻辑(跟踪用户状态)。

创建独立的Model项目,在ASP.NET MVC 项目中引用Model程序集。

尽量将频繁复用的业务逻辑放置在Model中。

如将所有业务逻辑放置在Model项目中,可根据实际业务数据来生成View和Controller。有如下好处:

     1.减少重复的业务逻辑。

     2.在View中减少业务逻辑,View易于理解。

     3.业务逻辑的测试仅仅和Model有关。

例如,下面需要显示用户的用户名 – 先显示Last Name,在View中代码如下:

<% if (String.Compare((string)TempData["displayLastNameFirst"], "on") == 0) { %> Welcome, <%= Model.lastName%>, <%= Model.firstName%> <% } else { %> Welcome, <%= Model.firstName%> <%= Model.lastName%> <% } %>

然而你需要在每一个地方重复这一逻辑。如将这一业务逻辑放置在Model中,可在Model中添加一个属性封装这一逻辑。

public string combinedName { get { return (displayLastNameFirst ? lastName + " " + firstName : firstName + " " + lastName); } private set { ; } }

 

这样,可大大简化视图代码:

<% Welcome, <%= Model.combinedName %> %>

将所有验证逻辑放置在Model

所有输入验证应该在Model层,包括Client-side 验证。

可使用ModelState 添加验证检查,代码如下所示:

if (String.IsNullOrEmpty(userName))

{

ModelState.AddModelError("username", Resources.SignUp.UserNameError);

}

不过,更好的办法是使用 System.ComponentModel.DataAnnotations,在Model类的属性上添加attribute,如下所示:

public class User

{

[Required(ErrorMessageResourceName = "nameRequired", ErrorMessageResourceType = typeof(Resources.User))]

public String userName { get; set; }

...

}

为数据访问定义接口

接口用来暴露数据访问类的方法,强化ASP.NET MVC 的松散耦合设计。

可考虑使用Entity Framework 或 LINQ to SQL 创建对数据库的访问类,Entity Framework 和 LINQ to SQL 都支持存储过程。

将所有会话逻辑放置在Model中。

它已超出本文档在模型中存储会话状态的各种机制深入探讨的范围。 作为一个起步的点下面是几个可能性的会话状态存储:

技术

优势

弱点

在过程中

没有所需额外的安装程序。

如果 web 站点需要扩展,起作用。

会话状态服务

轻量 sertvice 在 web 场中的每台计算机上运行。 更快地数据库会话存储。

如果该服务出现故障,会话数据将丢失。

数据库

会话数据保持不变。

会话状态较慢。 管理成本也较高。

视图建议

View用来展示Model数据,Controller负责选择View。业务逻辑不属于View,Model负责业务逻辑。View非常灵活,如Model的View可通过HTML显示,同样的Model也可通过XML 视图来呈现。

HTML放置在ViewPartial View中(不要在Controller中)

默认的ASP.NET视图引擎提供了如下视图文件:HTML View(.aspx),Partial HTML View(.ascx)和Master page(.master)

如下视图演示了对partial view的调用:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

Below is a list of items submitted by <b>

<%= Html.Encode(ViewData["name"]) %></b>.

<p>

...

<div id="items">

<% Html.RenderPartial("ItemsByName");%>

</div>

</asp:content>

Partial view(ItemsByName.ascx)如下所示:

<%@ Control Language="C#" %>

<% foreach (Seller.Controllers.Items item in (IEnumerable)ViewData.Model)

{ %>

<tr>

<td>

<%= Html.Encode(item.title)%>

</td>

<td>

<%= Html.Encode(item.price)%>

</td>

</tr>

<% } %>

</table>

<% } %>

Partial View 是一个强大的扩展和重用机制。你可在不同的地方包含相同的View,不必编写重复的代码。

View中使用ViewData访问数据

ASP.NET 提供了如下机制在View模板中访问数据:

ViewData.Model 对象 – 在Controller的action方法中,在return语句中传入一个Model对象(return View(myModelObject))。

ViewData Dictionary – 在action方法中存入数据(ViewData[“key”] = value),接着在View中方法相同的dictionary。

在可能的情况下,应该是一ViewData Model,而不是ViewData 来访问数据,因为Model 提供了类型安全。此外,你应在View模板中,使用数据访问机制,而不是Request / Session 来访问。

如需要显示一个对象的多个属性,可使用ViewData.Model,并创建一个强类型View。针对seller详细页面,seller类有name、phone、address、email等等属性,在呈现View之前,你可在Controller中对ViewData.Model 赋值seller对象实例。但是如果是一些零散的数据,如page#、用户名和current time,则一般使用ViewData字典。

在使用模型绑定(Model bingding)时,避免在view中访问数据。

在Controller 中访问数据库,在执行View之前,将从数据库中检索的数据复制给轻量的View Model对象,这样,轻量的View Model对象不必在视图执行时检索数据。

使用(自动生成)客户端验证

从ASP.NET MVC 2 开始,可以很容易添加客户端验证。

(1) 如前所述,在Model层中添加数据验证逻辑;

(2) 确保项目中Scripts目录有如下javascript 文件:MicrosoftAjax.js 和 MicrosoftMvcValidation.js;

(3) 在表单提交页面,添加如下代码:

<script src="<%= Url.Content("~/Scripts/MicrosoftAjax.js") %>" type="text/javascript"></script>

<script src="<%= Url.Content("~/Scripts/MicrosoftMvcValidation.js") %>" type="text/javascript"></script>

(4) 在表单中添加如下代码:

<% Html.EnableClientValidation(); %>

现在如果编辑表单内容,当输入值不合格时,客户端马上进行验证提醒。

在模板中插入server-side 注释

在View模板中使用服务端注释,在HTML呈现时,会剔除。

如下是server-side注释:

<%-- This is a server side template comment --%>

不要在View模板中使用HTML 注释,因为这些注释会呈现在web浏览器中,可被用户看到。

使用HTMLHelper 扩展方法。

System.Web.Mvc.Html 类中包含了很多有用的HTML 扩展方法。

Form 表单生成(BeginForm)

输入字段生成(checkbox、hidden、radio button、textbox)

链接URL生成(ActionLink)

XSS保护(Encode)

尽可能使用这些HTML扩展方法,如下是使用route table创建一个链接:

<%= Html.ActionLink(“Home page”, “Default”) %>

控制器的建议

Controller 和指定的Action方法由路由系统根据匹配的URL规则调用。Controller 接收路由系统的输入参数,包括HTTP 请求上下文(Session、Cookies、Browser等等)。

使用Model Binding,而不是手动解析请求。

ASP.NET MVC 通过Model binding 抽象了许多对象反序列代码,Model binding机制是将Request Context数据通过反射对应到Action方法中定义的对象类型上。

如下代码是Seller类,定义了表单提交的数据:

[csharp] public class Seller { public Int64 ID { get; set; }

             public string Name { get; set; }

             public string Phone { get; set; }

             public string Address { get; set; } }

[/csharp]

如下的是Register View 表单:

[html] < % using (Html.BeginForm())

{ %> < legend>Account Information</legend>

< p> < %= Html.TextBox(“Name”) %> < /p>

< p> < %= Html.TextBox(“Phone”) %> < /p>

< p> < %= Html.TextBox(“Address”) %> < /p>

< p> < input value=”Register” /> < /p> < % } %>

[/html]

Controller 需要Register Action 方法,提供Model binding:

[csharp] public ActionResult Register([Bind(Exclude="ID")] Seller newSeller) { … } [/csharp]

默认的model binder 会寻找类中的每一个属性,以Name 为例:

(1) 先查找Request.Form[“Name”]; (2) 接着RouteData.Value[“Name”]; (3) 接着 Request.QueryString[“Name”]; (4) 最后如没有匹配,则为null;

从Register action方法可以看到,通过默认的model binder,对象属性将被赋值。Model binding系统也会进行验证逻辑,如data annotations attributes。

Model binding系统有丰富的扩展机制,可对object的创建、赋值和验证进行全面定制。

在Action方法中显示命名View。

在action方法中完成必要逻辑后,最后返回ViewResult 或 PartialViewResult对象。如不传入View名称给result 类,View 文件将默认为action 名称。如一个名为Products的Controller,有一个List 的Action方法,在List action方法调用 return View() 方法,且不传入任何参数。MVC 框架将寻找 /Views/Products/List.aspx 视图,如不存在,则继续寻找 /Views/Products/List.ascx。如仍不存在,则尝试 /Views/Shared/List.aspx和/Views/Shared/List.ascx。你可使用/Views/Shared 存放多个Controllers共享的视图。

为了避免混淆,在action方法中,显式命名View,如 return View(“explictViewName”),这样可从不同的Action调用List视图。

在提交表单时使用Post/Redirect/Get(PRG)

根据HTTP POST 和 GET 的定义:

  • HTTP GET 用于不更改Model数据;
  • HTTP POST 用于更改Model 数据;

下面是一个清晰的描述,在post back的action方法接收表单数据,返回 RedirectToAction(<actionName>),该方法导致一个HTTP 302 (临时跳转),并对<actionName>方法生成GET请求,这就是Post – Redirect – Get 模式。

传统的ASP.NET 表单回传数据,有可能导致数据的重复提交,可通过MVC Post-Redirect-Get模式解决这一问题。

但是,这一模式会造成client-side性能影响,因为redirect跳转会再次请求服务器,需要在性能成本和可用性之间进行判断。

实现HandleUnknownAction 和 HandleError

默认的unknown action响应是404(Not found)错误。可在Controller中重载HandleUnknownAction,针对这一错误实现一个默认视图。此外,可在controller的action方法添加HandleError属性,针对未捕获异常提供标准的错误视图。

路由的建议

ASP.NET MVC 中路由映射 URL 直接到controller,而不是一个特定的文件。默认的路由添加到RouteTable,在Global.ascx文件的Application_Start中定义。该路由表负责映射特定URL到Controller和Action。

当时使用标准路由时,从特定的路由到一般路由进行排序。

路由表是有序的,因此按特定的到一般规则来创建路由。举一个示例,假定你有一个product category需要创建URL:

* http://sellmyproducts/                             * http://sellmyproducts/Page# * http://sellmyproducts/category * http://sellmyproducts/category/Page#

假定List方法定义如下(在ProductsController 类中): Public ViewResult List(string category, int page)

基于之前指定的URL,下面的路由规范将正确路由用户到正确的视图:

[csharp] routes.MapRoute(null,“”,

new { controller = “Products”, action = “List”, category = (string)null, page = 1 }

);

routes.MapRoute(

null,

“Page{page}”,

new { controller = “Products”, action = “List”, category = (string)null },

new { page = @”\d+” }

);

routes.MapRoute(

null,

“{category}”,

new { controller = “Products”, action = “List”, page = 1}

);

routes.MapRoute(

null,

“{category}/Page{page}”,

new { controller = “Products”, action = “List”},

new { page = @”\d+” }

); [/csharp]

使用命名的路由机制,避免路由歧义

在使用ASP.NET 路由机制时,必须知道路由机制是如何工作的。否则,会浪费很多时间去跟踪错误的路由。有一个办法是显式命名路由,可缓解这一问题。

如下路由映射定义了命名路由: [csharp] routes.MapRoute(“Default”,“”,

new { controller = “Products”, action = “List”, category = (string)null, page = 1 }

);

routes.MapRoute(

“PageRoute”,

“Page{page}”,

new { controller = “Products”, action = “List”, category = (string)null },

new { page = @”\d+” }

); [/csharp]

使用这些路由定义,可创建PageRoute路由链接:

[html] < %= Html.RouteLink(“Next”, “PageRoute”, new RouteValueDictionary( new { page = i + 1 } )); %> [/html]

可扩展性的建议

在ASP.NET MVC 框架中有很多点可扩展,可替换任何核心组件,下面列举了部分:

  • routing engine (MvcRouteHandler)
  • controller factory (IControllerFactory)
  • view engine (IViewEngine)

例如,你可以重写自己的Controller factory,使用IoC容器。另外,你可通过添加定制的行为来扩展框架,在框架中有一些标准的过滤器(filter),如OutputCache、HandleError和Authorize等等。

使用过滤器来添加动作

MVC支持通过attribute添加动作(filter),可在action方法和action结果之前和之后。这些过滤器在请求处理管道允许进行轻量级扩展。

过滤器可应用在action方法上,更改其行为。另外,过滤器也可应用在controller类之上,对controller类中的所有action方法生效。可使用基类base class定义通用的行为方法,并应用过滤器在基类controller上,接着确保其他controller继承该基类。

如你想增加一个功能,记录每一次请求信息,分析HTTP header的问题。如下代码定义了一个继承ActionFilterAttribute的类: [csharp] public class LogHeadersFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { foreach (string header in filterContext.HttpContext.Request.Headers.AllKeys) { Debug.WriteLine(“Header ” + header); Debug.WriteLine(“Value ” + filterContext.HttpContext.Request.Headers.Get(header)); } base.OnActionExecuting(filterContext); } } [/csharp] 为了添加给filter到指定的action方法,可简单将LogHeaderFilter属性添加到Action(或Controller)上面。

可测试性建议

MVC模式的一个主要优点是加强可测试性,保持关注点分离。在ASP.NET MVC 应用程序的Model中,可清晰分离和测试业务逻辑。如你可测试向拍卖网站增加一条拍卖纪录,不必依赖Controller或View。

编写单元测试

ASP.NET MVC 提供了很多工具,开发人员可用来创建可测试的应用程序。此外,也相当比较简单添加第三方的单元测试框架、Mocking框架、或依赖注入容器。ASP.NET MVC 提供了灵活的架构方便单元测试。

 

安全建议

安全是现代软件开发项目中一个很重要的方面。尽管没有框架提供完美的安全,但有很多地方可做,来保护ASP.NET MVC 应用程序。

安全保护以防攻击

网站安全需要考虑所有web开发人员编写的企业级网站和服务,有许多众所周知的工具,如下所示:

  • Cross-site scripting (XSS) attacks
  • SQL injection
  • Cross-site Request Forgery (XSRF)
  • Improperly implementing model binding

(1) 阻止跨站攻击(XSS):

  • 使用ValidateInput 属性禁止无效的请求;
  • 在所有显示用户输入数据的地方使用Html.Encode,不管是立即呈现或数据将存入数据库,然后显示;
  • 在Cookies上设置HttpOnly标识,阻止Javascript读取和发送Cookies;

(2) 阻止SQL注入攻击:

  • 总是使用参数化SQL查询;
  • 不要传递未经检验的SQL脚本到数据库;
  • 使用ORM工具,如Entity Framework,可完全消除在应用程序代码中编写SQL语句;

(3) 阻止跨站请求伪造攻击:

  • 在表单中使用Html.AntiForgeryToken 类阻止跨站伪造情况。在接收post请求的Action方法上设置ValidateAntiForgeryToken 属性;

(4) 正确实现model binding:

  • 显式指定模型绑定的类成员名称,如[Bind(Include=显式属性名称)];
  • 还可创建视图模型(View model),用来接收外部表单输入,视图模型仅包含外部输入的属性;

对用户进行验证和授权保护内容

对于受限的数据视图,通过编写自定义的RoleProvider,或使用Authorize 过滤器属性。

使用<%: %> (.NET 4) 保护,以防XSS攻击

在.NET 4.0 之前,开发人员通过使用如下代码,来确保编码HTML:

<%= Html.Encode(ViewData["name"]) %>

上述代码以防 XSS (cross-site scripting) 攻击。

如果你使用.NET 4,可不必使用上面的语法,简化语法如下:

<%: ViewData["name"] %>

该脚本会自动对字符串进行HTML编码(如有必要)。

本地化和全球化建议

全球化是产品多语言化的进程,本地化是使全球化的产品适应特定的语言和国家。为了开发支持全球化和本地化的web应用程序,至少需要知道一点:不要在View视图中使用硬编码的字符串。

使用ASP.NET 特殊resource文件夹和resource文件

在编写ASP.NET 应用程序时,添加App_GlobalResources存放全球化内容,App_LocalResources存放本地化内容。在这些目录中,根据Controller名称添加资源文件(.resx)。也就是说,如Controller命名为SubmissionPipeline,资源文件应该命名为SubmissionPipeline.resx。

Visual Studio转换这一文本映射类为全局类,可按如下方式调用:

Resources.<resource_filename>.<string_name>

接着在View视图中调用:

<%= Resources.SubmissionPipeline.continueButton %>

当翻译的资源文件存在,每一个资源文件使用如下格式命名:

<filename>.<language>.resx.

如德国版本的资源文件,命名为:SubmissionPipeline.de.resx

性能的建议

网站的性能问题是多方面的,每一个瓶颈都会影响性能:

(1) 数据库

  • 低效率查询;
  • 错误的索引;
  • 非范式设计;

(2) 带宽问题

  • 大的文件请求(受单个大的图片文件、CSS / js / html 等等);
  • 引用许多其他的资源,如多个script、CSS、或图片文件;
  • 慢的连接速度;

(3) 处理能力

  • Server: 费时费力的操作;
  • Client:低性能的javascript文件;

下面将仅仅关注服务器的处理和请求大小。

考虑使用AJAX的局部页面更新,减小带宽的使用。

缓和服务器处理和请求大小性能问题的一种方法是使用AJAX做局部页面更新。ASP.NET MVC 内置支持AJAX,有助于简化这一模式。性能的改进得益于该模式减少了请求的数量和HTML 片段的大小。

下面示例演示如何使用AJAX 实现局部页面更新:

(1) 选择想更新的HTML局部,并标明ID;

<div> to be updated dynamically </div>

(2) 添加javascript文件,启用AJAX(一般存放在master视图中);

<script src=”<%= Url.Content(“~/Scripts/MicrosoftAjax.js”) %>” type=”text/javascript”></script>

<script src=”<%= Url.Content(“~/Scripts/MicrosoftMvcAjax.js”) %>”></script>

(3) 在想更新的View视图中,添加AJAX链接,引用action方法(范例中action方法命名为RetrieveItems);

<%= Ajax.ActionLink(“Refresh All”, “RetrieveItems”, new { criteria = “all” } , new AjaxOptions { UpdateTargetId = “items” })%>

(4) 实现Controller的action方法,返回Partial 视图;

不要过度使用Session,可使用TempData作为短期存储空间。

在创建web网站时,尝试将对象添加的Session对象中,让对象可用。问题是将这些对象存放在Session中,可导致Server 不得不存放额外的信息,然而这些信息仅仅在页面跳转时有用。正确的方法是将这些页面跳转的临时数据存放在TempData字典中。

假设从POST请求中接收数据,action方法处理POST 过程如下: [csharp] [AcceptVerbs(HttpVerbs.Post)] public ActionResult LogIn(Seller person) { … TempData["name"] = person.Name; return RedirectToAction(“ItemUpload”); } [/csharp] 在上面的示例中,在跳转到ItemUpload action方法之前,seller的名称已存放到TempData 字典中。在ItemUpload action方法中,可从TempData 字典中获取seller的名称,并进一步存放到ViewData字典中,可在View视图中方法。 [csharp] public ActionResult ItemUpload() { string name = TempData["name"] as string; ViewData["name"] = name; return View(); } [/csharp] 对静态页面使用OutputCache 过滤器

对返回的、比较少更新的数据,可使用OutputCache 属性;可应用在首页。可针对HTML和JSON数据类型使用这一技术。在使用OutputCache时,仅需指定缓存名称,不必指定其他内容。如需要更佳调整cache设置,可使用Web.config 文件的output cache部分。

如下所示,OutputCache attribute应用于Dashboard action方法: [csharp] [AcceptVerbs(HttpVerbs.Get), OutputCache(CacheProfile = "Dashboard")] public ActionResult Dashboard(string userName, StoryListTab tab, OrderBy orderBy, int? page) { … } [/csharp] 在Web.config文件中,设置缓存时间为15秒。

考虑针对长运行请求使用异步controller。

ASP.NET 线程池默认针对每一个CPU有12同时工作线程的限制。当请求数超过server处理这些请求的能力时,这些请求将放置在队列中。如每一个请求都需要大量的时间等待外部的资源,如数据库或大文件操作。在整个等待期间,这些外部请求将阻塞线程。当请求队列很长时(5000个请求等待),server将开始想要503错误(server too busy)。

在ASP.NET 4,同时工作线程默认为5000,可以提高默认的限制。有一个好的办法是让长运行时间的请求异步进行,ASP.NET MVC 支持实现异步controller来实现这一目的。

十二个ASP.NET MVC实践

可以帮助大家更好的进行开发工作。希望对大家有所帮助。

关于Controller的最佳实践

1-删除AccountController

让Demo代码在你的程序中是一个非常不好的做法。请永远不要使用AccountController。

2-隔离外部网络和Controller

如果依赖HttpContext,数据访问类,配置,日志等,则会让程序难以测试,修改或者进一步开发。

3-使用一个IOC容器

使达到第二条最佳实践更加容易,使用IOC容器管理所有外部依赖我使用 Ninject v2,这种IOC容器有很多,如果需要的话,你甚至可以自己实现一个。

4-神奇的strings”说不

永远不要使用ViewData[“key”],而要为每一个视图创建一个ViewModel,从而使用强类型的ViewPage。

神奇的Strings是很邪恶的,因为你可能由于错误的拼写而导致视图出错,而强类型的Model不仅可以有智能感知,而且错误是在编译时获取而不是在运行时。

5-创建你自己的个人惯例

使用Asp。net MVC作为你个人(或者公司)的参考构架的基础,你还可以使Controller和View继承于你自己的基类而不是默认的基类来让你的惯例更加透彻。

6-注意Verbs

就算不使用最合适的HTTP Verb,最要也要采用PRG模式,(Post-Redirect-Get):使用Get来显示数据,使用Post来修改数据。

关于Model的最佳实践

7–DomainModel != ViewModel

DomainModel代表着相应的域,但ViewModel却是为View的需要而创建。这两者之间或许(一般情况下都)是不同的,此外DomainModel是数据加上行为的组合体,是由复杂的变量类型组成的并且具有层次。而ViewModel只是由一些String等简单变量类型组成。如果想移除冗余并且容易导致出错的ORM代码,可以使用AutoMapper。如果想要了解更多,我推荐阅读:ASP。NET MVC View Model Patterns。

8-共享的数据使用ActionFilter

这是我自己的解决方案,或许需要在未来发帖继续探讨。通常情况下,你都不希望你的Controller获取的数据在几个不同的View之间共享,我的方法则是使用ActionFilter来获取在几个不同View之间共享的数据,然后用合适的View来显示。

关于View的最佳实践

9-不要使用CodeBehind模式

永远不要。

10-尽可能的写HTML代码

我认为Web开发人员必须的习惯于写HTML(或者CSS和JAVASCRIPT)。所以最好少用仅仅用来隐藏HTML代码的HTMLHelper(比如HTML。Submit或者HTML。Button)。这也是我会在未来的帖子里讨论的。

11-如果有if语句,使用HTMLHelper

View必须是哑巴(Controller是瘦子而Model是胖子),如果你发现自己在使用if语句,那就写一个HTMLHelper来隐藏选择条件语句。

12-仔细的选择你的View引擎

默认的引擎室WebFormViewEngine,IMHO并不是最好的引擎,我更倾向于选择Spark ViewEngine,因为对于我来说这个引擎更适合MVC的View。我喜欢的是“dominates the flow and that code should fit seamlessly”对于每一次循环来说IF语句都会被定义在”HTML标签“中。

附录:未来软件发展6大趋势

<非常简短好记!>

一。胖客户瘦服务端

二。操作系统是个网页

三。MVC+REST控制器优化架构

四。RIA富客户方案

五。嵌入插件式风格

六。HTML5---6

posted @ 2013-05-30 09:51  天山式神剑  阅读(1673)  评论(0编辑  收藏  举报