Using an abstract Server Control as an updateable module

by MikeHogg 19. March 2009 14:15

The main part of this feature was to show a grid of some data, different database uptimes and other performance metrics, on a web page.  The interesting part was that the databases that were tracked changed often.  Sometimes there were 12, and then one would get killed, and two more were created, and the next week another new one would be built. Not only would it be better to put the datasources in a config of some sort, but it would be even better if a manager could edit that config through an easy-to-use UI on a web page. 

Rather than build two separate pages, I saw that there were some re-useable components in this use case, and so I created a Server control, with an XML config.  Most of the XML I/O I put in an Abstract class, and then from that I inherited and Admin Control and a Display Control.

First the Common Elements:

    interface IGrid
    { 
        DataTable getDataSource();
        void createColumns(DataTable aTable);
        void styleGrid();
    }
    public abstract class AGrid : GridView, IGrid
    {
        public string xFile{get; set;}
        public string headerBackColor{get; set;}
        public string gridBackColor {get;set;} 
        public string title { get; set; }        
        protected DataTable myTable; 
        protected override void OnInit(EventArgs e)
        {
            try
            {
                base.OnInit(e);
                this.AutoGenerateColumns = false;
                myTable = getDataSource();
                if (Page.IsPostBack)
                {
                    this.DataSource = myTable;
                    if (this.EditIndex > -1 )
                    {
                        this.DataBind();
                    }
                }
            } 
        }
        protected override void OnLoad(EventArgs e)
        { 
                base.OnLoad(e);
                if (!Page.IsPostBack)
                {
                    if (myTable != null)
                    {
                        createColumns(myTable);
                        this.DataSource = myTable;
                        this.DataBind();
                        styleGrid();
                    }
                }  
        } 
        public DataTable getDataSource()
        { 
            <snip>                      
        }
        public virtual void createColumns(DataTable myTable){
            try
            { <snip>
        }
        public void styleGrid()
        {
            try
            {
                if (this.gridBackColor != String.Empty)
                {
                    this.BackColor = System.Drawing.Color.FromName(this.gridBackColor);
                }
                else this.BackColor = System.Drawing.Color.Silver;
                this.BorderStyle = BorderStyle.Ridge;
                this.Font.Size = 10;
                this.Font.Name = "Verdana";
                this.Font.Bold = true;
                
                this.GridLines = GridLines.Horizontal;
                this.Style.Add("font-variant", "small-caps");
                this.Style.Add("text-align", "right");
                this.CellPadding = 2;
                if (this.headerBackColor != String.Empty)
                {
                    this.HeaderStyle.BackColor = System.Drawing.Color.FromName(this.headerBackColor);
                }
                else this.HeaderStyle.BackColor = System.Drawing.Color.MidnightBlue;
                this.HeaderStyle.ForeColor = System.Drawing.Color.White;
                this.HeaderStyle.Font.Size = 12;
                if (this.title != String.Empty)
                {
                    this.Caption = this.title;
                }
                else this.Caption = "Grid Monitor 1.0";
                
                this.CaptionAlign = TableCaptionAlign.Top;
                this.BorderWidth = 1;
            }
            catch (NullReferenceException e)
            {
                Console.WriteLine("Probably xml issue: " + e.ToString());
            }
        }

 

The Admin Grid was just a simple DataGrid and used the built-in OnDataBound to add editting controls and commands, and the OnRowEditing events to Add/Edit/Remove nodes in the XML file.

 

    [DefaultProperty("Text")]
    [ToolboxData("<{0}:AdminGrid runat=server></{0}:AdminGrid>")]
    public class AdminGrid : AGrid
    {
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
          
        protected override void OnInit(EventArgs e)
        {
            this.AutoGenerateEditButton = true;
            this.ShowFooter = true;
            
            base.OnInit(e); 
        } 
        protected override void OnPagePreLoad(object sender, EventArgs e)
        {
            base.OnPagePreLoad(sender, e);
            if (Page.IsPostBack && this.EditIndex == -1)
            {
                this.DataBind();
            }
        } 
        protected override void OnDataBound(EventArgs e)
        {
            base.OnDataBound(e);
            foreach (DataControlFieldCell cell in this.FooterRow.Cells)
            {
                if (cell.ContainingField.GetType().Equals(typeof(System.Web.UI.WebControls.BoundField)))
                {
                    TextBox myC = new TextBox();
                    cell.Controls.Add(myC);
                }
                else if (cell.ContainingField.GetType().Equals(typeof(System.Web.UI.WebControls.CheckBoxField)))
                {
                    CheckBox myC = new CheckBox();
                    cell.Controls.Add(myC);
                }
                else if (cell.ContainingField.GetType().Equals(typeof(System.Web.UI.WebControls.CommandField)))
                {
                    LinkButton myC = new LinkButton();
                    myC.Text = "Add New";
                    myC.ID = "btnAddNew";
                    myC.CommandName = "New";
                    myC.CommandArgument = "New";
                    cell.Controls.Add(myC);
                }
            }
         
        protected override void OnRowCommand(GridViewCommandEventArgs e)
        {
            try
            {
                base.OnRowCommand(e);
                if (e.CommandName == "New")
                {
                    DataRow newRow = myTable.NewRow();
                    //insert
                    for (int x = 0; x < myTable.Columns.Count; x++)
                    {
                        Control myControl = this.FooterRow.Cells[x + 1].Controls[0];
                        if (myControl.GetType().Equals(typeof(CheckBox)))
                        {
                            newRow[x] = ((CheckBox)myControl).Checked;
                        }
                        else if (myControl.GetType().Equals(typeof(TextBox)))
                        {
                            newRow[x] = ((TextBox)myControl).Text;
                        }
                    }
                    myTable.Rows.Add(newRow);
                    WriteXml(myTable, HttpContext.Current.Server.MapPath(xFile));
                    this.DataSource = myTable;
                    this.DataBind();
                }
            }
        protected override void OnRowUpdating(GridViewUpdateEventArgs e)
        { 
            //DataTable myTable = (DataTable)HttpContext.Current.Session["myTable"];
            DataTable oldTable = (DataTable)this.DataSource;
            GridViewRow myRow = this.Rows[e.RowIndex];
            for (int x = 0; x < myRow.Cells.Count; x++)
            {
                Control myControl = myRow.Cells[x].Controls[0];
                
                if (myControl.GetType().Equals(typeof(CheckBox)))
                {
                    oldTable.Rows[e.RowIndex][x - 1] = ((CheckBox)myControl).Checked;
                    //myTable.Rows[e.RowIndex][]
                }
                else if (myControl.GetType().Equals(typeof(TextBox)))
                {
                    oldTable.Rows[e.RowIndex][x - 1] = ((TextBox)myControl).Text;
                }
                    WriteXml(myTable, HttpContext.Current.Server.MapPath(xFile));
                    this.DataSource = myTable;
                    this.DataBind();
                }
            }
        }

 

The DisplayGrid has some neat UI components

[assembly: WebResource("DBMonitor.Resources.button_red.png","image/png")]
[assembly: WebResource("DBMonitor.Resources.button_green.png", "image/png")]
[assembly: WebResource("DBMonitor.Resources.button_yellow.png", "image/png")]
namespace DBMonitor
{
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
    public class DisplayGrid : AGrid
    {
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Text <snip>
        protected override void OnInit(EventArgs e)
        { 
                this.title = "Database Environments";
                this.headerBackColor = "MidnightBlue";
                this.gridBackColor = "Silver";
                this.xFile = "Config/dbmon.xml";
                base.OnInit(e); 
        }
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            DataTable myTable = this.DataSource as DataTable;
            if (myTable.Columns.IndexOf("Active") >= 0)
            {
                myTable.DefaultView.RowFilter = "Active = true";
                this.DataBind();
            }
        }
        protected override void OnRowDataBound(GridViewRowEventArgs e)
        {
            base.OnRowDataBound(e);
            try
            {
                //find BE Last Refreshed value
                int x = ((DataTable)this.DataSource).Columns.IndexOf("BE_Last_Run");
                if (x >= 0 && e.Row.RowType != DataControlRowType.Header)
                {
                    System.Web.UI.WebControls.Image myLight = new System.Web.UI.WebControls.Image();
                      
                    DateTime lastBE = new DateTime();
                    if (e.Row.Cells[x].Text.Contains(':'))
                    {
                        try
                        {
                            lastBE = DateTime.Parse(e.Row.Cells[x].Text);
                        }
                        catch (FormatException ex)
                        {
                            lastBE = DateTime.MinValue;
                            
                        }
                        e.Row.Cells[x].Text = lastBE.ToShortTimeString();
                        TimeSpan myAge = DateTime.Now - lastBE;
                        if (ConfigurationSettings.AppSettings["debug"] == "true")
                        {
                            logThis("myAge: " + myAge.ToString() + " and now is " + DateTime.Now.ToString() +
                                " and lastBE is " + lastBE.ToString() +
                                " and timespan.fromhours(1) is " +
                            TimeSpan.FromHours(1).ToString() +
                            " and now minus lastBE is " + myAge, EventLogEntryType.Information);
                        }
                        if (myAge > TimeSpan.FromHours(1))
                        {
                            myLight.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
                                    "DBMonitor.Resources.button_red.png");
                        }
                        else if (myAge > TimeSpan.FromMinutes(10))
                        {
                            myLight.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
                                "DBMonitor.Resources.button_yellow.png");
                        }
                        else
                        {
                            myLight.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
                                "DBMonitor.Resources.button_green.png");
                        }
                    }
                    else
                    {
                        myLight.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
                                    "DBMonitor.Resources.button_red.png");
                    }
                    e.Row.Cells[0].Controls.Add(myLight);
                    e.Row.Cells[0].BackColor = Color.White;
                }
            }

Adding it to a page is just a couple lines then- here the first line registers the namespace, and the last selected line places it in a div.  In this case I populated my attributes in the DisplayGrid class, but if I was to use this in several other places, I could remove those and populate my attributes here in the html element.

<%@ Register Assembly="DBMonitor" Namespace="DBMonitor" TagPrefix="dbm"  %> 
<!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>Status Page</title>
    <link href="StyleSheet.css" rel="stylesheet" type="text/css" />
    <link href="mobile.css" rel="stylesheet" type="text/css" media="handheld"/>
    
    <script src="stock-ticker.js" type="text/javascript"></script>
     <meta http-equiv=Refresh content="120" />
     
    <script type="text/javascript">
        
        var ticker;
        function loadTicker(){
            ticker = new Ticker('ticker','myTicker',4,15);
            ticker.start();
            
        }
 
    </script>
</head>
<body onload="loadTicker();">
    <form id="form1" runat="server"> 
    
   <table style="table-layout:fixed"><tr><td style="width:200px;overflow:hidden">
       <div id="myTicker" class="ticker" style="width:5000px" nowrap="nowrap">
            <asp:Xml DocumentSource="~/Config/ticker.xml" runat="server" ID="ticker" TransformSource="~/ticker.xslt"></asp:Xml>
   </div></td></tr></table>
   
    <div id="newEnv">
        <dbm:DisplayGrid ID="something" runat="server"></dbm:DisplayGrid>
    </div>

 

And that’s it.

About Mike Hogg

Mike Hogg is a c# developer in Brooklyn.

More Here

Favorite Books

This book had the most influence on my coding style. It drastically changed the way I write code and turned me on to test driven development even if I don't always use it. It made me write clearer, functional-style code using more principles such as DRY, encapsulation, single responsibility, and more. amazon.com

This book opened my eyes to a methodical and systematic approach to upgrading legacy codebases step by step. Incrementally transforming code blocks into testable code before making improvements. amazon.com

More Here