MVC最佳实践

MVC执行流程

mvc.png 

Model的建议

Model是用于定义特定于域的对象。 这些定义应包括业务逻辑 (对象的行为)、验证逻辑、数据逻辑和会话逻辑 (跟踪应用程序的用户状态)。javascript

作模型本身的项目使用分隔不一样的程序集。

大型复杂模型的应用程序最好建立一个单独的Model程序集,以减小耦合。 而后,能够在 ASP.NET MVC 项目中引用Model的程序集。css

把全部的业务逻辑放在模型中。

若是你把全部的业务逻辑模型中, 你还得到如下好处:html

  • 减小重复的业务逻辑。
  • 视图易于阅读,不存在的业务逻辑。
  • 测试业务规则是独立的。

例如:java

<% if (String.Compare((string)TempData["displayLastNameFirst"], "on") == 0)web

       { %>数据库

        Welcome, <%= Model.lastName%>, <%= Model.firstName%>浏览器

    <% }缓存

       else安全

       { %>服务器

        Welcome, <%= Model.firstName%> <%= Model.lastName%>

<% } %>

若是不少地方须要这样的逻辑,就是有在视图中写这样的代码,若是放在Model中:

public string combinedName

{

    get

    {

        return (displayLastNameFirst ? lastName + " " + firstName : firstName + " " + lastName);

    }

    private set

    {

        ;

    }

}

这将极大地简化视图,如图所示:

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

把全部的验证逻辑放在Model中。

全部输入的验证应该放在Model中,包括客户机端验证。

可使用 ModelState 来添加验证检查。 下面的示例显示了如何将有明确的 ModelState 添加验证检查:

if (String.IsNullOrEmpty(userName))

{

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

}    

.net 框架提供的 System.ComponentModel.DataAnnotations 应该是验证的首选的方法。好比:

public class User

{

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

   public String userName { get; set; }

  …

  }

定义数据访问接口

接口用于公开数据访问提供程序的方法, 这能加强 ASP.NET MVC 的松散耦合的组件的设计。

请考虑使用实体框架或 LINQ to SQL 建立到数据库的调用wrappers。

View建议

HTML 放在ViewPartial Views(而不是在一个Controller)。

对于默认视图引擎 ASP.NET 提供了几种文件类型: 彻底 HTML 视图 (.aspx),部分 HTML 视图 (.ascx) 和母版页(.master)。 母版页使您可以指定一个总体布局的视图。 母版页能够嵌套几回建立的可用的布局类型层次结构。若是是Razor视图没有后缀名区别。

下面的示例显示了调用局部视图的方式:

<%@ 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>

局部视图 (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>

      <% } %>

局部视图是一个功能强大的可扩展性和重用机制。

访问视图使用ViewData

ASP.NET 提供了如下的机制访问视图模板中的数据:

  • ViewData.Model
  • ViewData 字典

推荐使用ViewModel,ViewModel是强类型,能保证类型安全。

使用自动生成客户端验证。

在模板中插入服务器端注释。

下面的行演示了服务器端注释:

< %--这是一个服务器端的模板评论 -- %>

不要视图模板中使用 HTML 注释,由于他们会呈现到 web 浏览器,可被潜在的恶意的用户查看。

使用 HTMLHelper 扩展方法。

经常使用方法:

  • 表单生成 (BeginForm)
  • 输入的字段生成复选 (框,隐藏,单选按钮文本框)
  • 连接代 (ActionLink)
  • XSS 保护 (编码)

Controller的建议

Action方法显示指定view 名称。

能够直接return view();

若是这样,MVC框架首先查找 /Views/Products/List.aspx若是不存在,查找 /Views/Products/List.ascx,若是还没找到,查找 /Views/Shared/List.aspx 而后是/Views/Shared/List.ascx. 从上分析 /Views/Shared 这个目录的文件能够跨controller共享,为避免混淆和性能考虑,推荐使用显示指定view名称的方式。

当提交表单时,使用post/redirect/get PRG)。

根据 HTTP POST 和 GET 动词的定义:

  • HTTP GET 用于非更改您的模型 (幂等) 数据。
  • HTTP POST 用来更改您的模型的数据。

如图在标准的回发中使用同一个 url create.aspx进行 GET 和 POST, 这是一个问题,当网站的用户获取窗体发布到完成没有耐心等待,若是他们点了浏览器的刷新按钮,可能致使的重复数据。使用后Redirect 获取模式能够解决这个问题。

无标题1.png 

使用 HandleError或者在Web.config自定义错误处理

路由的建议

路由用于在 ASP.NET MVC url 直接映射到一个的控制器,而不是特定的文件。 这是以提升可读性,能够更好被搜索引擎收录。

使用定制路由时使用从具体到通常路线。

定制路由听从具体到通常的路线。

考虑下面的示例。 假设有一个以下列形式的 url 的产品目录:

  • http://sellmyproducts/
  • http://sellmyproducts/Page#
  • http://sellmyproducts/category
  • http://sellmyproducts/category/Page#

提供如下列表方法的签名 (ProductsController 类):

public ViewResult List(string category, int page)

定制路由:

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+" }

    );

使用命名的路由机制,避免多义性的路由。

例如如下命名的路由:

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+" }

);

使用这些路由定义,您能够建立连接,将解析为"PageRoute",以下所示:

<%= Html.RouteLink("Next", "PageRoute",

     new RouteValueDictionary( new { page = i + 1 } )); %>

可扩展性的建议

ASP.NET MVC 框架内有不少扩展点。 能够替换的任何一个部分的列表,其中包括如下的核心组件:

  • 路由引擎 (MvcRouteHandler)
  • 控制器厂 (IControllerFactory)
  • 视图引擎 (IViewEngine)

能够经过在filters添加自定义行为扩展框架,好比一些包含在框架的标准筛选器: OutputCache,HandleError,和Authorize。

使用filters添加行为

这些筛选器能够实现轻量级的请求处理管线的可扩展性。

例如假设要添加记录为每一个请求调试问题的 HTTP 标头信息的功能。 下面的代码定义了从 ActionFilterAttribute 类派生的类。

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);

   }

}

给定的操做方法中添加此筛选器,只要将 LogHeadersFilter 属性放在要筛选的Action(或Controller) 的顶部。

可测试性建议

MVC 模式的主要优点之一是改进可测试性设计,解耦。

编写单元测试。

ASP.NET MVC 提供了一个灵活的体系结构,容许简单测试。能够采用MS的单元测试工具或第三方工具来编写单元测试。

关于单元测试 ASP.NET MVC 的应用程序的详细信息,请参阅 MVC 应用程序中的单元测试

安全建议

安全是任何现代软件开发项目的一个重要方面。 虽然没有框架能够提供完善的安全,可是仍是有不少方面你能够帮助保护你的 ASP.NET MVC 应用程序。

防止常见的攻击。

网站安全须要全部 web 开发人员的关注,常见攻击方式:

  • 跨站点脚本 (XSS) 攻击
  • SQL 注入
  • 跨站点请求伪造文件 (XSRF)

为防止跨站点脚本 (XSS) 攻击:

  • 禁用请求验证经过使用 ValidateInput 属性。 此属性将拒绝错误的 HTML 输入。
  • 将显示的全部用户输入数据都添加 Html.Encode,不论是当即呈现的数据仍是从数据库取出要显示的数据。
  • cookie 设置 HttpOnly 标志。 防止JavaScript阅读和发送 cookie。

为防止 SQL 注入:

  • 始终使用参数化的 SQL 查询。
  • 不要将原始 SQL 传递到数据库中。
  • 使用 (例如,能够彻底消除的 SQL 语句在应用程序代码须要的实体架构的对象关系映射 (ORM)。

为防止跨站点请求伪造 (XSRF):

  • 在窗体中使用 Html.AntiForgeryToken 类可防止伪造跨站点请求。
  • 在须要保证的Action方法上增长ValidateAntiForgeryToken 属性

进行身份验证和受权来保护内容的用户。

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

前,.net 4.0 开发人员将必须那里确保 HTML 编码经过使用相似于下面的代码:

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

此代码被要防止 XSS 跨站点脚本攻击。

若是使用的.net 4 不要使用上述语法。 请使用如下语法。

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

本地化和全球化建议

全球化是使一个产品多语言本地化在哪里的适应特定语言和国家的全球产品过程的过程。 要开发一个 web 应用程序,它支持全球化和本地化,请记住至少一个规则。 在视图中不使用硬编码字符串。

不要使用 ASP.NET 特殊资源文件夹和资源文件。

添加 ASP.NET 项目文件夹全球化内容 (App_GlobalResources) 和给定的视图 (App_LocalResources) 的本地化内容。 在每一个这些文件夹应添加一个资源 (.resx) 文件,应根据该控制器的名称命名。若是控制器被命名为 SubmissionPipeline,资源文件应被命名为 SubmissionPipeline.resx。

visual Studio 将此文本映射类转换为全球的类,能够调用使用下面的语法:

Resources.<resource_filename>.<string_name>

而后将访问此类视图中的资源:

<%= Resources.SubmissionPipeline.continueButton %>

获得翻译的资源文件时应使用如下格式命名每一个文件: <filename>. <language>.resx。

例如将命名资源文件的德语版的: SubmissionPipeline.de.resx。

性能的建议

可能会影响 web 站点性能的瓶颈包括:

  • 数据库
    • 低效的查询
    • 不正确地放置索引
    • 非规范化设计
  • 带宽问题
    • 请求大小 (大图像、.css、.js、.html、 等)。
    • 引用了其余项目的多个脚本,CSS 或图像文件的内容
    • 慢速链接
  • 处理能力
    • 服务器: 费时的操做
    • 客户端: 糟糕的 javascript

考虑减小带宽使用 AJAX 的部分页更新。

使用Javascript  AJAX 异步来处理部分页更新的请求来减轻涉及服务器处理压力是的解决性能问题的一种方法。 ASP.NET MVC 有内置的 AJAX 支持,可减小处理服务器必须执行呈现的请求和减小的 HTML 片断大小。

下面的示例说明了如何使用 AJAX 的部分页更新:

1.     建立PartialView test.cshtml。

@model MVC.Test.Site.Models.UserView <div id="items">     username:     @if (Model != null)     {         @Model.UserName     }

</div>

2.     在Action.cshtml中添加

@{Html.RenderPartial("test", ViewData["user"]);} 

3.    Home/Login代码

 public ActionResult Login(UserView user)   {             ViewData["user"] = user;             return PartialView("Action", user); } 

不要过分使用会话,而是使用 TempData存储。

建立网站将对象添加到 会话 的对象,以便它们随时可用时,看起来很好,可是将这些对象放在 Session 对象中的问题是它可能给服务器形成负担,由于只是redirect的时候用到。 在重定向存储这些临时变量的正确方法是使用 TempData 词典。

例如假设收到一个 POST 登陆时的表单数据。 初始化过程多是下面的操做方法:

[AcceptVerbs(HttpVerbs.Post)]

public ActionResult LogIn(Seller person)

{

    ...

    TempData["name"] = person.Name;

    return RedirectToAction("ItemUpload");

}

重定向到 ItemUpload 的操做方法以前将person.name放置在 TempData 词典中。 ItemUpload 操做方法从 TempData 词典中检索,并放在本身的ViewData中,以便它能够在视图中引用。

public ActionResult ItemUpload()

{

    string name = TempData["name"] as string;

    ViewData["name"] = name;

 

    return View();

}

使用OutputCache

OutputCache 属性用于不频繁更新的数据 , 主要是用于首页。 对于 HTML 和 JSON 数据类型,可使用这种技术。 当使用它,只指定缓存配置文件的名称 ; 不指定任何其余。使用 Web.config 文件配置output cache section。

例如 OutputCache 属性附加到在如下代码中的控制板操做方法。

[AcceptVerbs(HttpVerbs.Get), OutputCache(CacheProfile = "Dashboard")]

public ActionResult Dashboard(string userName, StoryListTab tab, OrderBy orderBy, int? page)

{

    ...

}

考虑使用异步控制器长时间运行的请求。

ASP.net 线程池的默认限制为每一个 CPU 12 并发工做线程。 当请求过载处理这些请求的服务器的能力时,队列是创建的请求。 例如任何请求,须要大量的时间等待外部资源如数据库或较大的文件操做。 这些外部请求阻止他们占用整个等待时间的线程。 当此队列获取太大 (5000 请求挂起) 时,在服务器启动 503 (服务器太忙) 错误响应。

在 ASP.NET 4 的并发线程数设置默认状况下,5000。 虽然可能增长默认限制有更好的方法,以减轻长时间运行的请求占用了修改异步运行的长时间运行请求的线程。 ASP.NET MVC 使您能够为此目的实现异步控制器。 有关如何实现一个异步控制器的详细信息,请参阅 使用 ASP.NET MVC 的一种异步控制器