原文地址:ASP.NET MVC Tip #31 – Passing Data to Master Pages and User Controls
原文作者:swalther

本文译者:QLeelulu

摘要:
在这个Tip中,我会讨论给MasterPages和UserControls传递数据的4种策略。我会讲解通过code-behind类、通过使用ActionFilter、通过调用局部方法、和通过使用抽象的Controller基类来传递数据。我推荐使用最后一种方法。

在这个Tip中,我推荐一种传递数据到MasterPages和UserControls的方法。但在提出我的建议前,我会先讲解一下这个问题的几种解决方法。

The Problem

想象一下你要使用ASP.NET MVC框架来开发一个movie database application。你决定要在该应用的每一个页面上都显示一个电影分类的列表,这样,用户就可以方便的导航到他喜欢的分类。一旦你想该电影分类列表显示在每一个页面,很自然的就会想到在MasterPage中显示这个列表。
你也决定在某些页面上显示一些热门的电影列表,但不是显示在所有的页面上。这个热门的电影列表是从数据库中随机的取出来的。你决定要通过用户控件来实现:就叫 FeaturedMovies control (见图 1).

图 1 – The Movie Database Application

问题就出现在这里。你需要在程序中给每一个页面的母版页传递电影分类列表数据。你需要给程序中的某些特定的页面的热门电影用户控件传递热门电影列表数据。你怎么实现这个呢?

Using a Code-Behind Class

最通常的做法,但是是错误的,就是在code-behind class 中为你的MasterPage和FeaturedMovies用户控件取数据来解决这个问题。Listing 1 中的MasterPage显示code-behind class中的叫做Categories属性的电影分类列表。

Listing 1 – Site.Master

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="Solution1.Views.Shared.Site" %>
<%@ Import Namespace="Solution1.Models" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server"><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /><title>Movies</title><link href="http://www.cnblogs.com/Content/Site.css" rel="stylesheet" type="text/css" />
</head><body>
<div class="page"><div id="header"><h1>Movie Database Application</h1></div><div id="main"><div class="leftColumn"><ul><% foreach (MovieCategory c in this.Categories){ %><li> <%= Html.ActionLink(c.Name, "Category", new {id=c.Id} )%></li><% } %></ul></div><div class="rightColumn"><asp:ContentPlaceHolder ID="MainContent" runat="server" /></div><br style="clear:both" /><div id="footer">Movie Database Application &copy; Copyright 2008</div></div>
</div>
</body>
</html>

这个MasterPage的code-behind class在Listing 2 中。注意在Listing 2 中是直接通过LINQ2SQL来取数据的。

Listing 2 – Site.Master.cs

using System.Collections.Generic;
using System.Linq;
using Solution1.Models;namespace Solution1.Views.Shared
{public partial class Site : System.Web.Mvc.ViewMasterPage{protected IEnumerable<MovieCategory> Categories{get{var dataContext = new MovieDataContext();return from c in dataContext.MovieCategories select c;}}}}

你同样可以为FeaturedMovies 用户控件来取数据。在FeaturedMovies code-behind class 从数据库中取热门电影的列表数据。

那么,为什么这错了呢?这当然好像一个简单的解决办法。它正常工作了,为什么还要抱怨?

这个解决方案的问题是MasterPage的code-behind class 中的代码是不可测试的。你不可以很方便的为Site类写单元测试,因为Site类是继承自ViewMasterPage类,而ViewMasterPage类继承自Page类。The Page class relies on the HTTP Context object and all hope of isolating your code so that it can be tested goes away.

在开发ASP.NET MVC应用的时候,你应该尽量避免在你的程序中在code-behind class 中处理逻辑,尝试将所有的东西都放回到Controllers中。Controllers被设计为可测试的。

Using an Action Filter

所以让我们以另一种途径来解决这个传递数据给MasterPage或者view的问题。在这一节,我们创建一个ActionFilter来修改传递给view的ViewData。这个方法你可以通过给controller添加一个或者多个action filter来修改由controller传递给view的ViewData。

这个ActionFilter,命名为[Partial] ,如Listing 3所示。

Listing 3 – ActionFilters\PartialAttribute.cs

using System;
using System.Reflection;
using System.Web.Mvc;namespace Solution2.ActionFilters
{public class PartialAttribute : ActionFilterAttribute{private string _partialClassName;public PartialAttribute(string partialClassName){_partialClassName = partialClassName;}public override void OnActionExecuting(ActionExecutingContext filterContext){var viewData = (filterContext.Controller as Controller).ViewData;ExecutePartial(_partialClassName, viewData);}private void ExecutePartial(string partialName, ViewDataDictionary viewData){// Get partial typevar partialType = Type.GetType(partialName, true, true);var partial = Activator.CreateInstance(partialType);// Execute all public methodsvar methods = partialType.GetMethods();foreach (MethodInfo method in methods){var pams = method.GetParameters();if (pams.Length > 0 && pams[0].ParameterType == typeof(ViewDataDictionary))method.Invoke(partial, new object[] { viewData });}}}
}

当你添加[Partial] 到一个controller action的时候,这个ation filter会附加一些数据到view data中去。例如,有可以使用[Partial] 特性来添加电影分类列表的数据到view data中去以便在master page中显示。你也可以使用[Partial] 特性来添加热门电影列表到view data 中以使在FeaturedMovie 用户控件中得到该数据。

[Partial] 特性通过一个类名,实例化这个类,然后执行类里面所有的public方法(每一个方法都包含一个ViewDataDictionary参数),Listing 4 中的controller说明了你可以怎样使用[Partial] action filter来为不同的controller返回不同的ViewData。

Listing 4 – HomeController.cs

using System.Linq;
using System.Web.Mvc;
using Solution2.ActionFilters;
using Solution2.Models;namespace Solution2.Controllers
{[Partial("Solution2.Partials.Master")]public class HomeController : Controller{[Partial("Solution2.Partials.Featured")]public ActionResult Index(){return View();}public ActionResult Category(int id){var dataContext = new MovieDataContext();var movies = from m in dataContext.Movies where m.CategoryId == id select m;return View("Category", movies);}}
}

注意到HomeController它本身是添加了[Partial] action filter的。由于[Partial] action filter应用到类上,在HomeController里面的每一个action执行的时候这个action filter都会执行的。在类级别上应用[Partial] 特性来为master page提供view data。

类级别上的[Partial]特性添加电影分类列表到view data中。[Partial]执行Solution2.Partials.Master 类中的方法,如Listing 5 所示。

Listing 5 – Master.cs

using System.Linq;
using System.Web.Mvc;
using Solution2.Models;namespace Solution2.Partials
{public class Master{public void AddViewData(ViewDataDictionary viewData){var dataContext = new MovieDataContext();var categories = from c in dataContext.MovieCategories select c;viewData["master"] = categories; }}
}

AddViewData() 方法将categories 添加到key为"master"的view data dictionary中。master page从view data 中取出categories 并显示。

[Partial] 也可以添加到特定的action上,例如Listing 4 中的Index()方法。

然而这种从controller中传递数据给母版页和用户控件的解决方案错在哪里呢?这种方法的优于前面一种方法的地方在于他将获取数据的逻辑放回到controller中来处理了。ViewData在controller action 调用的时候会被修改。

无论怎样,这个解决方案还是挺不错的。通过使用[Partial] 特性,你可以为view data dictionary 添加更多的数据。例如,如果你决定你要添加一个新的用户控件到某一个页面,而这个新的用户控件需要一个不同的数据集,你可以很简单的添加一个新的[Partial] 特性到正确的controller action上来添加新的数据到view data dictionary中去。

遗憾的是,只是一点点的遗憾,这个解决方案不是很容易进行单元测试。当你在一个单元测试里面执行action方法的时候ActionFilter并不会执行。所以,我们需要寻找一个不同的策略来解决这个问题。

Calling Partial Methods Directly

让我们进入到这个问题的第三个解决方案中。在这一节中,我们尝试通过直接在controller中来获取数据然后传递给母版页和用户控件来解决这个问题。在Listing 6 中是我们修改后的HomeController的代码。

Listing 6 – HomeController.cs (with partials logic)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Solution3.Models;
using Solution3.Partials;namespace Solution3.Controllers
{public class HomeController : Controller{public HomeController(){Master.AddViewData(this.ViewData);}public ActionResult Index(){Featured.AddViewData(this.ViewData);return View();}public ActionResult Category(int id){var dataContext = new MovieDataContext();var movies = from m in dataContext.Movies where m.CategoryId == id select m;return View("Category", movies);}}
}

注意到Listing 6 中的HomeController有一个构造函数。在构造函数中调Master.AddViewData() 来改变controller action中返回的view data。这个方法要在母版页中显示的view data。

Index()方法也改变了。在Index()方法里面,调用了Featured.AddViewData() 方法。这个方法为FeaturedMovies 用户控件添加必需的view data。由于FeaturedMovies 用户控件只在Index视图中呈现,而不在Categorys视图中呈现,所以不在构造函数中调用Featured.AddViewData() 方法。

这个解决方案的优点是非常容易进行单元测试。当你调用Index()方法,view data同时被Master和Featured的部分方法改变了。也就是说,你可以容易的测试你传递给母版页和用户控件的view data是否是正确的。

那么,这个解决方案错在哪里了呢?添加view data的所有逻辑都已经放到controller类中来处理了。这个解决方案已经比前面的两个方案要好很多了。这个解决方法的唯一的问题是它违背了单一责任原则。

根据单一责任原则,代码应该只有一个单一的理由去改变(code should have only a single reason to change)。然而,我们有很多原因要去改变Listing 8 中的Index()方法。如果我们也决定添加一个新的用户控件到Index视图中,而这个新的用户控件显示一个新的数据集,然后我们就需要去修改Index() action了。

单一责任制原则背后的目的是你永远不要去改变已经在运作中的代码。改变代码通常意味着为你的应用带入一个bug。我们需要寻找一些途径来添加新的view data而不用修改我们的controller action。

Using Abstract Base Classes

这里是我对于这个问题的最后一个解决方案:我们将使用抽象的基类来改变从controller action返回来的view data。我现在要警告你这个是复杂的。我们需要创建好几个类。然而,每一个类都是单一职责的。每一个类都只是负责一种类型的view data 而已(见图2)。

Figure 2 – Class Hierarchy

我们将创建一个抽象基类,命名为ApplicationController ,改变view data divtionary来为我们的母版页添加所需的所有的view data(见Listing 7)。这个ApplicationController 在我们的程序中作为每一个controller的基类,而不单单是HomeController。

Listing 7 – ApplicationController

using System.Web.Mvc;
using Solution4.Partials;namespace Solution4.Controllers
{public abstract class ApplicationController : Controller{public ApplicationController(){Master.AddViewData(this.ViewData);}}
}

下一步,我们将要创建一个命名为HomeControllerBase 的抽象基类(见Listing 8).这个类包含了通常出现在HomeController的所有的应用逻辑。我们将会重写action方法来为特定的用户控件添加需要的view data。

Listing 8 – HomeControllerBase.cs

using System.Linq;
using System.Web.Mvc;
using Solution4.Models;namespace Solution4.Controllers.Home
{public abstract class HomeControllerBase : ApplicationController{public virtual ActionResult Index(){return View("Index");}public virtual ActionResult Category(int id){var dataContext = new MovieDataContext();var movies = from m in dataContext.Movies where m.CategoryId == id select m;return View("Category", movies);}}
}

对于每一个用户控件,我们将会需要创建一个额外的抽象类。对于FeaturedMovies 用户控件,我们将会创建一个HomeControllerFeatured 类(见Listing 9)。对于PopularMovies 用户控件,我们将会创建一个HomeControllerPopular 类(见Listing 10)。

Listing 9 – HomeControllerFeatured.cs

using System.Web.Mvc;namespace Solution4.Controllers.Home
{public abstract class HomeControllerFeatured : HomeControllerBase{public override ActionResult Index(){var result = (ViewResult)base.Index();Partials.Featured.AddViewData(result.ViewData);return result;}}
}

Listing 10 – HomeControllerPopular.cs

using System.Web.Mvc;namespace Solution4.Controllers.Home
{public abstract class HomeControllerPopular : HomeControllerFeatured{public override System.Web.Mvc.ActionResult Category(int id){var result = (ViewResult)base.Category(id);Partials.Popular.AddViewData(result.ViewData);return result;}}
}

最后,我们需要添加这个层次关系的最上面一个类。我们将会创建一个HomeController 类。这个类简单的继承自上面的其中一个基类(见Listing 11)。他本身并不包含应用逻辑。

HomeController 类这个层次关系中的唯一一个不是抽象类的。由于它不是抽象类,他的controller actions 可以被全世界调用(its controller actions can be invoked by the world)。

Listing 11 – HomeController.cs

namespace Solution4.Controllers.Home
{public class HomeController : HomeControllerPopular{}
}

现在,你或许会受不了这么多的类。然而,这个解决方案的优点是我们已经很干净的分离了建造view data 的逻辑。每一个抽象类都具有单一的责任。我们的代码不再脆弱。

Summary

我并完全信服我自己的Tip。我仍然在尝试这通过使用action filter 来为我的master pages 和 user controls 添加view data。描述的最后一个解决,使用抽象基类,好像需要大量工作。我很好奇于这个问题的其他的解决方案。

转载于:https://www.cnblogs.com/QLeelulu/archive/2008/08/17/1269599.html

ASP.NET MVC Tip #31: 给 Master Pages 和 User Controls 传递数据相关推荐

  1. ASP.net 2.0 Migrating系列 - Master Pages 感触

    ASP.net 2.0 Migrating系列 - Master Pages范维肖 在Visual Web Developer 2005里的新建里多了一个Master Pages,在微软的VWD200 ...

  2. (转)[翻译] ASP.NET MVC Tip #1 - 使用扩展方法创建新的HTML Helper

    原文地址:http://weblogs.asp.net/stephenwalther/archive/2008/06/13/asp-net-mvc-tip-1-creating-new-html-he ...

  3. 部分视图传viewbag_NET开发-MVC中如何使用ViewBag和操作方法参数向视图传递数据?...

    ASP.NET MVC控制器向视图传递数据 第1节:ViewBag的使用 基本概念 在ASP.NET MVC中,有一个特殊的ViewBag对象,ViewBag是一个dynamic动态类型,定义在Con ...

  4. ASP.NET MVC and jqGrid 学习笔记 2-如何从本地获得数据

    上回说到jqgrid的基本配置,同时演示了显示数据的一种方法--datatype: "local".这种方法是从本地获取的,确切地说是在前端页面的javascript里写的硬编码. ...

  5. ASP.NET MVC 5 - 验证编辑方法(Edit method)和编辑视图(Edit view)

    在本节中,您将验证电影控制器生成的编辑方法(Edit action methods)和视图.但是首先将修改点代码,使得发布日期属性(ReleaseDate)看上去更好.打开Models \ Movie ...

  6. ASP.NET MVC 表单提交教程

    在前面的两篇文章总,我们分别做了一个简单的ASP.NET MVC的例子和进行数据的绑定,在本文中,将通过ASP.NET MVC Framework实现表单的提交,你可以看到,在这里有多种方法来获取表单 ...

  7. asp.net MVC 的处理流程

    之前把笔记都放在空间日志中隐藏起来,今天看到这句话:作为经常从网上索取免费资料的一员,要有回报的思想,也为了让更多的人少走些弯路,想想自己不能这么自私,所以把空间日志搬到博客园来.闲话不说,直接开始. ...

  8. ASP.NET MVC以ModelValidator为核心的Model验证体系: ModelValidator

    旨在为目标Action方法的执行绑定输入参数的Model绑定过程伴随着对Model的验证.借助相应的验证特性,我们可以直接以声明的方式在Model类型上定义验证规则,这些规则将会作为Model元数据的 ...

  9. ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解

    ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解 原文 ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解 在教程的这一部分,我们将研究一 ...

最新文章

  1. no output in console for unittests in pycharm 2017
  2. 冲刺第六天 1.7 MON
  3. VTK:baking烘焙阴影贴图用法实战
  4. code vs1517 求一次函数解析式(数论 纯数学知识)
  5. 【复赛前排分享(一)】上分有路勤为径,大神教你剖析提分点
  6. 移动硬盘提示由于IO设备错误,无法运行此项请求要怎么办啊
  7. 批量doc转docx方法,使用软件、插件
  8. 酷派大观4 8970 刷android 4.4,酷派5890驱动 酷派 8970L(大观4)recovery卡刷通用刷机教程...
  9. 2019年“深圳杯”数学建模挑战赛B题(1、2问)
  10. Android 获取当前设备SIM运营商
  11. 说企业自研应用是误区的,非蠢即坏
  12. java 标签云_网站标签云(TagCloud)的实现
  13. 十亿级别人脸搜索引擎
  14. 分析:中国股市独步天下
  15. 工程图学及计算机绘图宋卫卫,工程图学及计算机绘图习题集
  16. 版本控制系统GIT和SVN的区别
  17. 如何通过Git客户端从Gitlab下载代码
  18. 阿里云——手把手教你搭建个人网站
  19. centos7系统时间的修改
  20. 关于substance designer内创建好材质,导入unity却变成灰球的问题

热门文章

  1. 小白科普:虚拟化简史
  2. 我的编码习惯 —— API 接口定义
  3. 脑洞大开,如何生成 2018 年度代码报告
  4. 从Java程序员的角度理解加密的那些事
  5. 开放封闭原则(OCP)
  6. 【报错】No match for argument: mysql-community-server Error: Unable to find a match: mysql-community-s
  7. 二层交换配置完ping失败_设置完端口聚合之后就ping不通了!!!
  8. c#获取对象的唯一标识_Articy Importer Guide - 01 基本对象处理
  9. 数据中心液体冷却方案正在兴起的五大原因
  10. 生鲜配送小程序源码_生鲜果蔬配送小程序开发源码