原文地址:http://www.asp.net/learn/mvc/tutorial-13-cs.aspx
这篇教程的目的是解释你可以怎样将数据从一个控制器传递到一个视图母版页。我们测试了两种传递数据到母版页的策略。首先,我们讨论了一个容易的方 案,结果导致了一个难以维护的应用。接下来,我们测试了一个比较好的解决方案。它需要多一点的早期工作,但是会产生更容易点维护的应用。
传递数据到母版页
这篇教程的目的是解释你可以怎样将数据从一个控制器传递到一个视图母版页。我们测试了两种传递数据到母版页的策略。首先,我们讨论了一个容易的方 案,但这个方案导致应用难以维护。接下来,我们测试了一个比较好的解决方案。它需要多一点的早期工作,但是会产生更容易点维护的应用。
问题
想象下你正在创建一个电影数据库应用,然后你想要在应用的每一页都显示一个电影分类列表(如图1)。此外,这个电影分类表存储在数据库表里。因此,从数据库检索这些分类然后在一个视图母版页面里显示这些电影分类显然很有意义。
图1:在视图母版页显示电影分类
问题出来了。在视图母版页里你怎么检索电影分类列表?直接在母版页调用模型类的方法显然很诱人。换句话说,在你的模板页里检索数据库的数据是很诱人的。然而,绕过你的MVC控制器去获得接触数据库会违反干净的关注点分离--这个MVC应用最主要的优点之一。
在一个MVC应用里,你会想要所有的MVC视图和MVC模型的交换都由MVC控制器来处理。这种关注点的分离意味着一个更可维护的、适应性更强并且测试性更强的应用。
在一个MVC应用,所有的数据传递到一个视图--包括视图母版页--都应该由控制器行为传递到视图。此外,数据应该利用视图数据的优势来传递。在这篇教程的其余部分,我测试了传递数据到视图母版页的两种方法。
简单的解决方案
让我们从一个简单的解决方案开始。这个简单的解决方案是在每一个控制器行为里传递视图数据到母版页。
考虑下清单1的控制器。它暴露了名叫Index()和Detials() 的两个行为。Index()行为方法返回了Movies数据库表的所有电影。Details()行为方法返回了特定的电影分类的所有电影。
清单1 - Controllers\HomeController.cs
using System.Linq;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
[HandleError]
public class HomeController : Controller
{
private MovieDataContext _dataContext = new MovieDataContext();
/// <summary>
/// Show list of all movies
/// </summary>
public ActionResult Index()
{
ViewData["categories"] = from c in _dataContext.MovieCategories
select c;
ViewData["movies"] = from m in _dataContext.Movies
select m;
return View();
}
/// <summary>
/// Show list of movies in a category
/// </summary>
public ActionResult Details(int id)
{
ViewData["categories"] = from c in _dataContext.MovieCategories
select c;
ViewData["movies"] = from m in _dataContext.Movies
where m.CategoryId == id
select m;
return View();
}
}
}
注意到Index()和Details()行为都添加了两个项到视图数据。Index()行为添加了两个键:categories和movies。Categories键。Movies键代表着由Index视图也显示的电影列表。
Details()行为同时也添加了两个名叫categories和movies的键。Categories键,再一次,代表着由视图母版页显示的电影分类。Movies键代表着由Details视图页面显示的特定的分类(如图2)。
图2:Details视图
Index视图被包含在清单2.它简单地迭代了在视图数据里的由movies项表示的电影列表。
清单2 - Views\Home\Index.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %>
<%@ Import Namespace="MvcApplication1.Models" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<ul>
<% foreach (var m in (IEnumerable<Movie>)ViewData["movies"])
{ %>
<li><%= m.Title %></li>
<% } %>
</ul>
</asp:Content>
视图母版页包含在清单3.视图母版页迭代并且渲染了从视图数据获得的categories项表示的所有电影分类。
清单3 - Views\Shared\Site.master
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.Master.cs" Inherits="MvcApplication1.Views.Shared.Site" %>
<%@ Import Namespace="MvcApplication1.Models" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title></title>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<div>
<h1>My Movie Website</h1>
<% foreach (var c in (IEnumerable<MovieCategory>)ViewData["categories"])
{%>
<%= Html.ActionLink(c.Name, "Details", new {id=c.Id} ) %>
<% } %>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</body>
</html>
所有的数据通过视图数据被传递到视图和视图母版页。这是传递数据到母版页的正确方式。
那么,这种解决方案的问题在哪里呢?问题在于这个方案违反了DRY(Don't Repeat Yourself)原则。每一个控制器行为必须添加同样的电影分类列表到视图数据。应用里有重复的代码会让你的应用维护难度更大、适应性更差并且修改也更困难。
好的解决方案
在这节,我们会测试一个替代的并且更好的解决方案来实现从控制器行为传递数据到视图母版页。与在每一个控制器行为添加电影分类相反,我们只添加电影分类到视图数据一次。所有的被视图母版页使用的视图数据都添加到一个应用控制器。
ApplicationController类包含在清单4.
清单4 - Controllers\ApplicationController.cs
using System.Linq;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public abstract class ApplicationController : Controller
{
private MovieDataContext _dataContext = new MovieDataContext();
public MovieDataContext DataContext
{
get { return _dataContext; }
}
public ApplicationController()
{
ViewData["categories"] = from c in DataContext.MovieCategories
select c;
}
}
}
清单4里面的ApplicationController三个地方你需要注意。首先,该类继承自System.Web.Mvc.Controller基类。应用控制器是一个控制器类。
第二,ApplicationController类是一个抽象类。一个抽象类是一个具体类必须实现的类。由于 ApplicationController是一个抽象类,你不能调用该类里面的任何方法。如果你想直接地调用 ApplicationController类,那么你会得到一个资源无法被找到(Resource Cannot Be Found)的错误信息。
第三,ApplicationController包含了一个添加电影分类到视图数据的构造函数。每一个继承自 ApplicationController类的控制器类会自动地调用ApplicationController类的构造函数。无论你何时调用继承自 ApplicationController类的控制器的何种行为,电影分类都会被自动地包含在视图数据里。
清单5的Movies控制器继承自ApplicationController。
清单5 - Controllers\MoviesController.cs
using System.Linq;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class MoviesController : ApplicationController
{
/// <summary>
/// Show list of all movies
/// </summary>
public ActionResult Index()
{
ViewData["movies"] = from m in DataContext.Movies
select m;
return View();
}
/// <summary>
/// Show list of movies in a category
/// </summary>
public ActionResult Details(int id)
{
ViewData["movies"] = from m in DataContext.Movies
where m.CategoryId == id
select m;
return View();
}
}
}
Movies控制器,就如上一节讨论过的Home控制器,暴露了名叫Index()和Detials()的两个行为方法。注意到母版页显示的电影分 类列表既不是由Index()函数也不是由Details()函数添加到。由于Movies控制器继承自ApplicationController,电 影分类列表被自动地添加到视图数据。
注意到这个添加视图数据到一个视图母版页的解决方案并没有违法DRY原则。添加电影分类列表到视图数据的代码只存在在一个地方:ApplicationController的构造函数。
总结
在这篇教程,我们讨论了两种从控制器传递视图数据到视图母版页的方法。首先,我们测试了一个简单,但难以维护的方法。在第一节,我们讨论了怎么样在每一个控制器行为添加视图数据到视图母版页。由于它违反了DIY原则,我们以它不是好的解决方案而结束了讨论。
接下来,我们测试了一个比较好的策略。与添加视图数据到每一个控制器行为不同,我们只在ApplicationController添加了视图数据。用这种方式的话,你可以避免代码重复。
摘自 http://hi.baidu.com/aliasmic/blog/item/9bb4c3b5dc5d93798ad4b2ef.html
注意 母版页需将值传递给分部视图
<div id="templatemo_header_wrapper">
<% Html.RenderPartial("Menu", ViewData["categories"] as List<Efied.Models.Category>); %>
</div>
然后在分部视图相应设置继承的强类型为对应的类型即可
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Efied.Models.Category>>" %>
此处我在母版页中动态生成一个多级下拉菜单,这样就只需母版页中进行一次绑定操作。
大家有问题可以问我,我会尽量回答。