PDA

View Full Version : Creating a Generic Store



Fredrik
Nov 29, 2010, 8:29 AM
After writing (more than) a few pages with Ext.Net I found that mapping datasource RecordFields in Stores was both tedious and error-prone. You always have to remember to update the RecordFields of the store when you add a new property to the datasource.

To make things a bit better I decided to create a generic store that automatically generates the RecordFields from the properties of the datatype object (DTO).

To make a generic store we need a generic reader. Since all my datasources generate json I base my generic reader on JsonReader. By using reflection to extract the properties of the DTO it's easy to generate the needed RecordFields.



public class JsonReader<T> : JsonReader
{
public JsonReader()
{
//Read the properties from the Dto and create a corresponding RecordField
PropertyInfo[] properties = typeof(T).GetProperties();
this.Fields.Clear();
foreach (PropertyInfo p in properties)
{
//Ignore properties with attribute JsonIgnore
if (!haveJsonIgnoreAttribute(p))
this.Fields.Add(CreateRecordField(p));
}
}

private bool haveJsonIgnoreAttribute(PropertyInfo p)
{
return p.GetCustomAttributes(typeof(Newtonsoft.Json.JsonI gnoreAttribute), true).Length > 0;
}

private RecordField CreateRecordField(PropertyInfo p)
{
RecordField rf = new RecordField(p.Name);
Type type = p.PropertyType;
if (type == typeof(string))
rf.SubmitEmptyValue = EmptyValue.Null;
rf.Type = GetType(type);
return rf;
}

private RecordFieldType GetType(Type type)
{
if (type == typeof(int))
{
return RecordFieldType.Int;
}
else if (type == typeof(double))
{
return RecordFieldType.Float;
}
else if (type == typeof(DateTime))
{
return RecordFieldType.Date;
}
else if (type == typeof(string))
{
return RecordFieldType.String;
}
else if (type == typeof(bool))
{
return RecordFieldType.Boolean;
}
else
{
return RecordFieldType.Auto;
}
}
}


After creating a generic JsonReader, it's very easy to create a generic store.



public class JsonStore<T> : Store
{
public JsonStore()
{
this.Reader.Add(new JsonReader<T>());
}
}


This is all well as long as you create the store in codebehind. But what if you want to create it directly in the aspx markup? ASP.NET has no direct way of declaring generic objects in the markup.
The solution is to use a less known feature called ControlBuilder. A ControlBuilder is a class that governs how a server control is parsed when it is used declaratively on an ASP.NET page.

The first step is to create a new non-generic class JsonStoreGeneric that inherits from our generic store. JsonStoreGeneric is extended with the property ObjectType that will hold the type of the generic argument (our DTO). JsonStoreGeneric is the class that we declare in the markup.



[ControlBuilder(typeof(GenericControlBuilder))]
public class JsonStoreGeneric : JsonStore<Object>
{
private string _objectType;
public string ObjectType
{
get
{
return _objectType ?? String.Empty;
}
set
{
_objectType = value;
}
}
}


With the ControlBuilder attribute we tell ASP.NET to use the class GenericControlBuilder when generating the control. GenericControlBuilder looks at the ObjectType property and transforms JsonStoreGeneric to a JsonStore<ObjectType> behind the scenes. I found the code for GenericControlBuilder online so I can't take credit for it, but here it is in its full glory:



// Custom ControlBuilder that instantiates the generic control when the page
// is constructed.
public class GenericControlBuilder : ControlBuilder
{
public override void Init(TemplateParser parser, ControlBuilder parentBuilder, Type type,
string tagName, string id, IDictionary attribs)
{
Type newType = type;
if (attribs.Contains("objecttype"))
{
// If objecttype is specified, create a generic type that is bound to that
// argument and then hide the objecttype attribute.
Type genericType = type.BaseType.GetGenericTypeDefinition();
Type genericArg = Type.GetType((string)attribs["objecttype"], true, true);
newType = genericType.MakeGenericType(genericArg);
attribs.Remove("objecttype");
}
base.Init(parser, parentBuilder, newType, tagName, id, attribs);
}
}


And now it's a one-liner to declare a store in markup no mather how many properties the datasource have.



<ext:gridpanel id="GridPanel1" runat="server" striperows="true" title="GridPanel With Generic Store"
trackmouseover="true" width="600" height="350" autoexpandcolumn="Name">
<Store>
<cc1:JsonStoreGeneric ObjectType="DataAccess.EmployeeDto,DataAccess" ID="Store1" runat="server" />
</Store>
<ColumnModel runat="server">
<Columns>
<ext:Column ColumnID="Name" Header="Name" DataIndex="Name" />
<ext:Column ColumnID="Age" Header="Age" DataIndex="Age" />
<ext:Column ColumnID="Salary" Header="Salary" DataIndex="Salary" />
<ext:Column ColumnID="IsOnVacation" Header="IsOnVacation" DataIndex="IsOnVacation" />
<ext:DateColumn ColumnID="Birthday" Header="Birthday" DataIndex="Birthday" />
</Columns>
</ColumnModel>
<SelectionModel>
<ext:RowSelectionModel runat="server" SingleSelect="true" />
</SelectionModel>
</ext:gridpanel>


You can continue to build further on this making the entire gridpanel generic. It all depends on your needs.

See link in reply below for full solution.

Fredrik
Nov 29, 2010, 4:29 PM
You can find the solution here in both .zip and .7z archives:

http://cid-d19a3a6346c64870.skydrive.live.com/redir.aspx?resid=D19A3A6346C64870!105&authkey=5USYAfq*ozQ%24

geoffrey.mcgill
Nov 29, 2010, 5:30 PM
Hi Fredrik,

Very cool. Thanks for sharing.