【原文转载】http://www.cnblogs.com/QLeelulu/archive/2008/08/17/1269599.html

原文地址: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 type
var partialType = Type.GetType(partialName, true, true);
var partial = Activator.CreateInstance(partialType); // Execute all public methods
var 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。描述的最后一个解决,使用抽象基类,好像需要大量工作。我很好奇于这个问题的其他的解决方案。

从ActionFilterAttribute向view传送数据的更多相关文章

  1. C# MVC分页,razor,view传送model

    IMVCPages interface IMVCPages { int GetItemsCount(); int GetPageSize(); int GetPagesCount(); /// < ...

  2. android 通过访问 php 接受 or 传送数据

    先说传送数据,可以在 利用 php 代替传送,直接把 访问的url加上 xxx.php?informatin=xxxxxx 就行了 接收的看代码吧,详细注释. 首先是 我自己定义的php 文件 < ...

  3. ajax的表单提交,与传送数据

    ajax的表单提交 $.ajax ({ url: "<%=basePath%>resource/addPortDetectOne.action", dataType: ...

  4. 使用ViewBag传送数据从控制器至视图

    前一篇<ASP.NET MVC读取XML并使用ViewData显示>http://www.cnblogs.com/insus/p/4308740.html 中,在控制器中使用了ViewDa ...

  5. ASP.NET MVC2中Controller向View传递数据的三种方式

    转自:http://www.cnblogs.com/zhuqil/archive/2010/08/03/Passing-Data-from-Controllers-to-View.html 在Asp. ...

  6. Android fragment 想activity 传送数据

    fragment可以通过定义 fragment的接口的方法来 想activity传送数据: 而activity则是通过实现 fragment的接口来接收fragment的送来的数据: 1.在fragm ...

  7. CPU与外设传送数据方式

    7.2 CPU与外设之间数据传送的方式 在微型计算机系统中,CPU与外设之间的数据传送方式主要有程序传送方式.中断传送方式和直接存储器存取(DMA)传送方式,分别介绍如下.     7.2.1 程序传 ...

  8. sqoop数据迁移(基于Hadoop和关系数据库服务器之间传送数据)

    1:sqoop的概述: (1):sqoop是apache旗下一款“Hadoop和关系数据库服务器之间传送数据”的工具.(2):导入数据:MySQL,Oracle导入数据到Hadoop的HDFS.HIV ...

  9. MVC Controller向View传递数据

    ASP.NET MVC中,Controller向View传递数据的方式有一下6种 ViewData ViewBag PartialView TempData ViewModel Tuple 1.Vie ...

随机推荐

  1. mysql 海量数据的存储和访问解决方案

    第1章  引言 随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题.对于一个大型的互 联网应用,每天几十亿的PV无疑对数据库造成了相当高的负载.对于系统的稳定性和扩展性造成了极大的 ...

  2. Linux Shell 学习笔记

    2.return与exit区别 return 表示从被调函数返回到主调函数继续执行,返回时可附带一个返回值,由return后面的参数指定,当然如果是在主函数main, 自然也就结束当前进程了,如果不是 ...

  3. HBase学习笔记之HFile格式

    主要看Roger的文档,这里作为文档的补充 HFile的格式-HFile的基本结构 Trailer通过指针找到Meta index.Data index.File info. Meta index保存 ...

  4. Scala:(1)变量

    Scala中变量需要注意的地方: (1)val,var val 定义的声明是一个常量,不能改变内容. var定义的声明是一个变量,可以改变其内容 在Scala中,尽可能使用val val answer ...

  5. HTML5 Canvas JavaScript库 Fabric.js 使用经验

    首先,表明我的态度:采用 Flash 才是最优方案,不建议使用 HTML 5 的 Canvas 做一些生产/工业级的网页应用. Flash的优势一是浏览器支持好,二是代码成熟稳定.而HTML5 的 C ...

  6. Unity FisheyeShader using Spherical Mapping

    Shader "Hidden/FisheyeShader" { Properties { _MainTex ("Base (RGB)", 2D) = " ...

  7. Hibernate(十一)多对多双向关联映射

    上次我们在中Hibernate从入门到精通(十)多对多单向关联映射讲解了一下多对多单向关联映射,这次我 们讲解一下七种映射中的最后一种多对多双向关联映射. 多对多双向关联映射 按照我们之前的惯例,先看 ...

  8. java多线程编程(2)交替输出数字和字母

    mark一下,不停的看看notify和wait的没有理解 class Printer { int index=0; //输出奇数 public synchronized void printA(int ...

  9. 在PHP网页中,如何把$_session["yyy"]赋值到一个文本框中?

    echo '<input type="text" id="text1" name="text1" value="'.$_SE ...

  10. lightoj 1033 区间dp

    题目链接:http://lightoj.com/volume_showproblem.php?problem=1033 #include <cstdio> #include <cst ...