[ASP.NET] Extending the Label Control

This is an old article I wrote for CodeProject once. I'm putting it on my blog just to keep track of it. The code is available for download at CodeProject -> http://www.codeproject.com/aspnet/textcontrolext.asp

Introduction

This small article shows how to extend the ASP.NET Label Control, so it reads resource strings from an XML file, which is stored in memory as a Cached DataSet with cache-dependency to the XML-file. Each web page has its own XML-file where texts are stored.

A control like this might come in handy if you need to translate your web application into other languages, or if the owner of the web site wants to change the “static” texts in the web pages by just editing the XML-files.

The program could easily be extended so the resource texts are read from another source, like a database, Web Service or a resource file, but I wanted to keep this article fairly small and easy, just to show the general idea.

I’ve used this technique in production, but since we use a web farm in production, having the resource strings in files isn’t that convenient, so instead I keep all strings in a database. Its also a good idea to give the web application owner, a nice interface for changing the texts. I might write another article to show you how to do this.

Some of the .NET features used in the sample project:

  • Custom controls
  • DataSet
  • XML
  • Cache

Code overview

The Control is called TextControl because it handles texts from a resource of strings. Perhaps a name like Text or StringResource is better, but TextControl will have to do. Feel free to change it :)

The central part of the code is made up from a single custom control, which inherits from, and extends the ASP.NET Label control. The control also uses a helper class called XMLResource, which will be explained later.

[DefaultProperty("Key"), ToolboxData("<{0}:TextControl runat=server></{0}:TextControl>")] public class TextControl : System.Web.UI.WebControls.Label 

The reason I wanted to inherit from the Label control instead of the base Control class is that I want to use the rendering functions that are already implemented in the Label control for different fonts, colors and stuff. I’m lazy ;)

The TextControl needs to know what text string it should get from its resource of texts, so therefore I added a new property to it called Key. I might as well have re-used the inherited Text property that is already in there, but I had better use for that Text property (shown later).

private string key; [Bindable(true), Category("Appearance"), DefaultValue(""), Description("ID/Key of the resource text to get")] public string Key { get { return key; } set { key = value; } } 

There really isn’t much to say about the code snippet above. Its important to have the right attributes or the property might not show up in the property dialog in VisualStudio.NET.

The final (and interesting part) of the control is the code that renders the output in both run-time and design-time. To do this you must override the Render() method of the Label class. When the ASPX page renders content, the page calls this Render() method for each control on the page.

protected override void Render(HtmlTextWriter output) { try { //get the text string from the resource //the Text property is used as "default" value Text = new XMLResource().GetValueFromKey(Key, Text); //call base class for rendering base.Render(output); } catch //catch design-time exceptions  { //render the Text property with brackets String tmpText = Text; Text = "[" + Text + "]"; //call base class for rendering base.Render(output); //put back the default text without brackets Text = tmpText; } } 

As you can see in the code above, I’m using a try/catch block to handle errors, that might happen when we’re getting the text from the resource. I’m getting the texts from an XML-file with the same name as the ASPX file (as described in the introduction), so if the TextControl sits in an ASPX page called Default.aspx, the resource XML file for that page is called Default.xml and located in the same directory as the ASPX page.

To get the name of the ASPX file, I use the CurrentExecutionFilePath property of the Request object. The Request object is accessed via the current HttpContext, which isn’t available at design-time, so therefore I catch the exception thrown and show the default text within square brackets. You might as well prevent the exception in the XMLResource class (shown below) but this works fine for me.

If the requested string (based on the Key) isn’t found or if an exception occurs, I show the default text, which I get from the re-used Text property of the parent Label control.

So, how do we get the text from the resource file? All is done in the XMLResource class, which I’ll try to explain below. The XMLResource class has two methods; one public to get the requested text string and one private, which get the XML file from a cached DataSet. First the public method:

public String GetValueFromKey(String key, String defaultValue) { DataSet ds = GetResourceDS(); //filter out everything except our resource string in a dataview DataView dv = ds.Tables[0].DefaultView; dv.RowFilter = "key = '" + key + "'"; if(dv.Count > 0) //key found, show the value return dv[0]["value"].ToString(); else //key not found return default value return defaultValue; } 

It’s pretty simple. Get a DataSet, which contains all the resource strings for this ASPX page. Then filter out our text string based on the key. If the key isn’t found, the default value is returned.

private DataSet GetResourceDS() { //Try to get the DataSet from the Cache first //Note that this one bombs at design time, (we have no HttpContext) //but this exception is caught in the custom control DataSet ds = (DataSet)HttpContext.Current.Cache[HttpContext.Current. Request.CurrentExecutionFilePath]; if(ds == null) { //no DataSet in the Cache, get a new one and store it in the cache //get the text-id from the resource file for our web-page ds = new DataSet("resourceStrings"); //build the name of the xml file from the name of the web // page we're sitting in String fileName = HttpContext.Current. Request.CurrentExecutionFilePath; fileName = fileName.Substring(0, fileName.LastIndexOf(".")) + ".xml"; try { //read ds from the xml file ds.ReadXml(HttpContext.Current.Server.MapPath(fileName)); } catch (System.IO.FileNotFoundException) { //xml file not found, we will return the empty DataSet } //put in Cache with dependency to the xml file HttpContext.Current.Cache.Insert(HttpContext.Current. Request.CurrentExecutionFilePath, ds, new CacheDependency(HttpContext.Current. Server.MapPath(fileName))); } return ds; } 

The GetResourceDS method is longer, but not too complicated. First it tries to get the DataSet from the built in .NET Cache object, which is very cool. As a Cache-key I use the name of the ASPX page. If the DataSet wasn’t in the Cache, I create a new DataSet and read in the content from the XML file, which should have the same name as the ASPX page where the TextControl is used (as explained earlier).

The cached DataSet should expire whenever someone updates the XML file, right? So, therefore I add a cache-dependency, which points at the XML file. So now, when the file is updated, the Cache object detects this and removes the cached DataSet from its memory.

The ASP.NET Cache might be even more useful if you decide to put all your texts into one big, single XML file. There is no reason to read that XML file every time a TextControl is rendered, right?

Using the code

To use the TextControl from VisualStudio.NET, you need to add the assembly containing the control to the toolbox. Load up VisualStudio.NET and create a new ASP.NET application. Then add a WebForm and bring it up in design view. Now right click in the Toolbox containing all the other built-in server controls and select “Customize Toolbox...” from the popup menu. Now select the tab named “.NET Framework Components” and use the “Browse...” button to find the DLL containing the TextControl. The sample project has the TextControl in the MyControls.dll assembly.

VS.NET will automatically find and activate the TextControl inside the MyControls DLL. You can put the control on any of the different tabs in the Toolbox, but I prefer to have it together with the other server controls.

The TextControl is now ready for use on a web page. Create a WebForm called WebForm1.aspx and drag/drop the TextControl from the Toolbox on it in design view. Select the TextControl on the ASPX page and look at the property dialog. Change the Key property to “key1” (for example) and optionally put some text in the Text property. It should look similar to this in the HTML view:

<form id="Form1" method="post" runat="server"> <cc1:TextControl id="TextControl1" runat="server" Key="key1"> </cc1:TextControl> </form> 

Note that VS.NET automatically added a reference to MyControls in the project view and also added code at the top of the file for registering the custom control:

<%@ Register TagPrefix="cc1" Namespace="MyControls" Assembly="MyControls" %>

The only thing to do now is to create the XML file containing the resource strings. Just add a new XML file called WebForm1.xml in the same directory as your newly created ASPX page. The XML file is pretty simple and looks like this:

<?xml version="1.0" encoding="utf-8" ?> <resourceStrings> <string> <key>key1</key> <value>sampleValue 1</value> </string> <string> <key>key2</key> <value>sampleValue 2</value> </string> </resourceStrings> 

Each string node in the XML file makes up a key/value pair in the resource file.

The web page is ready for testing and should show a plain web page with the text “sampleValue 1” on it. If you change the text “sampleValue 1” in the XML file into something else and reload the page, it should show the new value at once.

The XMLResource class can also be used from code behind to get and set texts at run-time. Just call the public method GetValueFromKey() from the code behind:

//set the text of the label in run-time Label1.Text = new MyControls.XMLResource().GetValueFromKey("key2", Label1.Text); 

Conclusion

As I wrote earlier, there are lots of things you could (and should) do with this code to use it in production, but I hope this little article can get some of you into making your own controls, which is very fun and really, really useful. I recommend doing similar controls for Buttons, Hyperlinks and so on, the owner of the web site might want to change the texts of all the submit buttons to SAVE or DO IT...

Some things that I’ve added to this control in a production site:

  • XML Schema for validating the XML resource file (never trust a customer to write perfect XML ;)
  • Password protected web form for updating the XML texts

1 Comment

  • Perhaps I have missed something here, but this article seems to overlap the functionality of .NET resources. Using local resource strings, you are able to display text dependent on the user's locale.

    For example, in a recent project I was able to use resources to vary the welcome text displayed to a user. Furthemore, because the resource files are XML files I was able to use XPath queries to transform the data into a format that could easily be displayed in a gridview. The gridview was editable for administrators and any changes made would be saved to the XML file and would be effective immediately.

Comments have been disabled for this content.