[Update April 2005] - At 5999 hits, I want to thank everyone that has visited. I changed the title to “Templated Pages etc...“ since there are so many searching for assistance on actual ASP.Net Master Page™ technology.
Over the last year using the technology described in this article, I've realized the big advantage: With this technique, your single web application can scale it's data to the level of granularity your visitor is expecting. The day of the global, newspaper-style website is over. Simplicity, specificity in your web application is what's going to keep your users interest piqued. The technology listed here allows you to create an abstract website... then, through parsing out the RequestPath, determine the granularity of the visitor's destination.
As a quick example, you can host domains for MyCoGlobal, MyCoWisconsin, MyCoMilwaukeeWI, MyCoEastSideMilwaukee, each of which will be crawled separately, and may contain data (news? classifieds? adwords?) scoped to the appropriate level. This is accomplished through the development of a single web application. I've probably read a hundred articles on the nextgen web frameworks, and I'm sure that there are other options out there for such a solution, but this solution has made things incredibly easy for creating a domain-scalable architecture.
Though the article describes loading controls dynamically and rewriting your RequestPath, it implicitly describes how to parse out your url, rewrite it on the fly, and handle it appropriately. The path rewriting in this article explains how to use rewriting to represent user controls as pages, but in practice, it has just as easily allowed the single web application to behave according to the url requested.
Overview
Master pages are still awhile away, so there are some interesting solutions popping up to hold us over in the meantime. I'm working on a project that requires ease of design (a single page holds the layout, with a placeholder for the content), and maximum “crawlability“ by search engines. The solution requires some advanced techniques in ASP.Net to come up with a "master pages" 1.1 solution for a project I'm working on. This solution revolves around 2 techniques: loading user controls dynamically at runtime, and rewriting urls.
- Load user controls dynamically into a master page by parsing out control name in the querystring. Retain all other querystring info as args to the control.
- Rewrite URL paths so client's page requests get passed as querystrings to the master page, example: www.asdf.com/faq.aspx maps to www.asdf.com/default.aspx?faq.aspx
Loading Controls Dynamically
Ultimately, we want to take the name of a “content control” in a querystring, and load it dynamically into a page.
I have created a directory to store my content controls, called “content.“ On the main page is a pnlContent panel into which the control will be loaded when the page loads. The name of the content control to load is passed to the page in the QueryString (passed as “p“ in this case). If the page sees www.asdf.com/default.aspx?p=faq, it knows to look for faq.ascx and load it into the content panel on the page. If no such control can be found, or if there is an error, then a custom 404 is displayed in the panel.
private void Page_Load(object sender, System.EventArgs e)
{
//Parse out control name from qs and load control into the page's pnlContent panel
string ucname=Request.QueryString["p"];
if(ucname == null || ucname == String.Empty || ucname.ToLower()=="default")
ucname="introduction";
if(new System.IO.FileInfo(this.MapPath("~/content/"+ucname+".ascx")).Exists)
this.pnlContent.Controls.Add(LoadControl("~/content/"+ucname+".ascx"));
else
this.pnlContent.Controls.Add(LoadControl("~/content/PageNotFound404.ascx"));
}
Rewriting URL Paths
There is a bug in the HttpContext.RewritePath method.
Jesse Ezell has a good description of the issue
here (apparantly a MS design flaw).
The workaround in making RewritePath work for you involves:
- overriding the Render method of your page
- writing a custom HtmlTextWriter class to modify the action atrribute of your web form (where the problem resides).
Before we get started with these things, let's get the URL rewrite started. This takes place in the global.asax, inside the Application_BeginRequest method. The process is roughly as follows:
The code that does just this is below.
protected void Application_BeginRequest(Object sender, EventArgs e)
{
//string pretoken="default.aspx/"; //text that precedes the page arg
string pretoken = ConfigurationSettings.AppSettings["pagename_pretoken_string"];
HttpContext c = HttpContext.Current;
string pagename=""; //destination page
string path = c.Request.Url.ToString();
string qsparams = ""; //additional querystring args if applicable
//mark start and finish indices of path to substring page name from request
int start=path.IndexOf(pretoken)+pretoken.Length;
int numchars=path.IndexOf(".",start)-start;
pagename=path.Substring(start,numchars);
if(pagename!="default")
{
if(path.IndexOf("?")!=-1) //there are additional querystring args
qsparams="&"+path.Substring(path.IndexOf("?")+1);
string newPath = "default.aspx?p="+pagename+qsparams;
c.Items["requestpath"]=newPath; //this will be needed for the RewritePath bugfix (below)
c.RewritePath(newPath);
}
}
A note regarding Configuration in ASP.Net
Many may be familiar with web.config and it's behaviour in ASP.Net's architecture, but for those that aren't, I wll post a quick paragraph on the process. While it's most common to have web.config residing in your application root, it is possible to place more web.config files in subdirectories of your application. They are processed in a top-down fashion, in the following order: machine, site, application, and subdirectory. Note that machine.config is the only config that is required in the ASP.Net framework. All other configs only exist to store changes from the default machine.config file.
In this application, the web.config in the site contains the configuration key to contain the pretoken.
<xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="pagename_pretoken_string" value="asdf.com/" />
<< FONT>appSettings>
<configuration>
Reading the configuration settings in an application is very simple. Inside .Net's System.Configuration namespace is the ConfigurationSettings class, and the AppSettings indexer that is able to reference the appSettings elements in your config file. Make sure you have a reference to System.Configuration, and you can access appSettings elements very easily:
string pretoken = ConfigurationSettings.AppSettings["pagename_pretoken_string"];
Finally - the BIG RewritePath issue
The solution is here, though I found it rather vague. It took some work to take Jesse's notes and turn them into a real solution. The 2 major steps follow.
Note! - The IHttpModule in Jesse's solution is used in place of the global.asax Application_BeginRequest method we're using in this solution. You can do your rewrites using a custom IHttpModule if you wish, but it is an alternative to the rewrite method below.
1. Write a custom HtmlTextWriter class to modify the action attribute of your web form (where the problem resides).
This looks for the form tag, and finds the action attribute inside, changing the action to the correct address.
using System.IO;
using System.Web.UI;
public class FormFixerHtmlTextWriter : HtmlTextWriter
{
private bool inForm = false;
private string _action;
public FormFixerHtmlTextWriter(TextWriter w, string a) : base(w)
{
_action=a;
}
public override void RenderBeginTag(string tagName)
{
inForm = String.Compare(tagName,"form") == 0;
base.RenderBeginTag (tagName);
}
public override void WriteAttribute(string name, string value, bool fEncode)
{
if(String.Compare(name,"action",true)==0)
value = _action;
base.WriteAttribute (name, value, fEncode);
}
}
2. Override the Render method of your page to use the new custom HtmlTextWriter. Note that the HttpContext's RewrittenPath item value was stored in the global.asax Application_BeginRequest method as part of the rewriting process.
protected override void Render(HtmlTextWriter writer)
{
string action = (string)HttpContext.Current.Items["requestpath"];
if(action!=null)
writer = new FormFixerHtmlTextWriter(writer,action);
base.Render(writer);
}
This article has quickly described how to leverage current ASP.Net techniques to enable easy, flexible page development, while providing a client-side environment that is simple and easy to navigate by end-users and search-engines. ASP.Net provides us with numerous techniques for configuration, page and control handling, and url manipulation.
Thank you.
Print | posted on Thursday, May 27, 2004 8:48 PM