thefrozencoder

Programming and Technology blog

Summary totals in a GridView

Introduction

This post is in response to the common request on the ASP.NET forums for adding summary totals in a GridView control.  The idea is to use the FooterTemplate of each column in the GridView that has some kind of value to place the summary of the column.  The sample also goes on to show how to use the RowDataBound event to set the actual totals as well to add a Total Price column that takes a quantity field and a price field to display the total for each line.

The code samples in this post can be downloaded using the link at the bottom and were created using Visual Studio 2010.  There are samples for both C# and Visual Basic.NET in the download file as well.

Implementation

Default.aspx Page

What we want on the grid is simple enough, display a line by line total as well a summary of the Qty and Total Price fields.  Below are the columns that make up the grid view.  For the columns that come out of the database we would simply use BoundField columns.  For our calculated columns we want to use a TemplateField so we can add asp.net controls to the columns so we can inject our own data.  I tend to use the Literal control as it renders just the contents of the Text property vs the Label control that renders a SPAN html tag.  For the columns that will contain a summary total we use the FooterTemplate to place Literal controls so we can write the summary data out.

            <Columns>
                <asp:BoundField DataField="Description" HeaderText="Description">
                    <HeaderStyle HorizontalAlign="Left" />
                    <ItemStyle HorizontalAlign="Left" />
                </asp:BoundField>
                <asp:BoundField DataField="Price" HeaderText="Price" DataFormatString="{0:c}">
                    <HeaderStyle HorizontalAlign="Right" Width="100" />
                    <ItemStyle HorizontalAlign="Right" Width="100" />
                </asp:BoundField>
                <asp:TemplateField HeaderText="Qty">
                    <ItemTemplate>
                        <asp:Literal ID="litQty" runat="server" Text="0" />
                    </ItemTemplate>
                    <FooterTemplate>
                        <asp:Literal ID="litSumQty" runat="server" Text="0" />
                    </FooterTemplate>
                    <HeaderStyle HorizontalAlign="Right" Width="100" />
                    <ItemStyle HorizontalAlign="Right" Width="100" />
                    <FooterStyle HorizontalAlign="Right" Width="100" />
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Total Price">
                    <ItemTemplate>
                        <asp:Literal ID="litTotalPrice" runat="server" Text="0" />
                    </ItemTemplate>
                    <FooterTemplate>
                        <asp:Literal ID="litSumTotalPrice" runat="server" Text="0" />
                    </FooterTemplate>
                    <HeaderStyle HorizontalAlign="Right" Width="100" />
                    <ItemStyle HorizontalAlign="Right" Width="100" />
                    <FooterStyle HorizontalAlign="Right" Width="100" />
                </asp:TemplateField>
            </Columns>

Default.aspx.cs Code Behind

To inject our computed data we use the RowDataBound event on the grid.  This event is fired for every row when the data is bound to the row and allows us access to the controls and the data at the same time.  We first check if the current row type is a DataRow (items in the grid) and then we grab an instance of the actual data that makes up the row.  This will be different if you are using a generic list of your objects, you would cast the DataItem as that object.  Then we get a reference to the data from the DataRow and perform our calculations, then we get an instance of the Literal controls in the row, check if they are null or not (null would mean we did not find them) and set the Text value.

        protected void GridView1_RowDataBound(object sender, System.Web.UI.WebControls.GridViewRowEventArgs e)
        {

            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                DataRowView dr = (DataRowView)e.Row.DataItem;
                int qty = (int)dr["Qty"];
                decimal price = (decimal)dr["Price"];
                decimal totalPrice = qty * price;

                Literal litTotalPrice = (Literal)e.Row.FindControl("litTotalPrice");
                Literal litQty = (Literal)e.Row.FindControl("litQty");

                if ((litQty != null))
                    litQty.Text = qty.ToString();

                if ((litTotalPrice != null))
                    litTotalPrice.Text = totalPrice.ToString("c");
            }

            if (e.Row.RowType == DataControlRowType.Footer)
            {
                int qty = 0;
                decimal price = default(decimal);

                GetTableTotals(ref qty, ref price);

                Literal litSumTotalPrice = (Literal)e.Row.FindControl("litSumTotalPrice");
                Literal litSumQty = (Literal)e.Row.FindControl("litSumQty");

                if ((litSumQty != null))
                    litSumQty.Text = qty.ToString();

                if ((litSumTotalPrice != null))
                    litSumTotalPrice.Text = price.ToString("c");
            }
        }

If the RowType is Footer we need to get the summary data from the entire table of data.  To do this we use some Linq in the GetTableTotals() method to get a sum of the quantity and calculate the sum of the quantity * price for each data row.

        public void GetTableTotals(ref int qty, ref decimal price)
        {
            DataTable dt = (DataTable)GridView1.DataSource;
            var rows = dt.AsEnumerable();

            qty = rows.Sum(p => (int)p["Qty"]);
            price = (from p in rows select (int)p["Qty"] * (decimal)p["Price"]).Sum();
        }

Code: GridTotalsSample.zip (22.01 kb)

Move your appSettings out of the web.config file

Introduction

This post goes about showing you how you can implement common global settings without using the web.config's appSettings section.  The idea is simple and taken from the latest craze of document based databases like RavenDB.  Why not create an object for your settings serialize it to a standard format and then just persist it to a file.  Now you probably don’t want to go and store passwords and such in the file (unless you encrypt them first) but for the most common settings it’s a nice alternative to the web.config appSettings which is pretty limited.  Now some people may say why not a database?  Well unless you are running under a server farm (and then you probably have a common share between all web servers in the farm) to me a database is a little over-kill for the most part

The code samples in this post can be downloaded using the link at the bottom and were created using Visual Studio 2010.  There are samples for both C# and Visual Basic.NET in the download file as well.  There is an external dependency (JSON.NET) that is included in the lib folder it is used to serialize and de-serialize the SettingsStore object.

Implementation

Default.aspx page UI

The UI is simple enough, just have a way to allow users to update the settings from the setting class.

SettingsLib Code

The SettingsLib class implements the getting and the persisting of the actual settings.  By default the site_settings.config file is stored in a folder called app_data, this can be changed to anything but just remember the windows account that your website runs under must have write access to this folder for the settings to be persisted.

    public static class SettingsLib
    {
        public const string SETTINGS_FILE_NAME = "site_settings.config";

        /// 
        /// Gets the current Settings object from the settings store
        /// 
        /// Settings object
        public static SettingsStore GetSettings()
        {
            return JsonConvert.DeserializeObject(ReadSettingsFile());
        }

        /// 
        /// Saves the settings to the settings store
        /// 
        ///The current Settings object
        /// 
        public static bool SaveSettings(SettingsStore settings)
        {
            return WriteSettingsFile(JsonConvert.SerializeObject(settings));
        }

        /// 
        /// Reads the json data from the settings file
        /// 
        /// json string from the file
        private static string ReadSettingsFile()
        {
            string settings = "{ }";
            string path = GetPath();

            try
            {
                if (File.Exists(path))
                    settings = File.ReadAllText(path);
            }
            catch (Exception) { }

            return settings;
        }

        /// 
        /// Writes the json string to the settings file
        /// 
        ///The json string that represents the Settings object 
        /// True | False if the write was successful
        static bool WriteSettingsFile(string json)
        {
            string path = GetPath();

            try
            {
                File.WriteAllText(path, json);
                return true;
            }
            catch (Exception) { }

            return false;
        }

        /// 
        /// Returns the full file path to the settings file
        /// 
        /// 
        static string GetPath()
        {
            string path = System.Web.HttpContext.Current.Server.MapPath("~\\app_data");

            if (!path.EndsWith("\\")) path += "\\";

            path = string.Concat(path, "\\", SETTINGS_FILE_NAME);

            return path;
        }
    }

SettingsStore Class

The SettingsStore class is just a simple class that implements your settings as an object using properties.  JSON.NET will serialze most primative .net types as well collections too which makes it more flexable than using the web.config file.

    /// 
    /// Class to hold all of your settings
    /// 
    public class SettingsStore
    {
        public string UploadFolder { get; set; }
        public string EmailErrorsTo { get; set; }
        public string EmailServerHostname { get; set; }
    }

Code SettingsStoreSample.zip (176.40 kb)