有時候想做簡單的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的文章.