Gabe Sumner

Gabe Sumner

The RSS Feed URL cannot be found!
The remote server returned an error: (400) Bad Request.

Back to all posts

Auto-generate a Web Admin Interface using LINQ to SQL

LinqMyAdmin Screen ShotLike a lot of web programmers, I spent years using PHP to create web applications. One of the tools I frequently used in conjunction with PHP was phpMyAdmin . This helpful web-based tool allowed me to easily manage my MySQL databases via a web browser.

After reading Scott Guthrie's "Binding UI using the ASP:LinqDataSource Control" article I became intrigued by the idea of building a phpMyAdmin-like inteface that could utlize any "LINQ to SQL Class". In theory this seemed simple, in practice it involved a lot reflection .

I have a very early build of this running and have provided the code below. Comments are sprinkled throughout the code and explain what is happening. Here is how to create this:

Step 1: Create the ASPX page

Create the following ASPX file: ~/admin/LinqMyAdmin.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="LinqMyAdmin.aspx.cs" Inherits="LinqMyAdmin" %> 
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
 
<html xmlns="http://www.w3.org/1999/xhtml">  
<head runat="server">  
    <title>LinqMyAdmin</title> 
    <link href="./LinqMyAdmin.css" rel="Stylesheet" type="text/css" /> 
</head> 
<body> 
    <form id="form1" runat="server">  
    <div> 
        <div id="contentwrapper">  
        <div id="contentcolumn">  
            <asp:PlaceHolder ID="LinqPlaceHolder" EnableViewState="true" Visible="true" runat="server"></asp:PlaceHolder> 
            <h2 id="TableHeader" runat="server" /> 
            <asp:GridView   
                ID="TableGrid"   
                PageSize = "20" 
                AllowPaging = "true" 
                AllowSorting = "true" 
                EnableViewState = "true" 
                AutoGenerateColumns = "true" 
                AutoGenerateDeleteButton = "true" 
                AutoGenerateEditButton = "true" 
                runat="server">  
                <AlternatingRowStyle CssClass="odd" /> 
                <PagerStyle CssClass="pager" HorizontalAlign="Left" /> 
            </asp:GridView> 
        </div> 
        </div> 
        <div id="leftcolumn">  
            <h2>Tables</h2> 
            <asp:Repeater ID="TablesRepeater" EnableViewState="false" runat="server">  
                <HeaderTemplate> 
                    <ul> 
                </HeaderTemplate> 
                <ItemTemplate> 
                    <li><a href="linqmyadmin.aspx?table=<%# Container.DataItem %>"><%# Container.DataItem %></a></li> 
                </ItemTemplate> 
                <FooterTemplate> 
                    </ul> 
                </FooterTemplate> 
            </asp:Repeater> 
        </div> 
    </div>          
    </form> 
</body> 
</html> 

Step 2: Create the Code-Behind

Create the following code-behind file: ~/admin/LinqMyAdmin.aspx.cs

using System;  
using System.Collections;  
using System.Data.Linq.Mapping;  
using System.Reflection;  
using System.Text.RegularExpressions;  
using System.Web;  
using System.Web.UI.WebControls;  
 
public partial class LinqMyAdmin : System.Web.UI.Page  
{  
    // --------------------------------------------------------------------  
    // CHANGE THE FOLLOWING LINE TO REFER TO YOUR LinqDataContext CLASS  
    // --------------------------------------------------------------------  
    string LinqSQLClass = "Intranet.Data.NorthwindDataContext, App_Code";  
    Type LinqContextType;  
 
    /// <summary>  
    /// Executed upon Page Load.  
    /// </summary>  
    protected void Page_Load(object sender, EventArgs e)  
    {  
        string TableName = Request.QueryString["table"];  
 
        // Display a list of available Tables  
        LinqContextType = System.Type.GetType(LinqSQLClass, true);  
        TablesRepeater.DataSource = GetAllTables(LinqContextType);  
        TablesRepeater.DataBind();  
 
        // If we have a "table" value in our querystring, then display the table.  
        if (TableName != null)  
        {  
            DisplayTable(TableName);  
        }  
    }  
 
    /// <summary>  
    /// Gets a list of all Tables found in a LinqContext  
    /// </summary>  
    /// <param name="_Type">The LinqContext Type</param>  
    /// <returns>list of table names.</returns>  
    private ArrayList GetAllTables(Type _Type)  
    {  
        ArrayList _Tables = new ArrayList();  
        PropertyInfo[] _AllProperties = _Type.GetProperties();  
        foreach (var _PropInfo in _AllProperties)  
        {  
            // LINQ Tables are Generics and will be declared as type System.Data.Linq.Table<TEntity>  
            // I worked a long time to create a generic Table Type comparison.  Nothing worked.  
            // So I ended up just converting the type to a string and searching for a pattern.  :(  
            if (Regex.IsMatch(_PropInfo.PropertyType.ToString(), @"^System.Data.Linq.Table") == true)  
            {  
                _Tables.Add(_PropInfo.Name);  
            }  
        }  
        return _Tables;  
    }  
 
    /// <summary>  
    ///   
    /// </summary>  
    /// <param name="TableName"></param>  
    private void DisplayTable(string TableName)  
    {  
        // We don't know the ContextTypeName or TableName in advance.  
        // Create a LinqDataSource control dynamically and add it to our  
        // PlaceHolder control.  
        LinqDataSource _linqSource = new LinqDataSource();  
        _linqSource.ID = "LinqSource";  
        _linqSource.ContextTypeName = LinqSQLClass;  
        _linqSource.TableName = TableName;  
        _linqSource.EnableInsert = true;  
        _linqSource.EnableDelete = true;  
        _linqSource.EnableUpdate = true;  
        _linqSource.AutoPage = true;  
        _linqSource.AutoSort = true;  
        _linqSource.EnableViewState = true;  
        LinqPlaceHolder.Controls.Add(_linqSource);  
 
        // Fetch the Primary Keys associated with the current table.  
        string[] _ID = GetTableKeys(TableName);  
          
        // Attach our LinqDataSource to our DataGrid.  
        TableHeader.InnerHtml = "Table -&gt; " + TableName;  
        TableGrid.DataSourceID = "LinqSource";  
        TableGrid.DataKeyNames = _ID;  
    }  
 
    /// <summary>  
    /// Get all properties associated with our LinqContext Table.  
    /// </summary>  
    /// <param name="TableName">The TableName</param>  
    /// <returns>The properties associated with this table</returns>  
    private PropertyInfo[] GetTableProperties(string TableName)  
    {  
        // All we currently have is a table name; "Customers" (plural).  
 
        // What we want to fetch are the properties associated with the  
        // model that is associated with our table.  That model, example,  
        // would be called "Customer" (singular).    
 
        // We find the connection between table & model by using  
        // reflection on the table.  The table will have have a type  
        // of System.Data.Linq.Table<Customer>.  The "Customer Type" is   
        // retrievable by looking at the Generic Arguments.  
          
        //  Clear as mud?  Great, let's get started...  
 
        Type _TableType = LinqContextType.GetProperty(TableName).PropertyType;  
        Type _ModelType;  
        PropertyInfo[] _ReturnProperties = null;  
 
        // Make sure we have a Generic Type  
        if (_TableType.IsGenericType == true)  
        {  
            Type[] _GenericArgs = _TableType.GetGenericArguments();  
 
            // Ensure that we have some Generic Arguments  
            if (_GenericArgs.Length > 0)  
            {  
                // Our model type will be the first Arg.  
                _ModelType = _GenericArgs[0];  
                _ReturnProperties = _ModelType.GetProperties();  
            }  
        }  
        return _ReturnProperties;  
    }  
 
    /// <summary>  
    /// Gets the primary keys associated with a LinqContext Table  
    /// </summary>  
    /// <param name="TableName">The Table Name</param>  
    /// <returns>A list of primary keys associated with the table</returns>  
    private string[] GetTableKeys(string TableName)  
    {  
        ArrayList _Keys = new ArrayList();  
        bool _PrimaryKey = false;  
 
        // Loop through each property found in the Table Model.  
        foreach (var _PropInfo in GetTableProperties(TableName))  
        {  
            // The Primary Key flag is a Custom Attribute.  Get all Custom Attributes  
            // associated with the current property.  
            Object[] _Objects = _PropInfo.GetCustomAttributes(true);  
 
            // Reset our Primary Key flag.  
            _PrimaryKey = false;  
 
            // Loop through each Custom Attribute.  
            foreach (Object _obj in _Objects)  
            {  
                // The attribute we're looking for is "System.Data.Linq.Mapping.ColumnAttribute"  
                if (_obj.GetType() == typeof(System.Data.Linq.Mapping.ColumnAttribute))  
                {  
                    ColumnAttribute _Attribute = (ColumnAttribute)_obj;  
                    if (_Attribute.IsPrimaryKey == true)  
                    {  
                        _PrimaryKey = true;  
                    }  
                }  
            }  
 
            if (_PrimaryKey == true)  
            {  
                _Keys.Add(_PropInfo.Name);  
            }  
        }  
 
        return _Keys.ToArray(typeof(string)) as string[];  
    }  

Step 3: Create the CSS

Create the following CSS file: ~/admin/LinqMyAdmin.css

body {  
    background-color#FFFFFF;  
    font-size15px;  
}  
#contentwrapper {  
    floatleft;  
    width: 100%;  
}  
#contentcolumn {  
    margin-left200px/*Set left margin to LeftColumnWidth*/ 
}  
#leftcolumn {  
    floatleft;  
    width200px/*Width of left column*/ 
    margin-left: -100%;  
}  
#leftcolumn ul {  
    list-stylenone;  
    margin-left5px;  
    padding-left0px;  
}  
#leftcolumn li   
{  
    padding-left0px;  
    margin-left0px;  
    margin-bottom5px;  
}  
h2 {  
    font-familyArial;  
}  
a:link {  
    color:#d42945;  
    text-decoration:none;  
}     
a:visited {  
    color:#d42945;  
    text-decoration:none;  
}         
a:hover, a:focus {  
    color:#f03b58;  
    text-decorationunderline;  
}  
table {  
    bordersolid 1px #e5eff8;  
}  
th {  
    font-weightnormal;  
    background:#f4f9fe;  
    text-aligncenter;  
    bordersolid 1px #e5eff8;  
    padding:.3em 1em;  
}  
thead th {  
    background:#f4f9fe;  
    text-aligncenter;  
    color:#66a3d3 
}     
tr.odd td {  
    background#f7fbff 
}  
td {  
    color#678197;  
    bordersolid 1px #e5eff8;  
    padding:.3em 1em;  
    text-aligncenter;  
}  
.pager td {  
    text-alignleft;  
}            

Access the page via the following URL: http://localhost/admin/LinqMyAdmin.aspx

I would not recommend putting this code on a public server or using production data. If you do happen to upload this code to a public server, be sure to secure access to the page.

Obviously this web application lacks a lot of functionality. What is amazing though is how much functionality I am getting "for free" because of ASP.NET's LinqDataSource and GridView. However, I find that I'm reaching the limit of what I can do dynamically. For example, weaving automatic foreign-key mappings into this code is proving difficult. I have some thoughts on how to overcome this, but have not yet found the time to experiment.

I hope to provide more updates later. Feedback is welcome!

Facebook DZone It! Digg It! StumbleUpon Technorati Del.icio.us NewsVine Reddit Blinklist Furl it!

Comments  5

  • Gabe Sumner 1 May, 02:58 PM

    It seems I didn't do enough research prior to creating this code.

    Microsoft already has a suite of "Dynamic Data" controls being cooked up.

    Here is a post describing these controls: http://weblogs.asp.net/scottgu/archive/2007/12/14/new-asp-net-dynamic-data-support.aspx

    I have not yet tested them, but they appear to do everything I was attempting to create on my own.


  • ebrerberb 6 May, 11:31 AM

    dynamic data craps all over your pitiful attempt. does drop downs for related fields too. never mind. seems you wasted a lot of time that could have been spent dining. learn to code a bit better too. the quality of your code is really bad.


  • Buffalo 13 Jun, 02:50 PM

    good work (even if there are other controls out there)


  • Steve 18 Jul, 11:25 AM

    After reading ebrerberb's comment, I can clearly see his code is superb. You can actually see the quality of his code come though his English prose. Like his code, his English also avoids the use of capitalization. Also, rather than using compound words (which a crappy programmer would use Camel Casing or underscores... eww!!!), our intrepid programmer opts to go against the grain of orthodox English by simply substituting compound words with two words. ZOMG!!!

    I surely wouldn't want to be crapped on by him!!!


  • samuel 3 Feb, 10:42 PM

    lo5RUU http://www.cRk2bdPqQls602mIa4bgo.com


Post a comment!


HostMySite.com   website uptime