ASP.NET MVC Tip #31: 给 Master Pages 和 User Controls 传递数据
原文地址:ASP.NET MVC Tip #31 – Passing Data to Master Pages and User Controls
原文作者:swalther
本文譯者:QLeelulu
摘要:
在這個Tip中,我會討論給MasterPages和UserControls傳遞數(shù)據(jù)的4種策略。我會講解通過code-behind類、通過使用ActionFilter、通過調(diào)用局部方法、和通過使用抽象的Controller基類來傳遞數(shù)據(jù)。我推薦使用最后一種方法。
?
在這個Tip中,我推薦一種傳遞數(shù)據(jù)到MasterPages和UserControls的方法。但在提出我的建議前,我會先講解一下這個問題的幾種解決方法。
The Problem
想象一下你要使用ASP.NET MVC框架來開發(fā)一個movie database application。你決定要在該應(yīng)用的每一個頁面上都顯示一個電影分類的列表,這樣,用戶就可以方便的導(dǎo)航到他喜歡的分類。一旦你想該電影分類列表顯示在每一個頁面,很自然的就會想到在MasterPage中顯示這個列表。
你也決定在某些頁面上顯示一些熱門的電影列表,但不是顯示在所有的頁面上。這個熱門的電影列表是從數(shù)據(jù)庫中隨機的取出來的。你決定要通過用戶控件來實現(xiàn):就叫 FeaturedMovies control (見圖 1).
圖 1 – The Movie Database Application
問題就出現(xiàn)在這里。你需要在程序中給每一個頁面的母版頁傳遞電影分類列表數(shù)據(jù)。你需要給程序中的某些特定的頁面的熱門電影用戶控件傳遞熱門電影列表數(shù)據(jù)。你怎么實現(xiàn)這個呢?
Using a Code-Behind Class
最通常的做法,但是是錯誤的,就是在code-behind class 中為你的MasterPage和FeaturedMovies用戶控件取數(shù)據(jù)來解決這個問題。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來取數(shù)據(jù)的。
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 用戶控件來取數(shù)據(jù)。在FeaturedMovies code-behind class 從數(shù)據(jù)庫中取熱門電影的列表數(shù)據(jù)。
那么,為什么這錯了呢?這當然好像一個簡單的解決辦法。它正常工作了,為什么還要抱怨?
這個解決方案的問題是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.
在開發(fā)ASP.NET MVC應(yīng)用的時候,你應(yīng)該盡量避免在你的程序中在code-behind class 中處理邏輯,嘗試將所有的東西都放回到Controllers中。Controllers被設(shè)計為可測試的。
Using an Action Filter
所以讓我們以另一種途徑來解決這個傳遞數(shù)據(jù)給MasterPage或者view的問題。在這一節(jié),我們創(chuàng)建一個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會附加一些數(shù)據(jù)到view data中去。例如,有可以使用[Partial] 特性來添加電影分類列表的數(shù)據(jù)到view data中去以便在master page中顯示。你也可以使用[Partial] 特性來添加熱門電影列表到view data 中以使在FeaturedMovie 用戶控件中得到該數(shù)據(jù)。
[Partial] 特性通過一個類名,實例化這個類,然后執(zhí)行類里面所有的public方法(每一個方法都包含一個ViewDataDictionary參數(shù)),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應(yīng)用到類上,在HomeController里面的每一個action執(zhí)行的時候這個action filter都會執(zhí)行的。在類級別上應(yīng)用[Partial] 特性來為master page提供view data。
類級別上的[Partial]特性添加電影分類列表到view data中。[Partial]執(zhí)行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中傳遞數(shù)據(jù)給母版頁和用戶控件的解決方案錯在哪里呢?這種方法的優(yōu)于前面一種方法的地方在于他將獲取數(shù)據(jù)的邏輯放回到controller中來處理了。ViewData在controller action 調(diào)用的時候會被修改。
無論怎樣,這個解決方案還是挺不錯的。通過使用[Partial] 特性,你可以為view data dictionary 添加更多的數(shù)據(jù)。例如,如果你決定你要添加一個新的用戶控件到某一個頁面,而這個新的用戶控件需要一個不同的數(shù)據(jù)集,你可以很簡單的添加一個新的[Partial] 特性到正確的controller action上來添加新的數(shù)據(jù)到view data dictionary中去。
遺憾的是,只是一點點的遺憾,這個解決方案不是很容易進行單元測試。當你在一個單元測試里面執(zhí)行action方法的時候ActionFilter并不會執(zhí)行。所以,我們需要尋找一個不同的策略來解決這個問題。
Calling Partial Methods Directly
讓我們進入到這個問題的第三個解決方案中。在這一節(jié)中,我們嘗試通過直接在controller中來獲取數(shù)據(jù)然后傳遞給母版頁和用戶控件來解決這個問題。在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有一個構(gòu)造函數(shù)。在構(gòu)造函數(shù)中調(diào)Master.AddViewData() 來改變controller action中返回的view data。這個方法要在母版頁中顯示的view data。
Index()方法也改變了。在Index()方法里面,調(diào)用了Featured.AddViewData() 方法。這個方法為FeaturedMovies 用戶控件添加必需的view data。由于FeaturedMovies 用戶控件只在Index視圖中呈現(xiàn),而不在Categorys視圖中呈現(xiàn),所以不在構(gòu)造函數(shù)中調(diào)用Featured.AddViewData() 方法。
這個解決方案的優(yōu)點是非常容易進行單元測試。當你調(diào)用Index()方法,view data同時被Master和Featured的部分方法改變了。也就是說,你可以容易的測試你傳遞給母版頁和用戶控件的view data是否是正確的。
那么,這個解決方案錯在哪里了呢?添加view data的所有邏輯都已經(jīng)放到controller類中來處理了。這個解決方案已經(jīng)比前面的兩個方案要好很多了。這個解決方法的唯一的問題是它違背了單一責任原則。
根據(jù)單一責任原則,代碼應(yīng)該只有一個單一的理由去改變(code should have only a single reason to change)。然而,我們有很多原因要去改變Listing 8 中的Index()方法。如果我們也決定添加一個新的用戶控件到Index視圖中,而這個新的用戶控件顯示一個新的數(shù)據(jù)集,然后我們就需要去修改Index() action了。
單一責任制原則背后的目的是你永遠不要去改變已經(jīng)在運作中的代碼。改變代碼通常意味著為你的應(yīng)用帶入一個bug。我們需要尋找一些途徑來添加新的view data而不用修改我們的controller action。
Using Abstract Base Classes
這里是我對于這個問題的最后一個解決方案:我們將使用抽象的基類來改變從controller action返回來的view data。我現(xiàn)在要警告你這個是復(fù)雜的。我們需要創(chuàng)建好幾個類。然而,每一個類都是單一職責的。每一個類都只是負責一種類型的view data 而已(見圖2)。
Figure 2 – Class Hierarchy
我們將創(chuàng)建一個抽象基類,命名為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);}} }下一步,我們將要創(chuàng)建一個命名為HomeControllerBase 的抽象基類(見Listing 8).這個類包含了通常出現(xiàn)在HomeController的所有的應(yīng)用邏輯。我們將會重寫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);}} }對于每一個用戶控件,我們將會需要創(chuàng)建一個額外的抽象類。對于FeaturedMovies 用戶控件,我們將會創(chuàng)建一個HomeControllerFeatured 類(見Listing 9)。對于PopularMovies 用戶控件,我們將會創(chuàng)建一個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;}} }最后,我們需要添加這個層次關(guān)系的最上面一個類。我們將會創(chuàng)建一個HomeController 類。這個類簡單的繼承自上面的其中一個基類(見Listing 11)。他本身并不包含應(yīng)用邏輯。
HomeController 類這個層次關(guān)系中的唯一一個不是抽象類的。由于它不是抽象類,他的controller actions 可以被全世界調(diào)用(its controller actions can be invoked by the world)。
Listing 11 – HomeController.cs
namespace Solution4.Controllers.Home {public class HomeController : HomeControllerPopular{} }現(xiàn)在,你或許會受不了這么多的類。然而,這個解決方案的優(yōu)點是我們已經(jīng)很干凈的分離了建造view data 的邏輯。每一個抽象類都具有單一的責任。我們的代碼不再脆弱。
Summary
我并完全信服我自己的Tip。我仍然在嘗試這通過使用action filter 來為我的master pages 和 user controls 添加view data。描述的最后一個解決,使用抽象基類,好像需要大量工作。我很好奇于這個問題的其他的解決方案。
轉(zhuǎn)載于:https://www.cnblogs.com/QLeelulu/archive/2008/08/17/1269599.html
總結(jié)
以上是生活随笔為你收集整理的ASP.NET MVC Tip #31: 给 Master Pages 和 User Controls 传递数据的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Bind和Eval地区别详细解说
- 下一篇: WMS Schema