Click here to Skip to main content
15,867,686 members
Articles / Web Development / ASP.NET
Article

User Interface generator for ASP.NET database applications

Rate me:
Please Sign up or sign in to vote.
3.91/5 (14 votes)
17 Sep 20065 min read 91.5K   3.4K   52   11
In web applications, many pages have simple CRUD purposes. This presentation shows how to produce these pages without a single line of code by generating them with description files.

List.aspx - Sample generated list page

Introduction

Most of database application UI represent data as a combination of lists and forms. The pages are very often the same : search criteria (form), result table (list) with add/edit/delete buttons, and data detail (form) with ok/cancel button. This is especially the case for admin pages (reference table like countries / cities) without much business rules (just ensuring the relational integrity).

A standard approach requires to develop specific code for each table and page (data object + sql requests + aspx + code behind).
The solution described in this article consists in :
  • writing all that is specific into one or two description files : meta data (table name, columns, sql commands) + rendering info (labels, edit types, layout,...)
  • generating on the fly the pages for the list and the detail (search criteria / result list / master-detail / detail page / buttons) with all these informations

  • In order to develop specific simple pages (list + edit), you just have to write the description files and that's all. No code needed (no class, no aspx, no code behind) !

    Similar articles on CodeProject :
    Universal Database Admin for ASP.NET and SQL Server
    Database Viewer and Updater for any Database
    Database Navigator
    Difference : generates end-user UI, not only for DBA
    Go to online demo

    Detail.aspx - Sample generated detail page
    Detail.aspx - Sample generated detail page

    Features

  • Generic data objects (table / record), data info (metadata) and rendering info (how to display a table or a record depending on the context)
  • Interfaces for retrieving and saving the datas : Reader / Writer. The standard reader and writer access the database but since it's an interface they can be replaced (for exemple LDAP requests or CSV/XLS files)
  • Generator on the fly for list / record pages and automatic data binding
  • Multi-language resources, but also multi-target, multi-user role, multi-client
  • ASP.NET 1.1 and 2.0, written in C# for .NET 1.1 and 2.0
  • ADO.NET : Oracle, MySql, Access, SqlServer,...
  • Possible customization of controls : adding edit types for your specific needs and the generator would create your web controls
  • Logging with Log4net (encapsulated so it can be replaced easily)
  • WindowsForms : to do !

  • This is mainly for IT developers, not yet for non-developers. Simples pages can be generated without effort. It also can be used for more complex pages : the developer would inherit the default page and assemble user controls (generated by the tool).

    This tool is supposed to remain relatively lightweight, simple and extensible. I'm not a fan of complex architectures especially for simple functionnalities like CRUD. But I think it can be used with other frameworks : O/R mapping (like NHibernate) by reflection, controller (like Maverick or NStruts) by interface (the controller can be replaced).

    Description files

    The description files consists in :
  • a module file : sql commands / table / columns / data types and rules / relations
  • one or many resource file : rendering properties like labels, visible, enabled, edit types (text box, combo-box, radio buttons, check-box, ...), access control (can read / insert / update / delete depending on user role), texts,...

  • These are simple text files with sections (like .ini with parameters in sections) because they are more human friendly. In future versions, there should be XML files (if possible compliant to SOAP or NHibernate DTD) and even a Windows UI for generating and editing these files.

    There can be a resource file for each language. The rendering properties may depend on the language, the action (view/edit/new/…), the target (web/windows/pocket), the user role (admin / guest / …) and the client base (if you want to distribute the same application to different customers with different parameters).

    #---- module file for currency table ------------------------------------
    [sql]
    select = select * from currency
             order by cur_cd
    selectOne = select * from currency
                where cur_id = ?cur_id
    checkBeforeDelete = select count(*) from country where cur_id = ?cur_id
    #------------------------------------------------------------------------
    [table]
    name = currency
    primaryKeys = cur_id
    #------------------------------------------------------------------------
    [column name=cur_id #nullable=0]
    [column name=cur_cd type=VA3 nullable=0]
    Unique = 1
    [column name=cur_name domain=name nullable=0]
    [column name=cur_name_2 domain=name nullable=0]
    [column name=cur_name_3 domain=name]
    [column name=cur_type type=N2 nullable=0]
    [column name=cur_status type=B nullable=0]
    [column name=cur_rate type=N10,5]
    [column name=cur_creation_date type=D]
    #------------------------------------------------------------------------
    [relation columns=cur_type]
    Reference = currency-type
    ReferenceColumns = cur_type
    DisplayField = cur_type_name
    See screens below
    #---- resource file for currency table (english version) ----------------
     [TableRender]
    ListTitle = List of currencies
    ColumnOrder = cur_cd, cur_name_2, cur_type, cur_status, cur_rate
    EditOrder = cur_cd, cur_name_2, cur_name, cur_name_3, cur_type, cur_status, cur_rate, cur_creation_date
    EditTitle = Currency detail : {cur_name_2}
    
    [TableRender action=new]
    EditTitle = New currency
    #------------------------------------------------------------------------
    [ColumnRender name=cur_cd]
    Label = Code
    Link = 1
    [ColumnRender name=cur_name]
    Label = Name (french)
    [ColumnRender name=cur_name_2]
    Label = Name
    [ColumnRender name=cur_name_3]
    Label = Name (german)
    [ColumnRender name=cur_rate]
    Label = Exchange rate (USD)
    [ColumnRender name=cur_type]
    Label = Type
    RelationDisplayField = cur_type_name_2
    [ColumnRender name=cur_status]
    Label = Accepted payment
    [ColumnRender name=cur_creation_date type=D]
    Label = Creation date
    There can be expressions. For exemple :
    # the column "cur_name_2" will be retrieved from the record
    EditTitle = Currency detail : {cur_name_2}
    
    # record will be updateable only if the IsAdmin property
    # in the session is set (either by param/key or by reflection)
    EditUpdateable = {session.IsAdmin}
    The layout can be more complex :
    # adds a sublist below the main form
    [TableRender]
    DetailLists = currency_country
    ActiveDetailList = 0
    
    # the layout can be a complex hierarchy of tables / rows / cells
    [EditRender]
    Layout = row1, row2, row3, row4, row5, row6, row7
    row1 = cur_cd
    row1.BackColor = magenta
    row2 = cur_name_2
    cur_name.width = 100px
    row3 = cur_name, cur_name_3
    cur_name_2.width = 100px
    cur_name_3.width = 100px
    row4 = cell41
    cell41 = label1
    cell41.ColumnSpan = 4
    label1.Text = Currency properties
    label1.ForeColor = #EE00FF
    label1.Width = 200px
    label1.Height = 20px
    row5 = cur_type
    row6 = cur_status, cur_rate
    row7 = cur_creation_date
    Generates Detail.aspx
    Image 3

    Class diagram

    Image 4

  • data objects : Record and RecordTable which are a kind of enhanced DataRow and DataTable, contains them so standard DataTable controls can be used (DataGrid / DataList)
  • data info : RecordInfo with metadata (table / columns properties)
  • relation info : relation between a source and a destination RecordInfo with column correspondance (id and label)
  • render info : RecordRenderInfo depending on a RenderContext (language, action, target, user role and client base)

  • Data access layer : interface for reading and writing records. The default implementation access the database but there can be other implementations for LDAP or CSV / XLS files.
    C#
    public interface IRecordEnumerator : IEnumerator {
      Record CurrentRecord { get; }
    }
    public interface IRecordReader : IRecordEnumerator {
      RecordInfo RecordInfo { get; }
      int Depth { get; }
      bool IsClosed { get; }
      int RecordsAffected { get; }
      void Close();
      DataTable GetSchemaTable();
      bool NextResult();
      bool Read();
      int RowNum { get; }
      object Parameters { get; }
    }
    public interface IRecordWriter {
      void ExecuteWriter(IDbConnection dbConnection, IDbTransaction dbTransaction, Record rec);
      void ExecuteWriter(IDbConnection dbConnection, IDbTransaction dbTransaction, RecordTable rt);  
    }

    Controller / UserControl / Page

    There are UserControls (class + ascx) and Pages (class + aspx) for a record and a list. They implement interfaces so they can be replaced by other classes and controllers. The interface can even serve for WindowsForms.
    The list page (List.aspx) contains a record list (RecordTable or RecordReader), eventually a parameter record (search criteria) and other elements (message label for confirmation / warning / error messages, pagination, buttons).
    <!-- #include file="_Top.inc"-->
      <asp:Label id="lblMessage" runat="server" Visible="false"></asp:Label>
      <form runat="server">
    <%  if (HasVisibleParameters) { %>
        <ucRecord:Record id="paramRecordControl" runat="server"></ucRecord:Record>
        <input type="hidden" name="__action" value="search">
        <asp:Button id="btSearch" runat="server" Text="Search" onclick="btSearch_Click"></asp:Button>
    <%  } %>
        <ucList:RecordList id="listControl" runat="server"></ucList:RecordList>
        <ucPagination:Pagination id="paginationControl" runat="server"></ucPagination:Pagination>
        <asp:Button id="btAdd" runat="server" Text="Add" onclick="btAdd_Click"></asp:Button>
      </form>
    <!-- #include file="_Bottom.inc"-->
    The detail page (Detail.aspx) contains the main RecordControl, a message label, validate / cancel buttons and eventually a sublist (RecordTableControl)
    <!-- #include file="_Top.inc"-->
      <form runat="server" Id="form1">
        <asp:Label id="lblMessage" runat="server" Visible="false"></asp:Label>
        <ucRecord:Record id="recordControl" runat="server" RecordName="record"></ucRecord:Record>
    <% if (RenderContext.IsViewAction || RenderContext.IsViewSummaryAction) { %>
        <asp:Button id="btReturn" causesValidation="false" runat="server" Text="Retour" onclick="btCancel_Click"></asp:Button>
        <asp:Button id="btEdit" runat="server" Text="Modifier" onclick="btEdit_Click"></asp:Button>
    <% }
       else { %>
        <asp:Button id="btCancel" causesValidation="false" runat="server" Text="Annuler" onclick="btCancel_Click"></asp:Button>
        <asp:Button id="btValidate" runat="server" Text="Valider" onclick="btOK_Click"></asp:Button>
    <% } %>
        <asp:ValidationSummary id="validationSummary" runat="server"></asp:ValidationSummary>
        <br>
    <% if (!RenderContext.IsNewAction && RecordRenderInfo.HasDetailList) {
         btAdd.Text = GetText("button.AddSublist");
    %>
        <ucList:RecordList id="subListControl" runat="server"></ucList:RecordList>
        <br>
        <asp:Button id="btAdd" runat="server" Text="Add" onclick="btAddSublist_Click"></asp:Button>
    <% } %>
      </form>
    <!-- #include file="_Bottom.inc"-->
    Controller source (List.aspx.cs)
    C#
    void Page_Init(object sender, EventArgs e) {
      base.Page_Init(sender, e);
      if (HasVisibleParameters) {
        paramRecordControl.Record = ParametersRecord;
        paramRecordControl.BuildControls();
      }
    }
    
    void Page_Load(object sender, EventArgs e) {
      base.Page_Load(sender, e);
      if (HasVisibleParameters) {
        if (!IsPostBack) paramRecordControl.RecordToControls();
        else paramRecordControl.ControlsToRecord();
      }
      ExecuteReader();
      if (IsReaderMode) {
        listControl.RecordReader = RecordReader;
        paginationControl.PageSize = 0;
      }
      else {
        listControl.RecordTable = RecordTable;
        paginationControl.PageSize = RecordRenderInfo.PageSize;
        paginationControl.PageNum = PageNum;
        paginationControl.RowCount = RecordTable.Count;
      }
      btAdd.Text = GetText("button.Add");
      btSearch.Text = GetText("button.Search");
    }
    

    Open source project

    The work is not finished yet. I have many improvements in mind. I need help for continuing the work and also would like to share ideas with the community. So I propose this as an open source project !

    People needed for development, testing, documentation, website administration, translations,... The open source licence would be simple and commercial use would be authorized. Please reply to this thread or write me at the address provided in the online demo.

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here


    Written By
    France France
    IT professional in Paris with 10 years of experience in Windows, client/server, databases (mostly Oracle) and J2EE.
    I have discovered .NET lately and I like it !

    Comments and Discussions

     
    QuestionGreate Pin
    Aladár Horváth4-Aug-15 3:58
    professionalAladár Horváth4-Aug-15 3:58 
    QuestionReally Neat App Pin
    Member 1022975713-Oct-13 1:34
    professionalMember 1022975713-Oct-13 1:34 
    GeneralMy vote of 2 Pin
    ReemaApsingekar24-Jul-09 2:58
    ReemaApsingekar24-Jul-09 2:58 
    QuestionHow to implement this with MS SQL server. Pin
    Deepta11-Jun-09 10:54
    Deepta11-Jun-09 10:54 
    GeneralGetting errors Pin
    ezhilselva3-Jun-08 21:07
    ezhilselva3-Jun-08 21:07 
    GeneralView Records Pin
    nour12325-Oct-06 2:08
    nour12325-Oct-06 2:08 
    Generalhelp on this project Pin
    Dimitri H4-Oct-06 9:55
    Dimitri H4-Oct-06 9:55 
    GeneralAnother suggestion Pin
    mrsnipey19-Sep-06 2:42
    mrsnipey19-Sep-06 2:42 
    GeneralRe: Another suggestion Pin
    Dactylo19-Sep-06 11:33
    Dactylo19-Sep-06 11:33 
    GeneralSuggestion Pin
    AnatolyS18-Sep-06 20:55
    AnatolyS18-Sep-06 20:55 
    GeneralRe: Suggestion Pin
    Dactylo19-Sep-06 11:15
    Dactylo19-Sep-06 11:15 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.