从ActionFilterAttribute向view传送数据
【原文转载】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 © 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传送数据的更多相关文章
- C# MVC分页,razor,view传送model
IMVCPages interface IMVCPages { int GetItemsCount(); int GetPageSize(); int GetPagesCount(); /// < ...
- android 通过访问 php 接受 or 传送数据
先说传送数据,可以在 利用 php 代替传送,直接把 访问的url加上 xxx.php?informatin=xxxxxx 就行了 接收的看代码吧,详细注释. 首先是 我自己定义的php 文件 < ...
- ajax的表单提交,与传送数据
ajax的表单提交 $.ajax ({ url: "<%=basePath%>resource/addPortDetectOne.action", dataType: ...
- 使用ViewBag传送数据从控制器至视图
前一篇<ASP.NET MVC读取XML并使用ViewData显示>http://www.cnblogs.com/insus/p/4308740.html 中,在控制器中使用了ViewDa ...
- ASP.NET MVC2中Controller向View传递数据的三种方式
转自:http://www.cnblogs.com/zhuqil/archive/2010/08/03/Passing-Data-from-Controllers-to-View.html 在Asp. ...
- Android fragment 想activity 传送数据
fragment可以通过定义 fragment的接口的方法来 想activity传送数据: 而activity则是通过实现 fragment的接口来接收fragment的送来的数据: 1.在fragm ...
- CPU与外设传送数据方式
7.2 CPU与外设之间数据传送的方式 在微型计算机系统中,CPU与外设之间的数据传送方式主要有程序传送方式.中断传送方式和直接存储器存取(DMA)传送方式,分别介绍如下. 7.2.1 程序传 ...
- sqoop数据迁移(基于Hadoop和关系数据库服务器之间传送数据)
1:sqoop的概述: (1):sqoop是apache旗下一款“Hadoop和关系数据库服务器之间传送数据”的工具.(2):导入数据:MySQL,Oracle导入数据到Hadoop的HDFS.HIV ...
- MVC Controller向View传递数据
ASP.NET MVC中,Controller向View传递数据的方式有一下6种 ViewData ViewBag PartialView TempData ViewModel Tuple 1.Vie ...
随机推荐
- 小细节:Java中split()中的特殊分隔符 小数点
这两天做项目过程中由于数据表字段设计的太恶心了,导致自己填坑 关于微信支付和支付宝的支付有一个不同点:就是金额的处理,支付宝金额的单位是0.01元,但是微信支付中1表示0.01元,当时设计价格的时候使 ...
- 【转】 各种 基于Unity3d 引擎的Android游戏优化 (drawcall)
合并纹理,减少贴图数量,合并网格,ui上减少不必要的层级叠加关系等 1. 更新不透明贴图的压缩格式为ETC 4bit,因为android市场的手机中的GPU有多种,每家的GPU支持不同的压缩格式 ...
- simulink下直接代码生成
连接好DSP,打开Matlab,首先必须要更改环境,要不不能编译通过 输入C2000lib,打开库, 在C2833X DSP Chip Support中添加外设,然后在C2000 Target Pre ...
- vijosP1014 旅行商简化版
vijosP1014 旅行商简化版 链接:https://vijos.org/p/1014 [思路] 双线DP. 设ab,ab同时走.用d[i][j]表示ab所处结点i.j,且定义i>j,则有转 ...
- 【C++】冒泡排序、插入排序、快速排序
#include<iostream> using namespace std; void BubbleSort(int *a,int istart,int len)//冒泡排序 { //a ...
- 通过mysql写入一句话木马
USE mysql;# MySQL 返回的查询结果为空(即零行). # MySQL 返回的查询结果为空(即零行). CREATE TABLE a( cmd1 text NOT NULL );# MyS ...
- SWMM代码移植到64位平台
在32位平台上运行SWMM模型,当节点数量到达60万以上的时候,模型运行占用内存接近1.85G的时候就会因为内存不够而无法计算.这种情况还是单独运行SWMM.exe的时候出现,如果采用SWMM.DLL ...
- 《A First Course in Mathematical Modeling》-chaper2-建模过程、比例性及几何相似性
这一章节着重从整体的层面给出数学建模过程中一个泛式流程,它给出了在给现实模型建立数学模型的框架性思路,但是需要注意的是,虽然这里称其为一种“泛式”思路,但是在具体的问题的分析中,整个建模过程还是充满了 ...
- Uber即将进驻扬州啦,车主火热招募中!
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 通过例子学python(2.2)
2.2 通用序列操作 #2.2 通用序列操作 #所有序列类型都可以进行的操作:索引indexing,分片sliceing,加adding,乘multiplying,成员资格, #计算序列长度,找出最大 ...