Friday, September 12, 2008

ASP.NET Substitute Custom Control In Output-cached Page

ASP.NET output cache is great. For dynamic content inside output-cached pages, ASP.NET 2.0 introduces Substitution concept, where the dynamic content is retrieved and substituted for the Substitution control at run time.

Usually Substitution content is just a string. But we can extend it to render a custom server control. For example there's a custom control named MyCustomControl that needs to be displayed dynamically in a output-cached page. First we need to declare a Substitution:
<asp:Substitution runat="server" ID="subControl" MethodName="GetControlString" />
Inside the GetControlString static method:
    public static string GetControlString(HttpContext context)
{
StringWriter output = new StringWriter();
Page pageHolder = new Page();
MyCustomControl control = MyCustomControl();
//control.SetParameters();
//pageHolder.Response.ContentEncoding = System.Text.Encoding.GetEncoding("UTF-8");
pageHolder.Controls.Add(control);

context.Server.Execute(pageHolder, output, false);
context.Response.ContentEncoding = Encoding.GetEncoding("UTF-8");

return output.ToString();
}

[2009/06 Updated] HttpContext.Response.ContentEncoding needs to be defined specifically if you encounter the wrong character encoding issue inside the custom control.

Wednesday, September 03, 2008

ASP.NET Cache Info Page

This is an ASP.NET page to show Cache usage of current w3wp process:



CacheInfo.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="CacheInfo.aspx.cs" Inherits="CacheInfo" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Cache Info/Test Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<div>
<p style="text-align: center">
<asp:Label ID="Label4" runat="server" Text="Cache Info" Font-Size="Large" Font-Bold="true"></asp:Label></p>
<p>
<asp:Label ID="lblSysInfo" runat="server" EnableViewState="false"></asp:Label>
<asp:Label ID="lblInfo" runat="server" EnableViewState="false"></asp:Label>
</p>
<p>
<asp:Label ID="lblCache" runat="server" EnableViewState="false"></asp:Label><br />
<asp:GridView ID="gvCache" runat="server" AutoGenerateColumns="False" BorderWidth="1px"
BackColor="White" CellPadding="4" BorderStyle="Solid" BorderColor="#3366CC" Font-Size="Small"
AlternatingRowStyle-ForeColor="ActiveCaption" EnableViewState="false">
<HeaderStyle ForeColor="White" BackColor="#003399" HorizontalAlign="Left"></HeaderStyle>
<Columns>
<asp:TemplateField ItemStyle-Width="20" ItemStyle-Font-Size="X-Small">
<ItemTemplate>
<%# Container.DataItemIndex + 1 %>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField HeaderText="Key" DataField="Key" ReadOnly="true" ItemStyle-Width="420px"></asp:BoundField>
<asp:BoundField HeaderText="Type" DataField="Type" ReadOnly="true" ItemStyle-Width="180px"></asp:BoundField>
<asp:BoundField HeaderText="Note" DataField="Note" ReadOnly="true" ItemStyle-Width="180px"></asp:BoundField>
</Columns>
<EmptyDataTemplate>
<asp:Label ID="lblEmptyMessage" runat="server" Text="No feature found."></asp:Label>
</EmptyDataTemplate>
</asp:GridView>
</p>
<p>
<asp:Label ID="Label3" runat="server" Text="Add cache with size in MB"></asp:Label>
<asp:DropDownList ID="ddlCacheSize" runat="server" AutoPostBack="false">
<asp:ListItem>50</asp:ListItem>
<asp:ListItem>100</asp:ListItem>
<asp:ListItem>200</asp:ListItem>
<asp:ListItem>300</asp:ListItem>
<asp:ListItem>400</asp:ListItem>
<asp:ListItem>500</asp:ListItem>
<asp:ListItem>600</asp:ListItem>
<asp:ListItem>700</asp:ListItem>
<asp:ListItem>800</asp:ListItem>
<asp:ListItem>900</asp:ListItem>
<asp:ListItem>1000</asp:ListItem>
<asp:ListItem>2000</asp:ListItem>
<asp:ListItem>3000</asp:ListItem>
<asp:ListItem>4000</asp:ListItem>
<asp:ListItem>5000</asp:ListItem>
<asp:ListItem>6000</asp:ListItem>
<asp:ListItem>7000</asp:ListItem>
<asp:ListItem>8000</asp:ListItem>
<asp:ListItem>9000</asp:ListItem>
<asp:ListItem>10000</asp:ListItem>
</asp:DropDownList>&nbsp;&nbsp;
<asp:Label ID="Label1" runat="server" Text="Cache Expire in seconds"></asp:Label>
<asp:DropDownList ID="ddlExpir" runat="server" AutoPostBack="false">
<asp:ListItem>10</asp:ListItem>
<asp:ListItem>20</asp:ListItem>
<asp:ListItem>30</asp:ListItem>
<asp:ListItem>40</asp:ListItem>
<asp:ListItem>50</asp:ListItem>
<asp:ListItem>60</asp:ListItem>
<asp:ListItem>70</asp:ListItem>
<asp:ListItem>80</asp:ListItem>
<asp:ListItem>90</asp:ListItem>
<asp:ListItem>100</asp:ListItem>
<asp:ListItem>110</asp:ListItem>
<asp:ListItem>120</asp:ListItem>
<asp:ListItem>180</asp:ListItem>
</asp:DropDownList>&nbsp;&nbsp;
<asp:Button ID="btnAddCache" runat="server" Text="Add Test Cache" OnClick="btnAddCache_click" />
</p>
</div>
</div>
</form>
</body>
</html>

CacheInfo.aspx.cs:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Web.UI.WebControls;
using System.Web.Caching;
using System.Text;
using System.Data;

public partial class CacheInfo : System.Web.UI.Page
{
protected class CacheInfoItem
{
public string Key { get; set; }
public string Type { get; set; }
public string Note { get; set; }
public CacheInfoItem(string key, string type)
{
Key = key;
Type = type;
}
}

protected class CacheData
{
public Byte[] Data { get; set; }
public CacheData(int totalBytes)
{
Data = new Byte[totalBytes];
for (int i = 0; i < totalBytes; i += 123)
{
Data[i] = (byte)(i % 256);
}
}
}

protected string TestCacheKey = "Cache_Test_Key";

protected void btnAddCache_click(object sender, EventArgs e)
{
try
{
int totalBytes = int.Parse(ddlCacheSize.SelectedValue) * 1024 * 1024;
CacheData data = new CacheData(totalBytes);
int seconds = int.Parse(ddlExpir.SelectedValue);
HttpRuntime.Cache.Insert(TestCacheKey, data, null, DateTime.Now.AddSeconds(seconds), Cache.NoSlidingExpiration);
lblInfo.Text = "Successfully added test data to cache (expire in " + seconds + " seconds).";
}
catch (Exception ex)
{
lblInfo.Text = "Insert Cache error: " + ex.Message;
}
}

protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
List<CacheInfoItem> cacheItems = new List<CacheInfoItem>();

// Populate System Info
System.Diagnostics.Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();
long totalMemBytes = currentProcess.WorkingSet64;
DateTime startTime = currentProcess.StartTime;
Cache cache = HttpRuntime.Cache;
StringBuilder sbInfo = new StringBuilder();
sbInfo.Append(Server.MachineName + " " + currentProcess.MachineName + " ");

sbInfo.Append(currentProcess.ProcessName + " start time: " + startTime.ToString("yyy-MM-dd HH:mm") + " ");
sbInfo.Append("Process memory usage (MB): " + totalMemBytes / (1024 * 1024) + "<br>");
lblSysInfo.Text = sbInfo.ToString();

// Populate Cache Info
sbInfo = new StringBuilder();
if (cache != null)
{
foreach (DictionaryEntry item in cache)
{
try
{
Type type = item.Value.GetType();
string typeInfo = type.ToString();

CacheInfoItem ci = new CacheInfoItem(item.Key.ToString(), typeInfo);
cacheItems.Add(ci);

if (type == Type.GetType("System.DateTime"))
{
ci.Note = "Time: " + Convert.ToDateTime(HttpRuntime.Cache[item.Key.ToString()]).ToString("yyy-MM-dd HH:mm");
}
else if (type == Type.GetType("System.String"))
{
ci.Note = "Value: " + cache[item.Key.ToString()].ToString();
}
else
{
Object obj = cache[item.Key.ToString()];
if (obj != null)
{
if (obj is IList)
{
ci.Note = "Item Count: " + ((IList)obj).Count.ToString();
}
else if (obj is DataSet && (DataSet)obj != null && ((DataSet)obj).Tables[0] != null)
{
ci.Note = "Item Count: " + ((DataSet)obj).Tables[0].Rows.Count.ToString();
}
else if (obj is DataTable && (DataTable)obj != null)
{
ci.Note = "Item Count: " + ((DataTable)obj).Rows.Count.ToString();
}
}
}
}
catch (Exception ex)
{
sbInfo.Append("Get cache info error for " + item.Key + ":" + ex.Message + "<br>");
throw;
}
}
}
lblInfo.Text += sbInfo.ToString();

lblCache.Text = "Total Cache item count: " + cacheItems.Count.ToString();
cacheItems.Sort((i, j) => i.Key.CompareTo(j.Key));
gvCache.DataSource = cacheItems;
gvCache.DataBind();
}
}