以上按12个场景进行代码分析,下面从技术点的角度来分析代码:
技术点1:这个应用中,在表示层大量使用了一种自定义控件,继承了Repeater类。先看看全文检索宠物调用Simplepager的方式:
Part 1 页面代码,先引入标签,后使用标签。使用标签时,先设置属性,后设置模板。这个标签可以设置页面大小,当没有条目时,给出提示,分页时用了一个onpageindexchanged事件。
<PetsControl:simplepager id="products" runat="server" pagesize="4" emptytext="No products found." onpageindexchanged="PageChanged">
<headertemplate>
<table cellpadding="0" cellspacing="0">
<tr class="gridHead">
<td>Product ID</td>
<td>Name</td>
<td>Description</td>
</tr>
</headertemplate>
<itemtemplate>
<tr class="gridItem">
<td><%# DataBinder.Eval(Container.DataItem, "Id") %></td>
<td><a href='Items.aspx?productId=<%# DataBinder.Eval(Container.DataItem, "Id") %>'><%# DataBinder.Eval(Container.DataItem, "Name") %></a></td>
<td><%# DataBinder.Eval(Container.DataItem, "Description") %></td>
</tr>
</itemtemplate>
<footertemplate>
</tbody>
</table>
</footertemplate>
</PetsControl:simplepager>
在
onpageindexchanged事件中,绑定数据源。
protected void PageChanged(object sender, DataGridPageChangedEventArgs e) {
products.CurrentPageIndex = e.NewPageIndex;
// Get the search terms from the query string
string searchKey = WebComponents.CleanString.InputText(Request["keywords"], 100);
if (searchKey != ""){
// Create a data cache key
string cacheKey = "search" + searchKey;
// Check if the objects are in the cache
if(Cache[cacheKey] != null){
products.DataSource = (IList)Cache[cacheKey];
}else{
// If that data is not in the cache then use the business logic tier to fetch the data
Product product = new Product();
IList productsBySearch = product.GetProductsBySearch(searchKey);
// Store the results in a cache
Cache.Add(cacheKey, productsBySearch, null, DateTime.Now.AddHours(12), Cache.NoSlidingExpiration , CacheItemPriority.High, null);
products.DataSource = productsBySearch;
}
// Databind the data to the controls
products.DataBind();
}
}
Part 2 看看SimplePager的源代码。在源代码中,SimplePager继承了Repeater,主要增添了分页功能,大部分是属性的定义,增加了OnPageIndexChanged事件,重写了OnLoad、Render和OnDataBinding方法。Onload获取请求参数,激发分页事件。Render描述了如何渲染这个控件,OnDataBinding告诉控件要绑定哪些数据。
public class SimplePager : Repeater {
//Static constants
protected const string HTML1 = "<table cellpadding=0 cellspacing=0><tr><td colspan=2>";
protected const string HTML2 = "</td></tr><tr class=gridNav><td>";
protected const string HTML3 = "</td><td align=right>";
protected const string HTML4 = "</td></tr></table>";
private static readonly Regex RX = new Regex(@"^&page=\d+", RegexOptions.Compiled);
private const string LINK_PREV = "<a href=?page={0}><img src=Images/buttonPrev.gif alt=Previous border=\"0\"></a>";
private const string LINK_MORE = "<a href=?page={0}><img src=Images/buttonMore.gif alt=More border=\"0\"></a>";
private const string KEY_PAGE = "page";
private const string COMMA = "?";
private const string AMP = "&";
protected string emptyText;
private IList dataSource;
private int pageSize = 10;
private int currentPageIndex;
private int itemCount;
override public object DataSource {
set {
//This try catch block is to avoid issues with the VS.NET designer
//The designer will try and bind a datasource which does not derive from ILIST
try{
dataSource = (IList)value;
ItemCount = dataSource.Count;
}catch{
dataSource = null;
ItemCount = 0;
}
}
}
public int PageSize {
get { return pageSize; }
set { pageSize = value; }
}
protected int PageCount {
get { return (ItemCount - 1) / pageSize; }
}
virtual protected int ItemCount {
get { return itemCount; }
set { itemCount = value; }
}
virtual public int CurrentPageIndex {
get { return currentPageIndex; }
set { currentPageIndex = value; }
}
public string EmptyText {
set { emptyText = value; }
}
public void SetPage(int index) {
OnPageIndexChanged(new DataGridPageChangedEventArgs(null, index));
}
override protected void OnLoad(EventArgs e) {
if (Visible) {
string page = Context.Request[KEY_PAGE];
int index = (page != null) ? int.Parse(page) : 0;
SetPage(index);
}
}
/**//// <summary>
/// Overriden method to control how the page is rendered
/// </summary>
/// <param name="writer"></param>
override protected void Render(HtmlTextWriter writer) {
//Check there is some data attached
if (ItemCount == 0) {
writer.Write(emptyText);
return;
}
//Mask the query
string query = Context.Request.Url.Query.Replace(COMMA, AMP);
query = RX.Replace(query, string.Empty);
// Write out the first part of the control, the table header
writer.Write(HTML1);
// Call the inherited method
base.Render(writer);
// Write out a table row closure
writer.Write(HTML2);
//Determin whether next and previous buttons are required
//Previous button?
if (currentPageIndex > 0)
writer.Write(string.Format(LINK_PREV, (currentPageIndex - 1) + query));
//Close the table data tag
writer.Write(HTML3);
//Next button?
if (currentPageIndex < PageCount)
writer.Write(string.Format(LINK_MORE, (currentPageIndex + 1) + query));
//Close the table
writer.Write(HTML4);
}
override protected void OnDataBinding(EventArgs e) {
//Work out which items we want to render to the page
int start = CurrentPageIndex * pageSize;
int size = Math.Min(pageSize, ItemCount - start);
IList page = new ArrayList();
//Add the relevant items from the datasource
for (int i = 0; i < size; i++)
page.Add(dataSource[start + i]);
//set the base objects datasource
base.DataSource = page;
base.OnDataBinding(e);
}
public event DataGridPageChangedEventHandler PageIndexChanged;
virtual protected void OnPageIndexChanged(DataGridPageChangedEventArgs e) {
if (PageIndexChanged != null)
PageIndexChanged(this, e);
}
}
Part 3 从上面的代码你可以看出SimplePager有一个致命的缺点就是:如果一个页面内有两个SimplePager控件怎么办?如果他们还依赖于请求的URL附带参数的话,两个控件岂不是会冲突?ViewStatePager正是为解决这个问题的,在ViewStatePager中用ViewState记住各自控件的记录数和当前页数。调用这个控件的地方是购物车页面。
public class ViewStatePager : SimplePager {
private const string KEY_ITEM_COUNT = "ItemCount";
private const string KEY_CURRENT_PAGE_INDEX = "CurrentPageIndex";
private const string IMG_PREV = "Images/buttonPrev.gif";
private const string IMG_MORE = "Images/buttonMore.gif";
private const string ALT_PREV = "Previous";
private const string ALT_MORE = "More";
private ImageButton btnPrev;
private ImageButton btnMore;
override protected int ItemCount {
get { return (int)ViewState[KEY_ITEM_COUNT]; }
set { ViewState[KEY_ITEM_COUNT] = value; }
}
override public int CurrentPageIndex {
get { return (int)ViewState[KEY_CURRENT_PAGE_INDEX]; }
set { ViewState[KEY_CURRENT_PAGE_INDEX] = value; }
}
override protected void OnLoad(EventArgs e) {
if (!Page.IsPostBack && Visible) {
CurrentPageIndex = 0;
SetPage(0);
}
}
private void PreviousClicked(object sender, ImageClickEventArgs e) {
OnPageIndexChanged(new DataGridPageChangedEventArgs(sender, CurrentPageIndex - 1));
}
private void MoreClicked(object sender, ImageClickEventArgs e) {
OnPageIndexChanged(new DataGridPageChangedEventArgs(sender, CurrentPageIndex + 1));
}
override protected void CreateControlHierarchy(bool useDataSource) {
base.CreateControlHierarchy(useDataSource);
btnPrev = new ImageButton();
btnPrev.ImageUrl = IMG_PREV;
btnPrev.AlternateText = ALT_PREV;
btnPrev.Click += new ImageClickEventHandler(PreviousClicked);
Controls.Add(btnPrev);
btnMore = new ImageButton();
btnMore.ImageUrl = IMG_MORE;
btnMore.AlternateText = ALT_MORE;
btnMore.Click += new ImageClickEventHandler(MoreClicked);
Controls.Add(btnMore);
}
override protected void OnPreRender(EventArgs e) {
btnPrev.Visible = CurrentPageIndex > 0;
btnMore.Visible = CurrentPageIndex < PageCount;
}
override protected void Render(HtmlTextWriter writer) {
if (ItemCount == 0) {
writer.Write(emptyText);
return;
}
writer.Write(HTML1);
for (int i = 0, j = Controls.Count - 2; i < j; i++)
Controls[i].RenderControl(writer);
writer.Write(HTML2);
btnPrev.RenderControl(writer);
writer.Write(HTML3);
btnMore.RenderControl(writer);
writer.Write(HTML4);
}
} 未完待续