十三、向母版页中传递数据
(原创:灰灰虫的家http://hi.baidu.com/grayworm)
这篇文章我们主要来介绍如何从控制器向母版页中传递数据,在这里我们来试验一下两种向母版页视图中传递数据的方法。
一种方法是操作简单但难以维护,另一种方法比较不错,它需要我们做一些初始化的工作,但程序的可维性更强。
一、问题提出
假设我们程序需要在每个页面中显示电影分类列表,如下图所示。如果我们的电影分类信息存储在数据库中,那如何把数据库中的电影分类信息显示在页面上呢?
《图1》
关于这个问题,我们可以直接在母版面中访问模型层的代码。换句话说,就是直接在母版页中包含数据库的访问代码,但这样会破坏MVC模式给我们带来分层清晰的优势。
因为在MVC程序中,我们的视图层和模型层并不直接交互,而是被控制层分开,由控制层协调视图层与模型层的交互,这样编写的代码具有较好的可维护性、可扩展性和可测试性。
在MVC模式中,所有的视图数据都是由控制器动作通过ViewData传递来的,在这里我们来试验两种向母版页中传递数据的方法
二、简单的方法(原创:灰灰虫的家http://hi.baidu.com/grayworm)
最简单的方法就是,在每个控制动作中都返回母版页中所需要的数据。
看下面的代码,在这个控制器中有两个动作Index()和Details()。Index()动作返回Movies表中所有的电影记录,而Details()动作则返回Movies表中每个分类下的所有电影记录。
Listing 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()两个动作中都向ViewData中添加了两项数据。
在Index()动作中添加的两项数据是:categories和movies。categories 用来保存向母版面中传递的电影分类信息。movies 用来保存在Index页面中传递的所有电影列表信息。
在Detailis()动作中也包含了两项数据:categories和movies,categories 仍是用来保存向母版面中传递的电影分类信息。movies 用来保存在Index页面中传递的当前分类中电影列表信息。
《图2》
Index视图的代码如下所示,它主要是迭代显示ViewData中电影列表所有的项。
Listing 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>
母版页中的代码如下所示,母版页中迭代显示ViewData中传递来的所有的电影分类。
Listing 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>
通过上面两段代码,我们可以看到我们是通过ViewData向内容页面和母版页传递数据的。这种向母版页传递数据的方式是正确的。
这种解决方案有什么问题呢?这种做法违反了DRY(Don't Repeat Yourself)原则,每个控制器都需要添加相同的代码,以向客户端传递电影分类信息。重复的代码使程序的难以维护。
三、好的解决方案(原创:灰灰虫的家http://hi.baidu.com/grayworm)
在这一部分中我们来看看另一种较好的向母版页传递数据的解决方案。这种方案不再是在每个控制器动作中都向母版页传递数据,而是单独添加一个控制器ApplicationController,用它向母版页视图传递ViewData。
ApplicationController的代码如下:
Listing 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;
}
}
}
上面的代码中有三个地方需要引起我们的注意:
1、ApplicationController派生自System.Web.Mvc.Controller,即ApplicationController就是一个控制器类
2、该类是个抽象类。抽象类必须要被其它类继承,所以我们没法直接触发控制器中的方法
3、在ApplicationController的构造函数中,把电影分类的信息添加到ViewData中。程序中所有的控制器都是从ApplicationController中派生的,因此当任何控制器都会自动调用ApplicationController类的构造函数,这样就会自动把电影分类信息加载到各控制器的ViewData中去了。
MoviesController派生自ApplicationController,代码如下所示:
Listing 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();
}
}
}
这里的MoviesController与我们前一部份讨论的HomeController很相似,都有Index()和Details()两个动作。但这两个动作方法中没有向ViewData中添加母版页中需要的电影分类信息,因为MoviesController派生自ApplicationController,电影列表信息会通过ApplicationController的构造函数自动添加到ViewData中去。
这种做法并没有违返DRY(Don't Repeat Yourself)原则,向ViewData中添加电影分类信息的代码只出现在ApplicationController的构造函数据。
总结
在这篇文章中,我们讨论了两种从控制器向母版页视图中传递ViewData的方法。
第一种方法很简单,但使代码难以维护。在这种方法中,我们学习了如何从每个控制器向母版页传递数据。最后我们得出结论这种方法不是一个好的解决方案。
第二种方法比较好,它不是在每个控制器中编写向母版页中传递电影分类的代码,我们把这块重复使用的代码放在所有控制器的父类ApplicationController中,这样我们就避免了代码的重复编写。