直接通过User Control生成HTML-asp.net页面的换皮肤方案

前些日子看了园友Jeffrey Zhao的关于User Control生成HTML的两篇文章. 因为我不喜欢看到我们的工程中有比较多的ashx文件(同时对于IHttpHandler接口,我的意见是尽量尝试不用IHttpHandler),就琢磨了一下如何不用这个ashx和IHttpHandler也能做到同样的功能,有点发现.在这里提出另外一个方法,和大家分享.核心就是覆盖Page类的Render方法.

先来看我的一个用来做皮肤的HTML文件HTMLPage1.htm:

<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"  >
< HTML >
    
< HEAD >
        
< TITLE ></ TITLE >
        
< META  NAME ="GENERATOR"  Content ="Microsoft Visual Studio 7.0" >
    
</ HEAD >
    
< BODY >
        
< P >< FONT  face ="宋体" > test </ FONT ></ P >
        
< P >< FONT  face ="宋体" > testee </ FONT ></ P >
        
<!-- $MyUserControl1$ -->
        
< P >< FONT  face ="宋体" > teest </ FONT ></ P >
        
< P >< FONT  face ="宋体" > Tttttest </ FONT ></ P >
        
<!-- $MyUserControl2$ -->
        
< P >< FONT  face ="宋体" > Ttttessssttt </ FONT ></ P >
        
< P > &nbsp; </ P >
    
</ BODY >
</ HTML >

 

这个HTML文件十分简单.当然实际中你可以做得很复杂,做试验就简单点.只要原理通就可以了. 其中有两个特别得地方:<!--$MyUserControl1$-->是用来标记在此处要嵌入一个叫MyUserControl1的UserControl, <!--$MyUserControl2$-->也类似.就是说这个HTML文件有两块地方的内容要由两个UserControl来提供.这是一个特殊的HTML文件,需要一个特殊的呈现器来将其解释并呈现出来.我们来看这个特殊的呈现器:一个覆盖了Render方法的aspx页面.

WebForm1.aspx:

<% @ Page language = " c# "  Codebehind = " WebForm1.aspx.cs "  AutoEventWireup = " True "  EnableEventValidation = " false "  Inherits = " WebApplication1.WebForm1 "  validaterequest = " false "   %>
<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"  >
< HTML >
    
< HEAD >
        
< title > WebForm1 </ title >
        
< meta  name ="GENERATOR"  Content ="Microsoft Visual Studio 7.0" >
        
< meta  name ="CODE_LANGUAGE"  Content ="C#" >
        
< meta  name ="vs_defaultClientScript"  content ="JavaScript" >
        
< meta  name ="vs_targetSchema"  content ="http://schemas.microsoft.com/intellisense/ie5" >
    
</ HEAD >
    
< body >
        
< form  id ="Form1"  method ="post"  runat ="server" >
        
</ form >
    
</ body >
</ HTML >

其实就是一个空的aspx页面,有一个服务器端的Form而已. 稍有不同的是要加上: EnableEventValidation="false"和validaterequest="false",原因稍后再说.

再来看这个页面的后端代码WebForm1.aspx.cs:

using  System;
using  System.Collections;
using  System.ComponentModel;
using  System.Data;
using  System.Drawing;
using  System.Text;
using  System.Web;
using  System.Web.SessionState;
using  System.Web.UI;
using  System.Web.UI.WebControls;
using  System.Web.UI.HtmlControls;

namespace  WebApplication1
{
    
///   <summary>
    
///  Summary description for WebForm1.
    
///   </summary>
     public   partial   class  WebForm1 : System.Web.UI.Page
    {

        
protected   void  Page_Load( object  sender, System.EventArgs e)
        {
            MyUserControl1 ctl1;
            MyUserControl2 ctl2;
            ctl1 
=  (MyUserControl1) this .Page.LoadControl( " MyUserControl1.ascx " );
            ctl2 
=  (MyUserControl2) this .Page.LoadControl( " MyUserControl2.ascx " );
            
this .Page.Controls.Add(ctl1);
            
this .Page.Controls.Add(ctl2);
        }

        
public   override   void  VerifyRenderingInServerForm(Control control)
        {
            
return ;
        }

        
protected   override   void  Render(HtmlTextWriter writer)
        {
            StringBuilder strBuilder;
            System.IO.StringWriter strWriter;
            System.Web.UI.HtmlTextWriter htmlWriter;
            
string  strResponse;
            
string  strControl;

            System.IO.FileStream fileStream 
=  System.IO.File.OpenRead(Server.MapPath( " HTMLPage1.htm " ));
            System.IO.TextReader rd 
=   new  System.IO.StreamReader(fileStream);
            strResponse 
=  rd.ReadToEnd();


            strBuilder 
=   new  StringBuilder();
            strWriter 
=   new  System.IO.StringWriter(strBuilder);
            htmlWriter 
=   new  System.Web.UI.HtmlTextWriter(strWriter);
            
this .Controls[ 1 ].RenderControl(htmlWriter);
            strControl 
=  strBuilder.ToString();
            strControl 
=  strControl.Replace( " </form> " string .Empty);
            strResponse 
=  strResponse.Replace( " <BODY> " " <BODY> "   +  strControl);
            strResponse 
=  strResponse.Replace( " </BODY> " " </FORM></BODY> " );

            strBuilder 
=   new  StringBuilder();
            strWriter 
=   new  System.IO.StringWriter(strBuilder);
            htmlWriter 
=   new  System.Web.UI.HtmlTextWriter(strWriter);
            
this .Controls[ 3 ].RenderControl(htmlWriter);
            strControl 
=  strBuilder.ToString();
            strResponse 
=  strResponse.Replace( " <!--$MyUserControl1$--> " , strControl);

            strBuilder 
=   new  StringBuilder();
            strWriter 
=   new  System.IO.StringWriter(strBuilder);
            htmlWriter 
=   new  System.Web.UI.HtmlTextWriter(strWriter);
            
this .Controls[ 4 ].RenderControl(htmlWriter);
            strControl 
=  strBuilder.ToString();
            strResponse 
=  strResponse.Replace( " <!--$MyUserControl2$--> " , strControl);

            writer.Write(strResponse);
        }

        
#region  Web Form Designer generated code
        
override   protected   void  OnInit(EventArgs e)
        {
            
//
            
//  CODEGEN: This call is required by the ASP.NET Web Form Designer.
            
//
            InitializeComponent();
            
base .OnInit(e);
        }

        
///   <summary>
        
///  Required method for Designer support - do not modify
        
///  the contents of this method with the code editor.
        
///   </summary>
         private   void  InitializeComponent()
        {
        }
        
#endregion
    }
}

 

解释:

1. Page_Load中须将要呈现的两个UserControl装入,并且不管是Get还是Post都要执行.只有这样才能让这两个UserControl经历Page请求的生命周期,同时让asp.net维持这两个UserControl的ViewState和客户端的事件响应代码.

2.覆盖VerifyRenderingInServerForm方法是为了让asp.net引擎不产生一些诸如UserControl中的服务器端TextBox控件需要在一个服务器端的Form中运行的错误信息.这样在UserControl中放置任何一个服务器端控件都可以了.

3.覆盖默认的Render方法. 这个自定义的Render方法先读入我的Html皮肤文件. 然后构造了htmlwriter, 用于保存form的html输出. 我在WebForm1.aspx里面定义的一个空的服务器端Form, 用RenderControl方法来取得其HTML输出.注意,因为这个Form经历了Page请求的生命周期,所以这个Form的HTML输出都带有ViewState和客户段PostBack响应的javascript代码. 请特别注意这几行代码:

            strControl = strControl.Replace("</form>", string.Empty);     //去掉form的HTML内容最后的</form>,只留下前面的<form>....
            strResponse = strResponse.Replace("<BODY>", "<BODY>" + strControl);    //asp.net form和HTML皮肤文件进行结合
            strResponse = strResponse.Replace("</BODY>", "</FORM></BODY>");       //asp.net form和HTML皮肤文件进行结合

    我们已经得到Form的HTML输出,其内容是这样的: <form>..........</form>. <form>标签在两头. 因为要把这个Form的内容和HTML皮肤的内容相结合.必须在皮肤HTML中的<BODY>后面加上<form>的内容, 同时在皮肤HTML中的</BODY>之前加上</form>. 这样就成了一个asp.net的form.  有读者可能会问,能不能在此页面中做多个服务器段的Form, 回答是不行. 原因是asp.net的引擎从1.0开始就不支持一个aspx页面中有多一个的服务器段form.据我所知这一点一直没有改变.

    Form的HTML内容已经处理了, 两个UserControl也类似, 用RenderControl方法取到其HTML内容, 再用String的Replace方法将我们的特殊标记替换成UserControl的HTML内容. 注意,因为这两个UserControl经历了Page请求的生命周期, 所以其HTML就带有相应的客户端javascipt PostBack方法, 能响应如Click等等事件.

4. 关于WebForm1.aspx里面的EnableEventValidation="false"和validaterequest="false", 因为asp.net服务器在处理Post过来的请求时要验证其请求是否原始的Page产生,如果我们不加两个false, 当我们点击UserControl上的按钮进行PostBack时, asp.net服务器会保错. 因为这个改造的Page已经不是我们原始的那个Page. 

关于通用化的考虑:

1.大凡采用皮肤的asp.net应用一般不会只有一套皮肤,这些皮肤HTML可以存在数据库中. 在用的时候不必象这里采用的方式从一个HTML文件中取出来,而可以采用从数据库中取出.

2.针对不同页面内容,HTML皮肤可以引入不同的UserControl. 这就要求特殊的呈现器在Page_load中解析HTML皮肤,根据HTML皮肤的指定,来装入相应的UserControl.

3. 如何向UserControl传递参数? 因为有些UserControl需要一些参数才能显示相应的内容, 我想在UserControl的代码中完全可以访问Request.Form或者Request.QueryString来取得所需要的参数.

关于内存开销,性能的问题

目前这样的样例代码还没有考虑太多这方面的问题. 我认为可能的问题包括:

如果一个HTML皮肤引入较多的UserControl时,会有性能上的问题.

在这里的String.Replace比较多.内存开销可能会比较大.

水平有限,欢迎指正.

 

 另注: 此方法在asp.net 1.1/2.0/3.0下都可行

 

对博客园的话:不知道怎么回事,居然点一下发出两篇Post了.有意思.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值