把使用者定義元件用程式把HTML呈現字串輸出

  • 2327
  • 0

有時候想做簡單的Ajax顯示Readonly資料,但自己Parse Html code也太麻煩又不好用.既保留VS的設計介面和功能,又能輸出成Html的方法.

這功能,在有UpdatePanel和Web Service之後,好像沒有甚麼存在價值,但我就是遇到了,很巧合地Rick Strahl和Scott Guthrie也遇到這樣的問題,在一輪試驗之後,來這裡分享一下成果.

這個功能的目標是這樣的,用一個AJAX Call去取得HTML,再呈現在介面上,不包含任何控制,也就是AJAX的最基本,Rick Strahl的解法更進一步去做到Postback Controls,而且要用到他的library,所以我在這裡是用Scott Guthrie的方法.

首先,把你想要呈現的資料片段,設計一個使用者自定元件(.asmx),裡面可以放任何元任, Label, GridView等都可以,還可以設定GridView的DataBind.設計好之後要動點小手腳,在code-behind裡把要傳到這個元件的資料,例如GridView的DataSource,定義一個public變數,例如

public partial class MyUserControl : System.Web.UI.UserControl
        {
        public object Data;
        public string LabelText;

        protected void Page_Load(object sender, EventArgs e)
        {
            lbl_Name.Text = LabelText;
            gdv_data.DataSource = Data;
            gdv_data.DataBind();
        }
}

然後就要建立一個方法來服務AJAX Call,可以是Web Service, 頁面, 泛型處理常式等,我這裡是用處理常式.

在.ashx裡面,實作處理需求的方法

//return the html of user control
        public void ProcessRequest(HttpContext context)
        {
            string l_response;
            //get your data source data to l_datasource
            if (l_datasource.Length > 0)
            {
                //extend from Page Class
                RenderPage l_page = new RenderPage();
                //pass data to user control as a dictionary
                Dictionary<string, object> l_data = new Dictionary<string, object>(2);
                l_data.Add("Data", l_datasource);
                l_data.Add("LabelText", "the name");
                //Method that load user control and pass data to it                   
                l_response = ViewManager.RenderView(@"~/WebService/MonitorInfoWindow.ascx", l_data);
            }
            else
            {
                l_response = "No Data";
            }

            context.Response.ContentType = "text/plain";
            //write out the rendered control HTML text
            context.Response.Write(l_response);
        }    

把你要傳進Usre Control的資料物件存到一個Dictionary裡面,Key是在User Control裡對應的變數名稱.

然後用RenderView()這個方法來取得Render後的字串,方法實作如下

public static string RenderView(string path, Dictionary<string,object> data)
    {
        RenderPage pageHolder = new RenderPage();
        UserControl viewControl = (UserControl)pageHolder.LoadControl(path);

        //should use a dictionary to set data to multiple fields
        if (data != null)
        {
            foreach (KeyValuePair<string, object> pair in data)
            {
                Type viewControlType = viewControl.GetType();
                FieldInfo field = viewControlType.GetField(pair.Key);

                if (field != null)
                {
                    field.SetValue(viewControl, pair.Value);
                }
                else
                {
                    throw new Exception(string.Format("The Control {0} do not have a public {1} property", path, pair.Key));
                }
            }
        }

        pageHolder.Controls.Add(viewControl);

        StringWriter output = new StringWriter();
        HttpContext.Current.Server.Execute(pageHolder, output, false);

        return output.ToString();
    }

其實是用reflection把傳進來的值設定好,再用Server.Execute去執行Page物件,這樣就會像真實的頁面一樣

去做Init, Load, Binding等動作,最後輸出成元作的HTML.

這裡有一個地方要注意,就是為甚麼要用繼承的Page而不用原本的Page.

因為Page物件會去檢查元件是否在伺服器執行,用這種虛擬讀進來的Control在檢查時會失敗

所以我們覆寫那個檢查方法來跳過檢查.

public class RenderPage : Page
{
    public override void VerifyRenderingInServerForm(Control control)
    {
        return;
    }
}

這樣就大功告成了,記得這方法只適合用在Readonly的元作,如果要實作有Postback功能的請參考Rick的文章.

My WP Blog with english technical docs.