直接通過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="宋體">testFONT></P>
       
<P><FONT face="宋體">testeeFONT></P>
        <!--$MyUserControl1$-->

       
<P><FONT face="宋體">teestFONT></P>
       
<P><FONT face="宋體">TttttestFONT></P>
        <!--$MyUserControl2$-->

       
<P><FONT face="宋體">TtttesssstttFONT></P>
       
<P></ P>
    </
BODY>
</HTML>

 

這個HTML文件十分簡單.當然實際中你可以做得很複雜,做試驗就簡單點.只要原理通就可以了. 其中有兩個特別得地方:是用來標記在此處要嵌入一個叫MyUserControl1的UserControl, 也類似.就是說這個HTML文件有兩塊地方的內容要由兩個UserControl來提供.這是一個特殊的HTML文件,需要一個特殊的呈現器來將其解釋並呈現出來.我們來看這個特殊的呈現器:一個覆蓋了Render方法的aspx頁面.

WebForm1.aspx:


DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
   
<HEAD>
       
<title>WebForm1title>
       
<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 description for WebForm1.
   
/// 
    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("", 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("", 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);
        }

       
/// 
       
/// Required method for Designer support - do not modify
       
/// the contents of this method with the code editor.
       
/// 
        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>.... 
            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下都可行

發佈了34 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章