春鱼·编程观点

技术在进步, 世界在变得美好...

导航

重新画差不多一摸一样的UI? - ASCX的MVC模式实现

[摘要]
本文论述了如何改善设计,使表层的结构更灵活。

[引言]
前几天, 我在与一个朋友, 他同时是我的技术经理,谈到我所设计的ASCX基本实现了MVC的时候, 他好像显得特别不以为然。他说ASP.NET本来就是MVC分开的。ASPX就是V,而CODE BEHIND就是C,数据库就是M。我想他没有真正理解我的意思。

确切地说我也不清楚自己到底有没有正确理解MVC。我想如果要像理解数学或者几何原理一样理解也不是必要的。能够解决实际问题的、灵活的、易于维护的设计就是好的设计。

[实例]
为了更明白地说明问题,或者为了减小篇幅,我引入一个实际应用中非常普遍的需求:管理会员。每个会员到系统中注册,我们想尽可能知道他的资料。我们需要一个模块来管理会员的基本资料,诸如姓名,年龄,个人爱好,联系办法种种。我们需要收集这些资料,管理员需要查询这些资料,用户自己也许会修改这些资料。这其中必须我们提供数个界面来协助用户操作。

V
这里的V是ASCX构图。大家都知道ASCX即可以显示HTML,又可以直接查询数据库,又可以做计算。可以包容任何服务器端控件。但是好的设计的原则是“高内聚,低耦合”,ASCX归根到底是一个类。我们不能设计得让它无所不能,或者说让它知道我们太多业务上的逻辑。比如数据库是如何设计的, 底层类都有那些方法。一种对象,不管其内部逻辑多复杂,其公开的特性或者方法越少越好,越常规越好。那么我们给ASCX都设计什么功能呢?

这里,我们将ASCX看作消极的显示数据的”白板“。仅仅被动到通过控制器设置其属性呈现数据。而有关数据的处理一概不管。当然,我们的白板也不见得就是白色的。在格式上还是允许它尽可能玩出花样。对于我们的实例,我们设计了一种ASCX来呈现会员基本资料。MemberInformation.ascx(MemberInformation类)。我们需要给类MemberInformation公开一系列特性。比如我们要求白板上显示姓名,出生日期,电话,电子邮件...,我们就需要给类MemberInformation公开Name, BirthDate, PhoneNumber, EmailAddress等特性。在显示构图方面,我们需要以集中模式将数据呈现出来
1.查询时只读显示
2.新会员登记填空表单
3.会员更改个人资料时的表单
至少会有以上几种情况。这些视图,显示的数据是完全一样的,而表现模式又大大不同。如果我们直接把上述界面制作在ASPX页上,我们不得不设置显示单条数据个一个个服务器对象的值。还有复杂的有效性验证。

将以上视图合一。
既然数据一致,并且多处使用,就有封装的必要。我们将上述视图总结一下,不外乎:以HTML表单形式展示数据,以报表的方式展示(只读)。不论是新登记还是老会员更改资料。我们必须是ASCX这个V层可以以两种方式展现相同的数据方案。

两种表现模式
我们需要以HTML ATTRIBUTE或者编程的方式控制ASCX的表现模式。我们给MemberInformation类设置一个属性:public string PresentationMode.之所以是串类型是因为需要在HTML ATTRIBTE中设置。可选的值有Form,(可输入数据的表单模式),Report模式(只读的报表模式)。这样我们可以像如下的方法控制其表现模式:
<Xxx:MemberInformation ID=MemberInformation1 Runat=Server PresentationMode=Form />
也可以在编程时像如下方法:
this.MemberInformation1.PresentationMode = “Form“;

设置FORM值时,ASCX显示出来就是表单样式了。

如何让ASCX这么听话呢
在ASCX里放置两个<asp:Panel />一个内装一个HTML table, 按照既定的格式排列各个服务器control. 另一个装安排好的form elements. 呈现时(一般是Page_Load控制)检查自己的PresentationMode值,如果是form就显示第一个panel, 隐藏第二个。如果设置为Report则相反。这样看起来ASCX就可以切换界面了。
代码范例(实际项目摘取)
  /// <summary>
  /// 关键特性: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
  /// Form: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  /// Report: xxxxxxxxxxxxxxxxxxxxxxxx
  /// 缺省值: Form
  /// </summary>
  private string representationMode = "Form";
  public string RepresentationMode
  {
   get
   {
    return this.representationMode;
   }
   set
   {
    this.representationMode = value;
    if(value == "Form")
    {
     this.FormPanel.Visible = true;
     this.ReportPanel.Visible = false;
    }
    else
    {
     this.FormPanel.Visible = false;
     this.ReportPanel.Visible = true;
    }
   }
  }


与公开的特性连接
公开的特性是其控制器访问或设置视图的数据的接口。一般通过编程进行设置。既然是公开的特性就必须有get,set访问器。这些特性的访问器必须即可以设置到report的数据,又可以访问到form的数据。总之,特性访问器一般必须很清楚地知道管姓名的是哪两个,管年龄的又是哪两个。这样,我们只需设定MemberInformation1.Name = “Jay Xu“, MemberInformation1.Age = 23就可以了。 而不用管显示姓名的控件被安排在什么地方,名称或ID是什么。

特性有关代码范例(实际项目摘取)
  /// <summary>
  /// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  /// xxxxxxxxxxxxxxxxxxxxxxxx
  /// </summary>
  public string Name
  {
   get
   {
    return this.NameBox.Text;
   }
   set
   {
    this.NameBox.Text = value;
    this.NameLabel.Text = value;
   }
  }


数据模型统一。
这里的M,其实是一系列常规值的组合。例如姓名(字符串),年龄(整型),出生年月日(日期)等。在作者实际的项目中,数据实体是一组派生自DataSet的强类型化了的对象。所以并没有将这些数据打包成一个类。

控制器
控制器是管理ASCX的容器。对MemberInformation的数据进行写入,读出的工作。设计到数据是怎么从数据库到前端来的,还有数据是如何持久化到数据库中去的,都是控制器应该操心的事情了。我们的V只管显示数据,还有听从安排切换模式。

[进一步增强]
在get, set访问器中进行有效性校验。例如日期的有效性。可以在get访问过程中进行计算。如果出错,可以选择抛出异常,或者以某种形式通知用户。这些逻辑都应该隐藏在ASCX。
为适应不同场合需求,可以给ASCX增加多个用于控制样式的特性。例如背景色,字体,等等。

[需要注意的问题]
如果某些值是通过在有限项中选择得到的, 则应通过一定途径给选择控件赋初值。如果某些值是多项选择的,则应以合适的数据类型表现其值,比如数组。
在使用编程的方式切换显示模式时注意应使控件的EnableViewState属性为true.(缺省值就是true, 只要你不显示地关闭它)。


[设计思想上的扩充]
可以将这个设计思路应用到其他类型的ASCX上. 比如用于显示List(名单)样式的数据时. 可以把已经设计好的Repeater或者DataGrid包装成一个ASCX.

如果你明白了本文意图, 你可以在你的下一个项目中尝试一下, 编码的工作量可能会稍微增加, 但是除了可以很好的解决当前的问题外, 在一定程度上使设计工作更灵活, 更优雅, 并且带来重用的可能. 也能为将来的项目提供帮助.

posted on 2004-04-08 03:13  春鱼  阅读(2574)  评论(1编辑  收藏  举报