As a web developer, you will encounter situations that call for an effective token replacement scheme. Token replacement is really just a technologically-savvy way of saying string replacement, and involves substituting a “real” value for a “token” value in a string. This article presents a powerful approach to token replacement in ASP.NET 2.0.
The goal is to create a centralized token replacement mechanism that works in ASP.NET controls, HTML controls, static HTML markup, and in text placed on the page from the code-behind. In other words, in any situation imaginable.
If you want to skip the background info and get right into the replacement mechanism, then jump to the section titled Acquiring All ASP.NET Page Content. Otherwise, let’s get some background on the various token replacement options in ASP.NET.
Basic token replacement concepts
The ultimate goal of token replacement is to make the value of a string dynamic. You begin with a static string containing tokens, and then update those tokens with replacement values to produce your dynamic string. For example, you may have the following string in an application that emails users a new password if theirs has been lost:
1 2 3 4 5 6 |
Dear [$NAME$], You recently requested a password reset. Your new password is [$PASSWORD$]. Please keep this password in a safe location and quit losing it. Thank You |
In this scenario, the application has two values to communicate to the user: their name (to personalize the email) and their new password (because they need it to login). Let’s say we wish to assign the value “Matt” to the [$NAME$]
token and “5ZQS76Bv” to the [$PASSWORD$]
token. The resulting text would look like this:
1 2 3 4 |
Dear Matt, You recently requested a password reset. Your new password is 5ZQS76Bv. Please keep this password in a safe location and quit losing it. Thank You |
One question that might arise is, why not just use concatenation to build the string? Concatenation is certainly faster, but it comes down to a question of maintainability. Concatenation can only occur in code, so you would have to hard-code the majority of the string, like this:
1 2 3 |
“Dear ” + Name + “,\r\nYou recently requested a password reset. Your new password is ” + password + “. Please keep this password in a safe location and quit losing it.\r\n\r\nThank You” |
What happens if you wanted to update the email content? You would have to update your code, recompile it, and redeploy it. Plus, a complex HTML email built directly in C# source is not the easiest thing to debug and maintain.
The token replacement approach allows you to store the content in a separate file, read it into your application, and replace the tokens in code. This separates the content from the application and allows you to make updates in a less convoluted environment and without having to recompile or redeploy the application.
Token / String Replacement in ASP.NET
There are several ways to replace strings in ASP.NET 2.0, but I’m only going to touch on the three that I consider the most popular:
- Inline Script
- The
String.Format
method - The
String.Replace
andStringBuilder.Replace
instance methods
Each of these are outlined in more detail below.
Inline Script
Although ASP.NET has moved to a code-behind model, you can still write inline script just as you could in original ASP. Inline script still has its uses and can be extremely helpful for string replacement scenarios. If you think about it, inline script is essentially a really powerful string replacement mechanism that finds your script in a string (i.e. the page markup), runs that script, and then replaces the script with the value it produces. Here’s our original example modified for inline script:
1 2 3 4 |
Dear <%=Name%>, You recently requested a password reset. Your new password is <%=Password%>. Please keep this password in a safe location and quit losing it. Thank You |
One issue with inline script is that you cannot always use it in conjunction with ASP.NET controls. For example, let’s say that you wanted a standard ASP.NET button on the page to say “Matt, Click Here to Email Your Password.” You cannot do the following:
1 2 |
<asp:Button runat=”server” id=”btnPwd”” text=”<%=Name%>, Click Here to Email Your Password” /> |
Nor can you use the mechanism from inside your code-behind. So, it’s useful for making a single page, but not for our global string-replacement needs.
The String.Format Method
The String.Format
method accepts a string containing tokens followed by a list of replacement values for the tokens found in the string. The tokens in the string are numbers surrounded by curly brackets, where the number corresponds to the index of the replacement value passed into the method.
1 2 3 4 |
String.Format(“Name: {0}, Gender: {1}, Age:{2}, Height:{3}, Weight:{4}“, “Matt”, “Male”, “25”, “5’10\””, “160”); //OUTPUT–>Name: Matt, Gender: Male, Age: 25, Height: 5’10”, Weight: 160 |
The first token, {0}
, corresponds to the first replacement value “Matt”, the second token, {1}
, corresponds to “Male”, and so on. You can have an infinite number of tokens and replacement values, but you have to have at least as many replacement values as you have tokens. In other words, when the function encounters a number in curly brackets, {N}
, there had better be a corresponding replacement value for that token or else the function throws an exception.
1 2 3 4 5 |
// Throws an exception because there are 4 tokens // but only one replacement value String.Format(“Name: {0}, Gender: {1}, Age:{2}, Height:{3}, Weight:{4}“, “Matt”); |
Tokens do not need to appear in order, you can use a token more than once in the string, and you can have more replacement values than tokens:
1 2 3 4 |
String.Format(“{1}{3}{2}{2}{0}”, “O”,”H”,”L”,”E”,”X”,”Y”,”Z”,”!”); //OUTPUT–>HELLO |
In this example, the token, {2}
, appears twice, the tokens are not in order, and the “X”, “Y”, “Z”, and “!” replacement values are never used.
You can easily put tokens for the string.Format method in ASP.NET controls, HTML controls, static HTML markup, and code in the code-behind, so it is a candidate for use in our global string replacement mechanism. My biggest issue is that the tokens are non-intuitive. When you see a token like [$NAME$]
, you have some idea what it represents, whereas the token, {7}
, communicates very little.
The String.Replace and StringBuilder.Replace Methods
Lastly, we have the Replace
methods found on the String
and StringBuilder
instances. Both of these methods operate using the same basic logic. You provide a string containing a token, identify the token, supply a replacement value for the token, and the method replaces any instances of the token with the replacement value.
We’ll begin by looking at the Replace
method on a String
instance. Since the Replace
method is only available on a String
instance and not from the String
class itself, you start by creating a String
instance. Then you call the Replace
method from the instance by passing in a token and replacement value, and the method returns a string containing the replacements. Your original string, however, remains unchanged. If you want to update your string with the replacement, you have to assign the result of the Replace
function back to your string, as demonstrated in the following example:
1 2 3 4 5 6 7 8 |
string myString = “Hello, my name is [$NAME$].”; //This does NOT change the value of myString myString.Replace(“[$NAME$]”, “Matt”); //You have to assign the value of the Replace function back //to the string to change the value myString = myString.Replace(“[$NAME$]”, “Matt”); |
You call the Replace
method on a StringBuilder
instance in the exact same way as a string: by passing in a token and replacement value. However, the Replace
method on a StringBuilder
instance operates directly on that StringBuilder
instance’s value, so you do not need to assign the result of the Replace
function back to your StringBuilder
to pick up the changes:
1 2 |
StringBuilder myBuilder = new StringBuilder(“Hello, my name is [$NAME$].”); myBuilder.Replace(“[$NAME]”,”Matt”); |
Tokens like [$NAME$]
are fairly intuitive, can be placed in ASP.NET controls, HTML controls, static HTML markup, and code in your code behind. So it’s the option we’re going to run with for building the global string replacement mechanism for our ASP.NET application.
Acquiring All ASP.NET Page Content
Our biggest objective in building a global string replacement mechanism is that it needs to work everywhere. If you put a token directly in your HTML, it needs to be replaced. If you assign a token to an ASP.NET control from a code-behind, then it needs to be replaced. If you put a token in a database and a control pulls that value from the database and displays it, then it needs to be replaced. So… how do you go about doing that?
When it comes right down to it, all of the architecture and code for ASP.NET revolves around building a giant string to send to the browser. ASP.NET controls, HTML, code, values from a database, they all end up as part of a string containing the source for a page. All we have to do is intercept that string before ASP.NET sends it to the browser and run our replacements. And it really doesn’t take all that much code.
Since we want this mechanism to be available to all of the pages in an application, we’ll create a new class named TokenReplacementPage
that derives from System.Web.UI.Page
. Any pages requiring token replacement functionality just need to derive from TokenReplacementPage
instead of System.Web.UI.Page
. Following is the code for the TokenReplacementPage
class:
Code Listing 1 – TokenReplacementPage class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
using System; using System.IO; using System.Text; using System.Web.UI; public abstract class TokenReplacementPage : Page { protected override void Render(HtmlTextWriter writer) { //Create our own mechanism to store the page output StringBuilder pageSource = new StringBuilder(); StringWriter sw = new StringWriter(pageSource); HtmlTextWriter htmlWriter = new HtmlTextWriter(sw); base.Render(htmlWriter); //Run replacements RunPageReplacements(pageSource); RunGlobalReplacements(pageSource); //Output replacements writer.Write(pageSource.ToString()); } protected void RunGlobalReplacements(StringBuilder pageSource) { pageSource.Replace(“[$SITECONTACT$]”, “John Smith”); pageSource.Replace(“[$SITEEMAIL$]”, “john.smith@somecompany.com”); pageSource.Replace(“[$CURRENTDATE$]”, DateTime.Now.ToString(“MM/dd/yyyy”)); } protected virtual void RunPageReplacements(StringBuilder pageSource) { } } |
First, note that the
class is an abstract class that derives from TokenReplacementPag
eSystem.Web.UI.Page
. This means that it has all the standard Page
functionality as well as token replacement features. All you have to do to confer token replacement functionality to a page is inherit from the TokenReplacementPage
class instead of the Page
class.
There are three methods inside the TokenReplacementPage
class:
- The
Render
method – to write the HTML page source to aStringBuilder
- The
RunGlobalReplacements
method – to perform global token replacements on the source RunPageReplacements
– to allow for page-specific token replacements
Writing the Page Source to StringBuilder
The overridden Render
method has a single HtmlTextWriter
parameter named writer. An HtmlTextWriter
allows you to write HTML to a stream. In this case, the underlying stream is sent to the browser, so the writer parameter is your conduit for outputting HTML to the people viewing your page. Normally, the Render
method iterates through all of the controls on the page and passes the writer parameter to the Render
method on each individual control. As each control executes its Render
method, it writes the HTML for that section of the page to the browser.
Since we want to capture the entire page source before it gets to the browser, we need to do a little work to re-route the page source into a stream that we can access. To start, we create a “stream” that we can work with: a StringBuilder
instance named pageSource
. Next, we create a new StringWriter
named sw
and pass pageSource
into its constructor. This initializes sw
with pageSource
as its underlying stream. Anything written to sw is output to pageSource
. Next, we create a new HtmlTextWriter
named htmlWriter
and pass sw
into its constructor. This initializes htmlWriter
with sw
as its underlying TextWriter
. Thus, anything written to htmlWriter
is written to sw
, which is then written to pageSource
. Finally, we pass htmlWriter
to the Base.Render
method and allow the page to render as it normally would. After Base.Render
finishes, the entire page source is available for modification in pageSource
.
After acquiring the page source, the overridden Render
method passes pageSource
to two methods that are responsible for actually making the substitutions: RunPageReplacements
and RunGlobalReplacements
. We will discuss these in more detail shortly.
Once the replacements have been made, the only thing left to do is send the updated content to the browser. We do that by using pageSource
to write in the last line of code in the overridden Render
method.
Next, let’s take a look at making the actual replacements.
Global Replacements
You make global replacements, that is the replacements you want to make on every page that inherits from the TokenReplacementPage
class, in the RunGlobalReplacements
method. All you have to do to make a replacement is call the Replace
method on the pageSource
parameter and pass in the token and the replacement value:
1 |
pageSource.Replace(“TOKEN”, “REPLACEMENT VALUE”); |
The Replace
method then searches through the string in pageSource
and replaces any instances of the token with the replacement value. Remember to make your token something that is unlikely to normally appear in the page. For example, [$NAME$]
is a much better choice than just NAME
because it’s very unlikely a normal sentence would contain a word with brackets and dollar signs around it. You don’t want to accidentally mistake a normal word in a sentence for a token.
Page Specific Replacements
There may be times when you want to run page-specific replacements. Notice that the RunPageReplacements
method in the TokenReplacementPage
class is marked as virtual and contains no code. This allows you to override the RunPageReplacements
method on the page in which you want to make page-specific token replacements. The replacements are made in the exact same fashion as described in the Global Replacements section, but they are only applied to that specific page:
Code Listing 2 – Overridden RunPageReplacements Example
1 2 3 4 5 6 7 8 9 |
public partial class PageSpecificReplacementsPage : TokenReplacementPage { protected override void RunPageReplacements( System.Text.StringBuilder pageSource) { pageSource.Replace(“[$PAGESPECIFICTOKEN$]”, “This replacement only runs on this page!”); } } |
Checking out the demo application
Download the demo application (from the Code Download link in the box to the right of the article title) and extract it to a location of your choosing on your hard drive. Start Visual Studio, and open the web site from wherever it is that you chose to save it. There are only four files (not including code-behinds) in the entire demo:
File Name |
Purpose |
App_Code\TokenReplacementPage.cs |
Contains the |
Default.aspx |
Demonstrates global token replacement functionality |
PageSpecificReplacementsPage.aspx |
Demonstrates global and page-specific token replacement functionality |
Web.config |
Website configuration |
Take a look at the markup in the two ASP.NET pages and notice the various tokens that appear throughout. Also take a look at each page’s code behind files because you will see a token set in code to demonstrate that you can put a token anywhere and, as long as it is output to the page source, it is replaced by the token replacement mechanism. The code-behind for PageSpecificReplacementsPage.aspx
also contains the RunPageReplacement
override from Code Listing 2.
When you run the demo application, you will see that the tokens are replaced with their respective values when the page appears in your browser. Make sure to observe the difference between the [$PAGESPECIFICTOKEN$]
behavior between Default.aspx
and PageSpecificReplacementsPage.aspx
. Feel free to add new tokens, replacement values, and pages to the demo application to get a feel for how it all works.
Conclusion
When you need a token replacement mechanism, you should be well equipped with this solution. It gives you the ability to replace tokens regardless of whether they appear in ASP.NET controls, HTML controls, static HTML markup, code, or even a content management database. As long as you can get the token to render on the page, it can be replaced.
Load comments