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;
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
{
Text = new XMLResource().GetValueFromKey(Key, Text);
base.Render(output);
}
catch
{
String tmpText = Text;
Text = "[" + Text + "]";
base.Render(output);
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();
DataView dv = ds.Tables[0].DefaultView;
dv.RowFilter = "key = '" + key + "'";
if(dv.Count > 0)
return dv[0]["value"].ToString();
else
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()
{
DataSet ds = (DataSet)HttpContext.Current.Cache[HttpContext.Current.
Request.CurrentExecutionFilePath];
if(ds == null)
{
ds = new DataSet("resourceStrings");
String fileName = HttpContext.Current.
Request.CurrentExecutionFilePath;
fileName = fileName.Substring(0, fileName.LastIndexOf("."))
+ ".xml";
try
{
ds.ReadXml(HttpContext.Current.Server.MapPath(fileName));
}
catch (System.IO.FileNotFoundException)
{
}
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:
="1.0" ="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:
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