上一节用了一个示例说明了Ajax在CS2中的一点简单的应用,这一节里着重探讨一下CS2中的Ajax的高级应用和实现原理,在了解Ajax的实现原理前我觉得有必要先了解一下aspx页面从请求到返回HTML都做了些什么,我想在了解了页面处理机制再来认识Ajax处理原理应该应该会很有帮助的,见下表:
序号
|
阶段
|
页面事件
|
可覆盖的方法
|
1
|
页面初始化
|
Init
|
|
2
|
加载视图状态
|
|
LoadViewState
|
3
|
处理回发数据
|
|
任意实现 IPostBackDataHandler 接口的控件中的 LoadPostData 方法
|
4
|
加载页面
|
Load
|
|
5
|
回发更改通知
|
|
任意实现 IPostBackDataHandler 接口的控件中的 RaisePostDataChangedEvent 方法
|
6
|
处理回发事件
|
由控件定义的任意回发事件
|
任意实现 IPostBackDataHandler 接口的控件中的 RaisePostBackEvent 方法
|
7
|
页面显示前阶段
|
PreRender
|
|
8
|
保存视图状态
|
|
SaveViewState
|
9
|
显示页面
|
|
Render
|
10
|
卸载页面
|
Unload
|
|
此表从上到下为处理ASPX页面所经历的过程,现简单解说一下各过程处理的工作:
1. 在页面实例化所有控件之后触发,页面的控件也仅仅是实例化好了,各静态变量也有了初始值,在这个事件里,一般我们可以设置一些事件处理程序等等。
2. 如果是回发那么加载视图状态。
3. 处理回发数据,为控件赋值,这里通过读取回发过来的数据初始化各控件的属性。这样,第一步生成的控件就会有值了。
4. 激发Load事件
5. 处理比如OnChange这样的事件,也就是控件属性和ViewState中的值不同的话,将会激发事件处理程序。
6. 处理页面里的按钮等回发事件。
7. 页面即将展现给用户,也就是即将生成HTML代码前激发的事件,注意这个地方是我们的Ajax关键事件,在这里页面前的初始化和相关事件都处理完了,服务器控件已经具有该有的值了,而Ajax应用只会返回给客户段一小部分HTML或字符串,作为客户端调用AjaxPostBack的返回值,显然此处不下手还等待何时呢,在这里,筛选出我们想要返回给客户端的对象并且只呈现这些对象即可,而执行完这些对象的Render方法之后即可停止Response数据了。
8. 保存视图状态,在aspx中视图状态保存在客户端HTML的隐藏字段__VIEWSTATE中。
9. 依次执行各个控件的Render方法,输出需要呈现的HTML标签,Render方法是asp.net中很关键的方法,每个服务器控件都有相应的Render方法,它的目的就是把自己生成HTML格式的字符串,此字符串最终返回给客户端的浏览器。
10. 最后一步触发卸载页面事件,我们可以在此处理资源回收等操作。
了解了此原理后,我想要理解Ajax的运行原理应该不是大问题了,让我们再了解一下Ajax都有哪些操作,我想不外乎两种方式,一种是不返回值或返回简单字符串形式,客户端通过这点标识操作一些功能,另外一种方式是返回HTML格式内容,比如在网页里的某个区块的动态加载内容,这两种方式大同小意,我们的Ajax在后台都是相似的处理,我想要说明处理过程还是结合示例来说比较通俗,现在就拿在CS2中经常用到的AjaxPager这个控件来说,这个控件在CS2中算是比较高级一点的应用了,与我们的第三方Ajax控件比较类似,使用方法既是在AjaxPager控件里嵌入相应的服务器控件(比如Repeater控件)以后就可以使里面的内容无刷新分页显示数据了,表面上看来是很高深很酷的功能,其实处理过程并不是想象中的那般复杂。
<CS:AjaxPager runat="Server" id="PostsPager" ShowFirstLastLinks="false">
<asp:Repeater id="Posts" runat="Server" >
<ItemTemplate>
<div class="CommonSidebarContentItem">
<asp:HyperLink Runat="server" id="TitleLink" />
</div>
</ItemTemplate>
</asp:Repeater>
</CS:AjaxPager>
大致分析一下此控件的工作流程:此控件为分页控件,在页面加载的时候赋上分页属性,计算页数,并生成相应的Ajax分页连接,当用户点击分页连接的时候,页面在后台进行ajax提交,通过服务器处理后返回HTML形式的提交结果,并通过js脚本呈现到浏览器上。
AjaxPager
public class AjaxPager : Label, IPagedControl
{
Member variables and constructor#region Member variables and constructor
protected CSContext csContext = CSContext.Current;
HyperLink previousLink;
HyperLink nextLink;
HyperLink firstLink;
HyperLink lastLink;
HyperLink[] pagingHyperLinks;
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender (e);
// Add Page buttons
//
AddPageLinks();
// Add Previous Next child controls
//
AddPreviousNextLinks();
// Add First Last child controls
//
AddFirstLastLinks();
}
#endregion
Render functions#region Render functions
protected override void Render(HtmlTextWriter writer)
{
Render(writer, true);
}
protected virtual void Render(HtmlTextWriter writer, bool renderContainer)
{
if (renderContainer)
writer.Write("<div id=\"" + this.ClientID + "_Content\">");
this.RenderChildren(writer);
if (Visible)
{
int totalPages = CalculateTotalPages();
// Do we have data?
//
if (totalPages <= 1)
return;
AddAttributesToRender(writer);
// Render the paging buttons
//
writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass, false);
//writer.RenderBeginTag(HtmlTextWriterTag.Span);
// Render the first button
//
if (this.ShowFirstLastLinks)
RenderFirst(writer);
// Render the previous button
//
RenderPrevious(writer);
// Render the page button(s)
//
RenderPagingButtons(writer);
// Render the next button
//
RenderNext(writer);
// Render the last button
//
if (this.ShowFirstLastLinks)
RenderLast(writer);
//writer.RenderEndTag();
}
if (renderContainer)
writer.Write("</div>");
}
void RenderFirst (HtmlTextWriter writer)
{
int totalPages = CalculateTotalPages();
if ((PageIndex >= 3) && (totalPages > 5))
{
firstLink.RenderControl(writer);
LiteralControl l = new LiteralControl(" ");
l.RenderControl(writer);
}
}
void RenderLast (HtmlTextWriter writer)
{
int totalPages = CalculateTotalPages();
if (((PageIndex + 3) < totalPages) && (totalPages > 5))
{
LiteralControl l = new LiteralControl(" ");
l.RenderControl(writer);
lastLink.RenderControl(writer);
}
}
void RenderPrevious (HtmlTextWriter writer)
{
Literal l;
if (HasPrevious)
{
previousLink.RenderControl(writer);
l = new Literal();
l.Text = " ";
l.RenderControl(writer);
}
}
void RenderNext(HtmlTextWriter writer)
{
Literal l;
if (HasNext)
{
l = new Literal();
l.Text = " ";
l.RenderControl(writer);
nextLink.RenderControl(writer);
}
}
void RenderButtonRange(int start, int end, HtmlTextWriter writer)
{
for (int i = start; i < end; i++)
{
// Are we working with the selected index?
//
if (PageIndex == i)
{
// Render different output
//
Literal l = new Literal();
//l.Text = "<span class=\"currentPage\">[" + (i + 1).ToString() + "]</span>";
l.Text = (i + 1).ToString();
l.RenderControl(writer);
}
else
{
pagingHyperLinks[i].RenderControl(writer);
}
if ( i < (end - 1) )
writer.Write(" ");
}
}
void RenderPagingButtons(HtmlTextWriter writer)
{
int totalPages;
// Get the total pages available
//
totalPages = CalculateTotalPages();
// If we have <= 5 pages display all the pages and exit
//
if ( totalPages <= 5)
{
RenderButtonRange(0, totalPages, writer);
}
else
{
int lowerBound = (PageIndex - 2);
int upperBound = (PageIndex + 3);
if (lowerBound <= 0)
lowerBound = 0;
if (PageIndex == 0)
RenderButtonRange(0, 5, writer);
else if (PageIndex == 1)
RenderButtonRange(0, (PageIndex + 4), writer);
else if (PageIndex < (totalPages - 2))
RenderButtonRange(lowerBound, (PageIndex + 3), writer);
else if (PageIndex == (totalPages - 2))
RenderButtonRange((totalPages - 5), (PageIndex + 2), writer);
else if (PageIndex == (totalPages - 1))
RenderButtonRange((totalPages - 5), (PageIndex + 1), writer);
}
}
#endregion
ControlTree functions#region ControlTree functions
void AddPageLinks()
{
// First add the lower page buttons
//
pagingHyperLinks = new HyperLink[CalculateTotalPages()];
// Create the buttons and add them to
// the Controls collection
//
for (int i = 0; i < pagingHyperLinks.Length; i++)
{
pagingHyperLinks[i] = new HyperLink();
pagingHyperLinks[i].EnableViewState = false;
pagingHyperLinks[i].Text = (i + 1).ToString();
pagingHyperLinks[i].ID = (i + 1).ToString();
pagingHyperLinks[i].NavigateUrl = "#";
pagingHyperLinks[i].Attributes.Add("onclick", CreatePagerJavaScript((i + 1).ToString()));
}
}
void AddFirstLastLinks()
{
int start = 1;
firstLink = new HyperLink();
firstLink.ID = "First";
firstLink.Text = ResourceManager.GetString("Utility_Pager_firstButton");
firstLink.NavigateUrl = "#";
firstLink.Attributes.Add("onclick", CreatePagerJavaScript(start.ToString()));
int last = CalculateTotalPages();
lastLink = new HyperLink();
lastLink.ID = "Last";
lastLink.Text = ResourceManager.GetString("Utility_Pager_lastButton");
lastLink.NavigateUrl = "#";
lastLink.Attributes.Add("onclick", CreatePagerJavaScript(last.ToString()));
}
void AddPreviousNextLinks()
{
int previous;
if (this.PageIndex < 2)
previous = 1;
else
previous = this.PageIndex;
previousLink = new HyperLink();
previousLink.ID = "Prev";
previousLink.Text = ResourceManager.GetString("Utility_Pager_previousButton");
previousLink.NavigateUrl = "#";
previousLink.Attributes.Add("onclick", CreatePagerJavaScript(previous.ToString()));
int next = this.PageIndex + 2;
nextLink = new HyperLink();
nextLink.ID = "Next";
nextLink.Text = ResourceManager.GetString("Utility_Pager_nextButton");
nextLink.NavigateUrl = "#";
nextLink.Attributes.Add("onclick", CreatePagerJavaScript(next.ToString()));
}
#endregion
Private Properties#region Private Properties
private bool HasPrevious
{
get
{
if (PageIndex > 0)
return true;
return false;
}
}
private bool HasNext
{
get
{
if (PageIndex + 1 < CalculateTotalPages() )
return true;
return false;
}
}
#endregion
Helper methods and Public Properties#region Helper methods and Public Properties
string url = "AjaxPager.GetPage('{0}', {1}, new Function('result', 'if (result.error) {{ alert(result.error); }} else {{ document.getElementById(\\'{0}_Content\\').innerHTML = result.value; }}')); return false;";
protected virtual string CreatePagerJavaScript(string pageIndex)
{
return string.Format(url, this.ClientID, pageIndex);
}
// *********************************************************************
// CalculateTotalPages
//
/**//// <summary>
/// Static that caculates the total pages available.
/// </summary>
///
// ********************************************************************/
public virtual int CalculateTotalPages()
{
int totalPagesAvailable;
if (TotalRecords == 0)
return 0;
// First calculate the division
//
totalPagesAvailable = TotalRecords / PageSize;
// Now do a mod for any remainder
//
if ((TotalRecords % PageSize) > 0)
totalPagesAvailable++;
return totalPagesAvailable;
}
int _pageIndex = 0;
public virtual int PageIndex
{
get
{
// Give first try to the ViewState if it was a postback
if (Page.IsPostBack && ViewState["PageIndex"] != null)
{
_pageIndex = (int) ViewState["PageIndex"];
}
else
{
if (csContext.QueryString["pageindex"] != null)
_pageIndex = int.Parse(csContext.QueryString["pageindex"]) - 1;
}
if (_pageIndex < 0)
return 0;
else
return _pageIndex;
}
set
{
ViewState["PageIndex"] = value;
_pageIndex = value;
}
}
public virtual int PageSize
{
get
{
int pageSize = Convert.ToInt32(ViewState["PageSize"]);
if (pageSize == 0)
return 10;
return pageSize;
}
set
{
ViewState["PageSize"] = value;
}
}
public virtual bool ShowFirstLastLinks
{
get
{
object state = ViewState["ShowFirstLastLinks"];
if (state == null)
return true;
else
return (bool) state;
}
set
{
ViewState["ShowFirstLastLinks"] = value;
}
}
private bool _causeValidation = true;
public bool CausesValidation
{
get {return _causeValidation;}
set {_causeValidation = value;}
}
public int TotalRecords
{
get
{
return Convert.ToInt32(ViewState["TotalRecords"]);
}
set
{
ViewState["TotalRecords"] = value;
}
}
/**//// <summary>
/// Making sure to strip the existing pager value from the querystring before appending a new one
/// </summary>
/// <param name="RawURL">Usually CSContext.Current.RawUrl</param>
/// <param name="QueryName">Usually "pageindex"</param>
/// <returns></returns>
public string UrlCleaner(string RawURL, string QueryName)
{
// Configure the Url
if(RawURL.IndexOf("?") != -1)
{
bool hasPart = false;
string queryString = RawURL.Substring( RawURL.IndexOf("?") + 1 );
string[] parts = queryString.Split('&');
for(int i = 0 ; i < parts.Length ; i++)
{
if(parts[i].StartsWith(QueryName))
{
parts[i] = QueryName + "={0}";
hasPart = true;
}
}
if(hasPart == true)
RawURL = RawURL.Replace(queryString, string.Join("&", parts));
else
RawURL = RawURL.Replace(queryString, string.Join("&", parts) + "&" + QueryName + "={0}");
}
else
RawURL = RawURL + "?" + QueryName + "={0}";
return RawURL;
}
#endregion
public override bool Visible
{
get
{
object state = this.ViewState["Visible"];
if (state == null)
return true;
else
return (bool) state;
}
set
{
this.ViewState["Visible"] = value;
}
}
AJAX#region AJAX
protected override void OnInit(EventArgs e)
{
base.OnInit (e);
AjaxManager.Register(this, "AjaxPager");
}
[AjaxMethod(IncludeControlValuesWithCallBack=true)]
public virtual string GetPage(int page)
{
StringWriter stringWriter = new StringWriter();
HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
AddPageLinks();
AddPreviousNextLinks();
if (this.ShowFirstLastLinks)
AddFirstLastLinks();
this.Render(htmlWriter, false);
return stringWriter.ToString();
}
#endregion
}
关键一点是让程序注册PreRender处理事件,我们知道,在此事件控件还没有生成HTML代码,也就是Response对象里还没有具体的HTML代码,所有的控件还没有执行Reader方法,这里我们就可以选择想执行Reader方法的控件而忽略其他控件,在此我们只需要执行包含在AjaxPager控件内的控件(也就是AjaxPager的子控件)的Reader方法, 这样就可以保证Response给客户端的是纯净的,只在AjaxPager控件内部的控件的HTML实现。
让我看看此控件重写的OnInit方法,此方法里的AjaxManager.Register(this, "AjaxPager");这一句在前面一章已经介绍过,为注册Ajax的回发脚本,当然它还有另外一个作用那就是注册PreRender事件,当触发PreReader事件 到时候,AjaxManager会去找控件里具有AjaxMethod属性的方法来执行,让我们看看在AjaxPager里的AjaxMethod都干了些什么:
[AjaxMethod(IncludeControlValuesWithCallBack=true)]
public virtual string GetPage(int page)
{
StringWriter stringWriter = new StringWriter();
HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
AddPageLinks();
AddPreviousNextLinks();
if (this.ShowFirstLastLinks)
AddFirstLastLinks();
this.Render(htmlWriter, false);
return stringWriter.ToString();
}
可以看出,倒数第二句就是Reader方法,输出的HTML保存到HtmlTextWriter中,最后一句即是把控件的HTML呈现字符串对象返回给调用者(这里指的是AjaxManager),AjaxManager获得值后按照指定的格式Response到客户端,并中止其他的Response,客户端通过调用document.getElementById(‘’).innerHTML = result.value来向浏览器呈现指定区域的数据。