BLUE.NET

-------- 众里寻她千百度

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

ASP.NET 中的设计模式之MVC篇

  • 设计模式
  • MVC
  • 页面控制器
  • 模板与Page基类

设计模式

在这里,笔者无意对设计模式的含义进行过多介绍或者严格定义,只是给一个比较简单的理解:设计模式是对一些经常出现问题的一种解决方式,这种解决方式来自于许多开发人员的经验总结。

MVC—WEB开发中最基本的设计模式

在经典的设计模式书籍中,常用的设计模式有二三十种。就WEB应用程序开发来说,MVC可能是最基本的一种设计模式了。的确,WEB应用程序有一些特有的问题,瘦客户端、不连续的状态、对友善界面的关注,以及愈来愈多的终端设备…。
 MVC将程序功能分成三部分考虑:视图(VIEW,即可见的用户界面部分)、模型(Model,程序的数据模型和业务逻辑部分),控制器(Controller,根据用户输入通知模型和视图进行相应更改)。
 MVC将WEB应用程序的一个页面分成若干部分,当对其中的一部分修改时,另外一部分可能只需要很少的变动甚至保持原样,使得应用程序对需求变化的适应性更好。
 此外,WEB应用程序往往既要实现美观的用户界面,又要实现精确复杂的商务逻辑,然而并不是所有的人都能同时做好这两件事情。有了MVC,开发团队成员间的协作就比较容易了。

ASP.NET中的MVC

ASP.NET对MVC提供了支持。编写代码隐藏的网页时,程序代码被分为两部分:.ASPX页面文件和一个单独的类文件(.CS文件)。
在图1所示的例子中,用户从下拉框选择图书类别,提交后,列出该类别下的书籍清单。

图1  示例

视图

此例子的View部分在.ASPX文件中:
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="TempSite.WebForm1" %>
<HTML>
    <HEAD><title>BookView</title></HEAD>
    <body >
    <form id="Form1" method="post" runat="server">
    <FONT face="宋体"><h4>图书浏览</h4>
    <asp:Label id="Label1" style="TOP: 56px" 
runat="server" Width="80px" Height="24px">选择类别:</asp:Label>
</FONT>
<asp:DropDownList id="bookClassSelect" style="TOP: 56px"    runat="server" Width="192px" Height="24px"></asp:DropDownList>
    <asp:Button id="Button1" style="TOP: 56px" runat="server"
        Width="72px" Text="确定"></asp:Button>
    <br>&nbsp;<br>
        <asp:DataGrid id="bookDataGrid1"
            HeaderStyle-BackColor="#aaaadd" BackColor="#ccccff" 
        style="font-size:8pt;"
        runat="server" Width="75%" CellPadding=3 CellSpacing=0 
BorderColor="#000000"></asp:DataGrid>
    </form>
    </body>
</HTML>

 
例子程序中的Model和Controller部分则在独立的类文件中:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
namespace TempSite{
    public class WebForm1 : System.Web.UI.Page    {
    protected System.Web.UI.WebControls.DropDownList bookClassSelect;
    protected System.Web.UI.WebControls.Button Button1;
    protected System.Web.UI.WebControls.DataGrid bookDataGrid1;
    protected System.Web.UI.WebControls.Label Label1;
    
    private void Page_Load(object sender, System.EventArgs e)
    {
        if(!Page.IsPostBack){
            string strcmd="Select * From BookClass";
            SqlConnection sqlCn=new SqlConnection(
        "server=localhost;database=guanxi;uid=sa;pwd=pass");

            SqlDataAdapter sqlCommand=new
                 SqlDataAdapter(strcmd,sqlCn);
             DataSet ds=new DataSet();
            sqlCommand.Fill(ds,"BookClass");
            
            bookClassSelect.DataSource=ds;
            bookClassSelect.DataTextField="ClassName";
            bookClassSelect.DataValueField="ClassID";
            bookClassSelect.DataBind();}
    }
    Web 窗体设计器生成的代码#region Web 窗体设计器生成的代码
    override protected void OnInit(EventArgs e){
        InitializeComponent();
        base.OnInit(e);    }
    private void InitializeComponent(){    
        this.Button1.Click += new
 System.EventHandler(this.Button1_Click);
        this.Load += new System.EventHandler(this.Page_Load);}
    #endregion

    private void Button1_Click(object sender, System.EventArgs e){
        string cmd=string.Format("SELECT BookID,BookName,[Count] 
FROM BookInfo Where BookClassID={0}"
            ,this.bookClassSelect.SelectedItem.Value);
        SqlConnection sqlCn=new SqlConnection(
"server=localhost;database=guanxi;uid=sa;pwd=pass");
        SqlDataAdapter sqlCommand=new SqlDataAdapter(cmd,sqlCn);
        DataSet ds=new DataSet();
        sqlCommand.Fill(ds,"Book");
        
        bookDataGrid1.DataSource=ds;
        bookDataGrid1.DataBind();}
    }
}

 

分离模型和控制器

代码隐藏文件自动实现的MVC模式中,只是实现了View和Model-Controller的分离,还没有解决代码复用问题。对此的解决方式是,进一步将模型和控制器分离。
将模型分离到单独的类文件中,使其只包含与数据库、业务逻辑相关的代码,其它的页面可很容易使用此代码:
using System;
using System.Data;
using System.Data.SqlClient;
public class DataBaseGateWay{
    public static DataSet GetBookClass(){
        //
    }
    public static DataSet GetBookList(string BookClassID){
            //
    }
}
 
尽管页面中的公共方法也可以在其它页面调用,但不推荐该方式,因为会导致页面间的藕合度增加。
 
控制器部分的代码仍然在代码隐藏文件中,但其逻辑已经非常清晰:
private void Page_Load(object sender, System.EventArgs e){
    if(!Page.IsPostBack){
        DataSet ds=DataBaseGateWay.GetBookClass();

        bookClassSelect.DataSource=ds;
        bookClassSelect.DataTextField="ClassName";
        bookClassSelect.DataValueField="ClassID";
        bookClassSelect.DataBind();}
}
override protected void OnInit(EventArgs e){
    InitializeComponent();
    base.OnInit(e);    }
private void InitializeComponent(){
    this.Button1.Click += new
System.EventHandler(this.Button1_Click);
    this.Load += new System.EventHandler(this.Page_Load);}
private void Button1_Click(object sender, System.EventArgs e){
    DataSet ds=new DataSet();
    sqlCommand.Fill(ds,"Book");
    
    bookDataGrid1.DataSource=ds;
    bookDataGrid1.DataBind();}
}

 

对控制器和视图的进一步重构

B/S结构的应用程序被分成了一个一个的页面。开发过大一些应用程序的人可能都知道,在商业应用程序的不同页面中,常常有许多相同的页面元素需要保持一致,如Banner、菜单、页脚等。此外,在不同的页面上,也有许多非常相似的事情需要处理,它们可能包括:对用户身份和权限的验证、接收用户传递的请求、对错误的处理,甚至可以包括更多(例如对缓存的处理)。显然,这可以为多个页面共享的二者分属于MVC中的不同范畴:界面属于View,而事务的处理则属于控制器。
 
在前面为了实现复用,我们曾经将模型分离到了单独的类文件中实现,而对于可能为多个页面共享的视图和控制器,如果仍然为每一个页面都单独编写一个复杂而臃肿的视图与控制器、或者在页面间复制拷贝、或者互相调用不同页面的成员,也都不是好的选择。

模板技术

在Web开发中,模板技术常常是人们用来解决视图重用的一种较好选择。其基本思想是为多个页面定义统一的外观和布局,然后在每个单独的页面加载过程中,用该页面内容替换或者嵌入到模板内容中。
 
ASP.NET中,也可以使用模板方式。例如,图2所示的页面中,上面和左边的部分都是站点多个页面的公共部分,只有页面中间的部分是每一个页面单独维护的内容。对此,可以采用模板方式,只在一个称为Template的视图文件中编写公共的外观,而不需要在每一个页面都重新把这些代码复制一遍。  
  
    图2  使用模板实现页面的一个简单例子
实现这个例子的模板代码如下:
<%@ Control Language="c#" AutoEventWireup="false" Codebehind="Template1.ascx.cs" Inherits="M161.CS.Web.Award.Template1" TargetSchema="http://schemas.microsoft.com/intellisense/ie5"%>
<%@ Register TagName="AwardHeader" TagPrefix="CC" Src="AwardHeader.ascx" %>
<%@ Register TagName="AwardNav" TagPrefix="CC" Src="AwardNav.ascx" %>
<HTML><HEAD>
    <LINK href="../default.css" type="text/css" rel="stylesheet">
</HEAD>
<body onload="javascript:onload();">
    <form id="Form1" method="post" runat="server">
    <div><CC:AwardHeader runat="server" ID="Header1" /></div>
    <div style="WIDTH:100%">
    <table width="100%" border="0" cellpadding="0" cellspacing="0" id="TableTemplate">
        <tr>
        <td style="width:150px">
        <CC:AwardNav runat="server" ID="Awardnav1" />
        </td>
        <td valign="top" align=left>
    <asp:PlaceHolder id="_placeHolder" runat="server"
 EnableViewState="False" />
        </td>
        </tr>
    </table>
    </div>
    </form>
</body>
</HTML>

 
在这部分代码中,是将页面公共部分做成了控件,然后在模板中注册。尽管在每一个页面中注册一遍控件所需代码也不是很多,但通过模板维护公共部分有一个好处:可以更快速地改变整个应用程序的外观、布局、颜色等,也可以准备多个不同的模板,并为不同的页面选择其合适的模板。

Page基类

要采用模板来实现页面,除了要将模板的内容包含起来,往往还要在页面中替换或者填充模板中的一些内容。这些装载、转换工作既然也是每一个页面都要做的事情,当然也可以将它们放到一个单独的类中实现,这就是Page基类。
Page基类在ASP.NET中的角色是公共的控制器。我们可以将每一个页面都要实现的控制逻辑放到此处。
下面的代码文件是实现图2的Page基类。
using System;
using System.Collections;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace MyNameSpace
{
    /**//// <summary>
    ///系统各页面的BasePage
    /// 作用:通过装载模板为各个页面加载公共布局,包括Header、导航条、页脚
    /// </summary>
    public class MyBasePage:Page
    {
        PlaceHolder _container;
        string      _templatePath = "Template1.ascx";

        public MyBasePage()
        {
            _container = new PlaceHolder();
            _container.ID = "_container";
        }
        
        protected string TemplatePath
        {    //允许指定不同的模板文件
            get { return _templatePath;  }
            set { _templatePath = value; }
        }

        protected override void OnInit(EventArgs e)
        {
            //装载模板文件
            Control template = LoadControl(_templatePath);
            if (template == null)
                throw new Exception("装载模板文件错误!");
            Control placeHolder = 
                template.FindControl("_placeHolder");
            if (placeHolder == null)
                throw new Exception("模板文件格式错误");
            placeHolder.Controls.Add(_container);
            for (int i=0; i<template.Controls.Count; i++)
            {
                Control c = template.Controls[0];
                template.Controls.Remove(c);
                Controls.Add(c);
            }
              base.OnInit(e);
        }
    }
}

  其它页面继承Page基类,而不是继承默认的System.Web.UI.Page类。
  //page1.aspx.cs
public class page1 : MyBasePage
{
    //
  }

<!—page1.aspx
<%@ Page Inherits="page1" CodeBehind="page1.aspx.cs" Language="c#" AutoEventWireup="false" %>
<h5>This is page1</h5>

 
公共的Page基类可以做很多事情,常见的如对错误(Exception)的处理、接收页面传递的字段、URL与字符集转换、增加客户端脚本等。
在性能方面,使用ACT测试的结果表明,使用Page基类和模板技术带来的性能损失非常有限。
最后,顺便提到一下笔者认为非常有意思的一点:在ASP.NET中,无论是模板、Page基类、还是独立的页面,都可以同时具有视图文件(.aspx文件)和代码文件(.cs文件)其中之一或者全部。例如,在微软资助出版的ESP(即.NET企业解决方案模式)书籍中,就提供了另外一种Page基类,该page基类的视图文件就是各个页面的公共视图,也相当于我们这里的模板文件。ASP.NET中这种灵活的机制既为大多数开发人员提供了方便,也同时增加了犯错的可能---尽管你可以用多种方式实现同样的功能,但最佳的方式是什么呢?正如我们已经找到了复用MVC三者中任何一种的方式,但对于MVC究竟是分离还是合并到一起,仍然需要根据应用程序的复杂程度等实际情况做出选择。
-------------------------------------------------------------------------------------------
Created by superhasty
2005/06/07
posted on 2005-06-09 09:07  blue.net  阅读(446)  评论(0)    收藏  举报