in

CodeSmith Community

Your Code. Your Way. Faster!

.netTiers Team Blog

  • Creating strongly typed repeater

    When you develop ASP.Net application, you quickly discover that the basic ASP:Repeater is a killer control, although it looks quite dumb at first look, compared to DataList or the powerfull GridView, but actually its simplicity, and it's lightness offer the flexibility required to do all those funcky stuff you only find in enterprise developpement ...

    Ok, in this perfect world there is yet a dark side: the repeater is a late binding control and use a lot of reflection, which make things a lot more slower, and unhandy, because you do not have auto completion... So my idea was to generate some strongly typed Repeater, as part of the .netTiers framework. A repeater will be generated for each Table and View, and will work in conjunction with the actual strongly typed DataSource.

    Templated control

    To create these repeaters, we actually create a ASP.NET templated control, with the following ITemplate ( to match standard repeater), i mean HeaderTemplate, ItemTemplate, AlternatingItemTemplate and FooterTemplate. Here is the code of my repeater for a simple "Product" entity:

    ParseChildren(true)]
    [ToolboxData("<{0}:ProductRepeater runat=\"server\"></{0}:ProductRepeater>")]
    public class ProductRepeater : Control, System.Web.UI.INamingContainer
    {
    public ProductRepeater()
    {
    }
    
    public override ControlCollection Controls
    {
    get
    {
    this.EnsureChildControls();
    return base.Controls;
    }
    }
    
    private ITemplate m_headerTemplate;
    [Browsable(false)]
    [TemplateContainer(typeof(ProductItem))]
    [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
    public ITemplate HeaderTemplate
    {
    get { return m_headerTemplate; }
    set { m_headerTemplate = value; }
    }
    
    private ITemplate m_itemTemplate;
    [Browsable(false)]
    [TemplateContainer(typeof(ProductItem))]
    [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
    public ITemplate ItemTemplate
    {
    get { return m_itemTemplate; }
    set { m_itemTemplate = value; }
    }
    
    
    private ITemplate m_altenateItemTemplate;
    [Browsable(false)]
    [TemplateContainer(typeof(ProductItem))]
    [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
    public ITemplate AlternatingItemTemplate
    {
    get { return m_altenateItemTemplate; }
    set { m_altenateItemTemplate = value; }
    }
    
    private ITemplate m_footerTemplate;
    [Browsable(false)]
    [TemplateContainer(typeof(ProductItem))]
    [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
    public ITemplate FooterTemplate
    {
    get { return m_footerTemplate; }
    set { m_footerTemplate = value; }
    }
    
    [Category("Data")]
    public virtual string DataSourceID
    {
    get
    {
    if (ViewState["DataSourceID"] == null)
    {
    return string.Empty;
    }
    return (string)ViewState["DataSourceID"];
    }
    set
    {
    ViewState["DataSourceID"] = value;
    }
    }
    
    System.Collections.IEnumerable m_currentView;
    
    private System.Collections.IEnumerable ConnectToDataSourceView()
    {
    if (m_currentView == null)
    {
    NetTiers.SQLiteDemo.Web.Data.ProductDataSource datasource = null;
    Control ctl = this.Page.FindControl(DataSourceID);
    if (ctl == null)
    {
    throw new System.Web.HttpException("Datasource does not exists");
    }
    datasource = ctl as NetTiers.SQLiteDemo.Web.Data.ProductDataSource;
    if (datasource == null)
    {
    throw new System.Web.HttpException("Datasource must be data control");
    }
    
    System.Collections.IEnumerable dsv = datasource.GetEntityList(); //this.DataMember);
    if (dsv == null)
    {
    throw new System.Web.HttpException("View not found");
    }
    m_currentView = dsv;
    }
    return m_currentView;
    }
    
    
    protected override void CreateChildControls()
    {
    if (ChildControlsCreated)
    {
    return;
    }
    Controls.Clear();
    
    System.Collections.IEnumerable datas = (System.Collections.IEnumerable)ConnectToDataSourceView();
    
    if (datas != null)
    {
    if (m_headerTemplate != null)
    {
    Control headerItem = new Control();
    m_headerTemplate.InstantiateIn(headerItem);
    Controls.Add(headerItem);
    }
    
    int pos = 0;
    foreach (object o in datas)
    {
    NetTiers.SQLiteDemo.Entities.Product entity = o as NetTiers.SQLiteDemo.Entities.Product;
    ProductItem container = new ProductItem(entity);
    
    if (m_itemTemplate != null && (pos % 2) == 0)
    {
    m_itemTemplate.InstantiateIn(container);
    }
    else
    {
    if (m_altenateItemTemplate != null)
    {
    m_altenateItemTemplate.InstantiateIn(container);
    }
    else if (m_itemTemplate != null)
    {
    m_itemTemplate.InstantiateIn(container);
    }
    else
    {
    // no template !!!
    }
    }
    Controls.Add(container);
    pos++;
    }
    
    if (m_footerTemplate != null)
    {
    Control footerItem = new Control();
    m_footerTemplate.InstantiateIn(footerItem);
    Controls.Add(footerItem);
    }
    ChildControlsCreated = true;
    }
    }
    
    protected override void OnPreRender(EventArgs e)
    {
    base.DataBind();
    }
    
    #region Design time
    
    internal string RenderAtDesignTime()
    {
    return "TODO create a designer";
    }
    
    #endregion
    }
    

     

    The code is a bit long but it's mostly due to the templates properties. The main logic is in the two methods "ConnectToDataSourceView" and "CreateChildControls". The first take the given datasourceId and search for the control in the page, then it get the entity list from it. The second a bit more tricky, it creates the children controls depending of the differents templates. Please note that for the ItemTemplate and AlternatingItemTemplate we loop on each entity, we create a container for this entity and send it to the current ITemplate to render it.

     

    Strongly typed TemplateContainer

     

    It's probably not obvious here, but one of the most intersting stuff is the TemplateContainer attribute on the ITemplate properties: we use them to indicate the use of strongly typed template container. Here is the code of the Product Template container:

     

     

    <">
    [System.ComponentModel.ToolboxItem(false)]
    public class ProductItem : System.Web.UI.Control, System.Web.UI.INamingContainer
    {
    private NetTiers.SQLiteDemo.Entities.Product _entity;
    
    public ProductItem()
    : base()
    { }
    
    public ProductItem(NetTiers.SQLiteDemo.Entities.Product entity)
    : base()
    {
    _entity = entity;
    }
    
    [System.ComponentModel.Bindable(true)]
    public System.Int64 Id
    {
    get { return _entity.Id; }
    }
    [System.ComponentModel.Bindable(true)]
    public System.String Name
    {
    get { return _entity.Name; }
    }
    [System.ComponentModel.Bindable(true)]
    public System.String Description
    {
    get { return _entity.Description; }
    }
    [System.ComponentModel.Bindable(true)]
    public System.Int64 CategoryId
    {
    get { return _entity.CategoryId; }
    }
    
    }
    

     

    The template container acts as a decorator, it takes our entity in the constructor, and exposes the entity properties as container properties: that the "magic" part, now we have a strongly typed Container in our web page, no more Container.Eval("Name"), but <%# Container.Name %>, with full intellisense !!! and of course, no more reflection.

    Use the repeater

    as we've said the control is very friendly and offer intelisense on the child templates, and on the container properties, here is a sample of use:

     

     

    <data:ProductRepeater
    ID="productRepeater1"
    runat="server"
    DataSourceID="productDataSource1" >
    <HeaderTemplate>
    <h1>List of product</h1>
    </HeaderTemplate>
    <ItemTemplate>
    <b><%# Container.Name %></b><br />
    <%# Container.Description %>
    <hr />
    </ItemTemplate>
    </data:ProductRepeater>
    <data:ProductDataSource
    ID="productDataSource1"
    runat="server"
    SelectMethod="GetAll">
    </data:ProductDataSource>
    

     

     

    Conclusion

    Hopefully this article will help you to write some templated control.
    note: adding a typed designer would be a great improvement, will be for a next session.

    You'll find attached a zip file containing the corresponding codesmith template.

    John Roland
    http://www.serialcoder.net/
    http://www.nettiers.com/
    http://predicatet.blogspot.com/

  • Music i like to share with you

    Hi,

    Some ad today for some friends of mine (actually my former band) :-)

    if you like band like Tool, One minute Silence, Alice in chains, etc. you should get the new Third Eye Machine album, as they have decided to offer it as a free download, get it at:

    Million Reasons download

    please feel free to post comment about the album here. and sorry for the totally off topic post, but hey, we need music to code !!!

  • TList<Entity> (Tips and Tricks)

    TList<Entity> (Tips and Tricks)

    Utilizing many new features from the .Net 2 framework (Generics, Delegate and Anonymous Methods) we were able to greatly improve some core functionalities of the templates including Searching, Filtering and Sorting.

    1. Find Methods

    Old Implementation
    When using the Find method in .netTeirs 1, the entity search was done with this kind of method call:

    Method Signature: Find (EntityColumn column, object searchCriteria);

    Example A:
    ProductCollection productCollection = products.Find(ProductColumn.ProductId, 1);

    this looks very clean but actually it's mostly inefficient (try it in a 100x loop, with a 1000 item list ;-) the reason is that it use internally reflection to get the right property and its value, and then do the comparison.

    New Implementation
    In .netTiers2, the Find method has a new overload that take a generic delegate as parameter.

    Method Signature: Find(Predicate<T> match);

    Example B:
    TList<Product> products = DataRepository.ProductProvider.GetAll();

    void UpdateProductQuantity(int productId, int newQuantity)
    {
      Product product = products.Find(delegate (Product p) {return p.ProductId == productId;});
      product.Quantity = newQuantity;
    }

    See how the use of an anonymous method for the predicate allow us to use the current context (here the productId parameter of the UpdateProductQuantity method).
    To search for several items that match the predicate, just use the FindAll method.

    You can find more information about the Predicate Generic Delegate on msdn2.

    2. Filtering

    The Filtering is used to filter a given list of entities, this is very usefull when the list is binded to a visual control like the DataGridView for example.  Filtering is done by providing a set of filter criteria, which is then applied to the list.  The items that DO NOT match the criteria are removed temporarly from the collection's innerList and put in the FilteredItems property. The filter can finally be removed by calling the RemoveFilter() method.

    Old Implementation
    In netTiers1, filtering was done like so:

    Example C:
    products.Filter = "ProductName = 'Sugar' AND CategoryId = 5"; 

    While it tried to emulate a familiar SQL syntax for where clauses, a special parser was evaluating the filter expression, and then use reflection to check property value; the result was a very slow and very limited filtering (due to the filter parser limitations and the heavy use of reflection).

    New Implementation
    With the use of the new Predicate<T> overload, you can do:

    Example D:
    product.ApplyFilter(delegate(Product p) {return p.ProductName == "Sugar" && p.CategoryId ==5;}));


    3. Sorting

    Following a familiar pattern now, with the help of the comparison generic delegate.

    Method Signature:
    Sort(Comparison<T> match);

    We can create more efficent and complex sorting, for example, say that we want to sort the northwind category list based on the child ProductCollection property Count.

    Example E:
    We could do something like the following:

    category.Sort(MyCustomSort);  //uses the MyCustomSort delegate
    ...
    public int MyCustomSort(Category c1, Category c2)
    {
           return c1.ProductCollection.Count.CompareTo(c2.ProductCollection.Count);
    }

    Similarly, like in the previous examples of Find and Filter, we could've also used an anonymous delegate.
    category.Sort(
          MyCustomSort(Category c1, Category c2){
               return c1.ProductCollection.Count.CompareTo(c2.ProductCollection.Count); 
         });
    You can find more information about the Comparing and Generic delegates on MSDN2.

    4. Action

    This last point is dedicated to a small tip i really like :-)
    The Action generic delegate that is used on the ForEach method of the TList collections.

    Example F:
    category.ProductCollection.ForEach(delegate(Product p) {Console.WriteLine(p.ProductName);});

    Example G:
    int total = 0;
    order.OrderDetailCollection.ForEach(delegate(OrderDetail od) {total += od.Quantity * od.UnitPrice ;});

    You can find more information about the Action Generic Delegate on MSDN2.

    That's all!   Good Luck!
    Cheers!

    John Roland / Robert Hinojosa - NetTiers.com

Copyright © 2008 CodeSmith Tools, LLC
Powered by Community Server (Commercial Edition), by Telligent Systems