While working on one of my projects which entitled creation of user control in ASP.Net, i was flummoxed with a very interesting problem. The problem was very simple and no doubt there was a straight forward answer to it, but i was not satisfied. The problem is for initializing properties for control dynamically.
The user control is generic, but there is a fixed set of properties which are requierd for a certain behaviour. Values for each property for a given behaviour is stored in an XML config file. The user control will have a unique identifier which will point to the correct configuration.
Lets dive into the problem space.
Say there is a user control which has different types of properties exposed. The properties themselves are of different data types (for e.g. int, boolean, string, string array, enums. Pretty complex control, I know
, but this is a grind you need to go make things generic). For illustration let us assume a realistic scenario, where this kind of complex situation may arise (there are very few of them though
)
For illustration we shall assume a very simple scenario. We shall develop a user control for drawing a shape (2-Dimensional)
The shape control will have the following properties
- Type
- Enum type
- Type of shape (Circle, rectangle, polygon etc)
- No of Sides
- Integer
- in case of polygon type
- Length of each side
- Array of strings (comma seperated string values)
- in case of polygon type
- Scale
- Enum type
- The type of mathematical scale to use for lengths(Centimeter, Millimeter, Inches, meters)
- some more properties
To initialize the properties of this control in the consuming aspx/ascx we would usually do something as suggested below
<Aryan:Shape runat="server" Type="Polygon" Sides="5" Length="20,45,45,60,50" Scale="Centimeter"></Aryan:Shape>
We wont go into details of how the properties are implemented, for our little pertains to initializing the properties dynamically.
The XML configuration snippet looks like below (for instance Shape type – Square)
So now if we wanted to refer to the configuration file to read th
<Shapes>
<Shape ID="Square">
<Property Name="Type" Value="Polygon"></Property>
<Property Name="Sides" Value="4"></Property>
<Property Name="Length" Value="20,20,20,20"></Property>
<Property Name="Scale" Value="Inches"></Property>
</Shape>
</Shapes>
e property values, we would do something like this
<Aryan:Shape runat="server" ShapeConfigID="Square"></Aryan:Shape>
Makes my consuming page less cluttered and i can reuse my configuration across multiple pages, effectively allowing to centralize my changes. sweet
How the values are initialized is what we are concerned and the crux of the post.
For initializing the values we need to do the following
- Read xml
- Read property value
- Assuming property value exists cast the property value to appropriate datatype and assign the value.
/// <summary>
/// Reads the configuration information for the given shape and returns the value
/// for the property provided by PropertyName
/// </summary>
/// <param name="PropertyName">Name of the property whose value need to be read</param>
/// <returns></returns>
private String GetPropertyValue(String PropertyName)
{
//read the xml file or the xml segment
//assuming we have the proper xml segment
//get to the property node suggested by property name
//XPath = "//Shape/Propert[@Name='<PropertyName>']"
//read the node and return value
}
The function is pretty simple and i wont go into details of the implementation
string sides = GetPropertyValue("Sides");
int intsides = -1;
Int32.TryParse(sides, out intsides);
//set the property
shp.sides = intsides;
The steps repeats for the other properties, based on the datatype the code changes. If the number of properties increase you can imagine the horror of the situation.
This type of code gives me the chills in any case, something needs to be done. I wanted something elegant.
This is how i did it, may be it is not the most fullproof, but it works and it reduces the lines of code i would otherwise have to write. I stumbled upon the solution during one of my rendezvous on the net. The solution uses Generics and another concept TypeConverters.
There are whole lot of type converters defined which reads a string value and tries to convert the value to the appropriate datatype. There are whole lot of methods provided in the typeconverter class, which can be used, the one which i use is ConvertFromInvariantString method, which takes string as an argument and tries to convert it into the appropriate datatype. Here is the method
/// <summary>
/// Convert the string value to the appropriate datatype suggested by T
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dataValueString"></param>
/// <returns></returns>
public T GetDatafromString<T>(string dataValueString)
{
//Initialize the value to return a default value
T returnValue = default(T);
//Get the Type for the return datatype; the Type will be used to get the appropriate converter
Type valueType = typeof(T);
//Get the converter for the given datatype
TypeConverter converter = TypeDescriptor.GetConverter(valueType);
//Convert the string to the appropriate data type value;
//the value returned will be a an object which needs to be cast to the required datatype
returnValue = (T)converter.ConvertFromInvariantString(dataValueString);
return returnValue;
}
Inorder to return the appropriate value for a given datatype with the value returned from XML file, the method below will do the bulk of the work
private T GetPropertyValue<T>(String PropertyName)
{
//read the xml file or the xml segment
//assuming we have the proper xml segment
//get to the property node suggested by property name
//XPath = "//Shape/Propert[@Name='<PropertyName>']"
//read the node
//if the node contains value get the value
return GetDatafromString<T>(dataValueString);
}
Now inorder to initialize the properties of the control, all we need to do is this
...
shp.sides = GetDataFromString<int>("Sides");
...
It even works for enum types, so i no longer need to cast the enum to the appropriate enumtype, so this code will also work as a charm.
shp.Type = GetPropertyValue<Shape>("Shape");
Sweet i say, very sweet.
With little ingenuity you can make it work for string array as well, we shall keep this as an exercise for you guys
Refrences
MSDN : TypeConverter Class
MSDN : An introduction to Generics