In this section of the article we will develop a custom
ExpressionBuilder. This expression builder will be used to retrieve values
based on a key from an XML settings file. Sometimes it seems proper to store
some configuration settings for a web application in a separate XML file for
several reasons, among which is to change the XML settings file without
requiring the .NET Framework to recompile the whole application.
The ExpressionBuilder class is an abstract class where all
the above expression builders extend from. This abstract class contains one
method that should be overridden by any class extending it and this is the GetCodeExpression.
The GetCodeExpression returns the expression to be executed to obtain the
evaluated expression when the Page parser finds a declarative expression and
calls the required ExpressionBuilder. This method returns an expression of type
CodeExpression and part of the CODEDom. The reason behind using the CodeDom is
that, during the Page parsing, when a declarative expression is found, the
corresponding ExpressionBuilder executes the GetCodeExpression and the return
value is just a CodeExpression that will be embedded into the compiled ASP.NET
class and then all together compiled and executed. The resulting class is
stored in the ASP.NET Temporary folder. Since the ExpressionBuilder might be
used in an ASP.NET web application that is either built using Visual Basic .NET
or C#, the GetCodeExpression must be language neutral and the CodeDom must be
integrated with any language set for the web application.
The GetCodeExpression has a major parameter that you will be
using caled entry. This object represents the property that the current
expression is bound to. For example, look at the following declarative
expression.
Listing 5
<asp:Label ID="lblPath" runat="server" Text='<%$ AppSettings: WebPath %>' />
The entry parameter is Text property of the Label server
control.
In addition, if you want your declarative expressions to be
evaluated with no-compile pages, you should override the EvaluateExpression
method and the SupportsEvaluate property so that the expressions will be
evaluated even before compiling your web applications. More on those methods
and properties will be shown in the section below where we create the custom
XMLSettingExpressionBuilder.
XML Settings file
The ExpressionBuilder that we will develop will interact
with an XML file. The XML file has the following structure.
Listing 6
<?xml version="1.0"?>
<xmlsettings>
<add key="WebPath" value="http://www.mywebsite.com" />
</xmlsettings >
The expression builder shall be able to retrieve values from
the above XML settings file by using the key values as a look up key for each
of the configurations listed in this file.
XMLSettingExpressionBuilder
As mentioned above, to develop a new ExpressionBuilder we
should extend the ExpressionBuilder abstract class and override the major
method which is the GetCodeExpression.
The header of the XmlSettingExpressionBuilder is as follows.
Listing 7
public class XmlSettingExpressionBuilder : ExpressionBuilder
{
The GetCodeExpression is a method that will be called during
the page execution to get the expression that is going to be executed when it
is time to bind the declarative expression on the page.
Listing 8
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry,
object parsedData, ExpressionBuilderContext context)
{
CodeTypeReferenceExpression thisType = new CodeTypeReferenceExpression
(base.GetType());
CodePrimitiveExpression expression = new CodePrimitiveExpression
(entry.Expression.Trim().ToString());
string evaluationMethod = "GetXmlSetting";
return new CodeMethodInvokeExpression(thisType, evaluationMethod, new
CodeExpression[]
{
expression
}
);
}
The first line of this method gets a reference to the
XmlSettingExpressionBuilder class because we are going to execute a method on
this class that will evaluate the required expression.
Then, it creates a new expression based on the KEY specified
in the declarative expression on the page which will be used as an input to the
method that will evaluate the required expression.
A method name that will do the real evaluation is stored in
a string variable. Finally, the method returns the expression that will invoke
a method named GetXmlSetting, which is a static method responsible for
evaluating the expression. The CodeMethodInvokeExpression class has been used
for this purpose and takes as input the type on which to execute the method
specified the method name, as well as an array of type CodeExpression that
represents the parameters of the method called.
As a summary, the GetCodeExpression returns a CodeDOM method
invocation expression so that this method invocation will be added to the page
compiled class and executed later on when the whole page executes.
We have mentioned the method GetXmlSetting which is
responsible for loading the data based on the key specified in the declarative
expression. The implementation of that method is as follows.
Listing 9
public static string GetXmlSetting(string expression)
{
XmlDocument xmlSettingDoc = (XmlDocument)HostingEnvironment.Cache[
"XmlSettings"];
if (xmlSettingDoc == null)
{
xmlSettingDoc = new XmlDocument();
string settingsFile = HostingEnvironment.MapPath("~/XmlSettings.config");
xmlSettingDoc.Load(settingsFile);
CacheDependency settingsDepend = new CacheDependency(settingsFile);
HostingEnvironment.Cache.Insert("XmlSettings", xmlSettingDoc,
settingsDepend);
}
string getXPATHKey = String.Format("//add[@key='{0}']", expression);
XmlNode wantedRecord = xmlSettingDoc.SelectSingleNode(getXPATHKey);
if (wantedRecord != null)
return wantedRecord.Attributes["value"].Value;
return "Unable to Process Expression";
}
The GetXmlSetting method evaluates the declarative
expression by accessing the XMLSettings.config file and retrieving the value
corresponding to the key specified in the above mentioned expression.
The method simply loads the XmlSettings.config file into an
XmlDocument. If this file has been loaded before, it is checking if it is still
placed in the application cache. If not, it loads the file into the XmlDocument
then adds it to the application cache to gain some performance points by
caching the file content.
An XPATH expression is built based on the add record whose key
attribute value is the same as the one specified in the declarative expression.
Finally, the value attribute value is returned as if we had a lookup based on
the key attribute. If there was no such key, we could have simply thrown an
exception, but in this case we simply returned a warning message that the
expression could not be evaluated properly.
Now the picture is clear. The GetCodeExpression returns a
CodeDOM expression that invokes the method GetXmlSetting. When the page
executes, it finds a call to the GetXmlSetting, it invokes this method and the
returned value replaces the declarative expression on the page.
In addition, if you wanted to enable the Expression Builder
in no-compile pages, another method should be overriden together with a public
property.
Listing 10
public override object EvaluateExpression(object target, BoundPropertyEntry
entry, object parsedData, ExpressionBuilderContext context)
{
// Call the GetXmlSetting method --> evaluate the expression
return GetXmlSetting(entry.Expression);
}
public override bool SupportsEvaluate
{
get
{
return true;
}
}
The EvaulateExpression method simply returns a call to the
GetXmlSetting method. The SupportsEvaluate property returns true, meaning that
the ExpressionBuilder is enabled on no-compile pages.
How to use the XmlSettingExpressionBuilder
The first thing to do is to place the
XmlSettingExpressionBuilder class in the App_Code folder. Next, add the
following section to the web configuration file under the compilation section.
Listing 11
<compilation debug="true">
<expressionBuilders>
<add expressionPrefix="XmlSettings"
type="BilalHaidarLibrary.XmlSettingExpressionBuilder"/>
</expressionBuilders>
</compilation>
We have registered the new ExpressionBuilder in the web
configuration file and now the web application is ready to use it.
On a new WebForm you simply add the following server
control.
Listing 12
<asp:literal ID="expressionTest" runat="server" Text='<%$ XmlSettings: WebPath %>' />
The .NET runtime will take the XmlSettings keyword, create a
new instance of the XmlSettingExpressionBuilder and evaluate the expression; in
this case it is the value of the key attribute of each record in the
XmlSettings.config file.