【原文转载】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. Reverse Linked List 递归非递归实现

    单链表反转--递归非递归实现 Java接口: ListNode reverseList(ListNode head) 非递归的实现 有2种,参考 头结点插入法 就地反转 递归的实现 1) Divide ...

  2. 【HDOJ】1466 计算直线的交点数

    找了个规律. #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXN 21 ...

  3. 用Robotium 去实现点击imageview

    今天用rototium做自动化遇到imageview无法点击的问题,最终解决如下: 有两种方法: 1.View v = solo.getView(R.id.iv_main_setting);      ...

  4. Linux学习笔记11——文件I/O之二

    一.文件共享 内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响. 1.每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述表 2.内 ...

  5. BZOJ1015 [JSOI2008]星球大战starwar(并查集)

    1015: [JSOI2008]星球大战starwar Time Limit: 3 Sec  Memory Limit: 162 MBSubmit: 3895  Solved: 1750[Submit ...

  6. JavaScript高级程序设计6.pdf

    ECMAScript通过RegExp类型来支持正则表达式 var expression=/pattern/flags;其中模式(pattern)部分是正则表达式,可以包含字符类.限定符.分组.向前查找 ...

  7. MVC 5 第一章 起航

    本章将讲述一些构建ASP.NET  MVC 5 web application的一些基础知识, 通过本章学习,你应该能够掌握到构建MVC 5应用程序的基本步骤,并且通过展示一个完整的MVC 5 hel ...

  8. ReactiveCocoa Tutorial

    ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 ReactiveCocoa教程——明确的介绍:第一部分(共两部分) As ...

  9. java的任务监听器监听任务

    Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务. 使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行.一般用的较少 监听 ...

  10. uva11549 Floyd判圈法

    题意: 给两个数n, k,每次将k平方取k的前n位,问所有出现过的数的最大值 原来这就是floyd判圈法.. #include<cstdio> #include<cstdlib> ...