by William
28. June 2010 19:13
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)
by William
20. June 2010 14:35
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)
by William
16. August 2009 15:36
So I decided to update my blog engine to the newest version and along with that I wrote a simple extension to detect IE6 users and redirect them to a non-supported browser page. Not that my theme cannot handle IE6 but I feel I should get on board and help as much as I can with the movement to rid the internet of this outdated browser.
The extension is pretty simple and uses the built in Response.Browser object which should work for IE6 nicely. To make an extension for BlogEngine is pretty easy just create a class with a constructor with no parameters and then wire up one or more events that the BlogEngine raises to your event in your extension class. Below is my extension class for the redirect logic.
using System;
using System.Web;
using BlogEngine.Core;
using BlogEngine.Core.Web;
using BlogEngine.Core.Web.Controls;
using BlogEngine.Core.Web.HttpHandlers;
[Extension("Redirects users with non-supported browsers to an alternate download page", "1.0", "thefrozencoder.ca")]
public class NonSupportedBrowser
{
public NonSupportedBrowser()
{
Post.Serving += new EventHandler(ServingHandler);
Page.Serving += new EventHandler(ServingHandler);
}
void ServingHandler(object sender, ServingEventArgs e)
{
HttpContext context = HttpContext.Current;
if (context != null && !context.Items.Contains("NonSupportedBrowserTest"))
{
System.Web.UI.Page page = context.CurrentHandler as System.Web.UI.Page;
if (page != null)
{
context.Items.Add("NonSupportedBrowserTest", 1);
if (context.Request.Browser.MajorVersion <= 6 && context.Request.Browser.Browser.Equals("IE"))
{
string file = System.Configuration.ConfigurationManager.AppSettings["NonSupportedBrowser"];
context.Response.Redirect(file, true);
}
}
}
}
}
As you can see in the constructor I wire up the Page.Serving and Post.Serving events to my own method. I create a context item (NonSupportedBrowserTest) and check for it because the event will be called more than once in the lifetime of the request. I also store the file name in an appSettings key/value so I can change the file to be redirected to (simple HTML file in this case). All that is required is to place the code file in the App_Code\Extensions folder and add the HTML file with the instructions to download a newer browser.
If you are looking for some ideas for this page I simply used the code from the IE6 No More website.
by William
15. May 2009 19:12
The following is a couple of T4 templates that I have been fooling around with in my spare time (sharing the time between jQuery, StringTemplate view engine for ASP.NET MVC and other endeavors). For the most part these templates are nothing special, they generate both the T-SQL (stored procedures) and a simple but effective data access layer. Like I said nothing special.
A good resource for starting with T4 templates is @ David Hayden site where he has a Screencast on how to use them. Below describes some of the settings used to get my simple sample up and running.
Extract the _common.tt, DataClass.tt and T-SQL.tt files from the zip and add them to your VS project in a folder (like Generation). In the _common.tt file there are some settings (variables) that you can use to modify how the code is generated. You will need SQL 2005 client tools installed on your machine as it uses the SMO object library. Once the code is compiled just cut and paste it into separate class files and run the T-SQL code on your db.
-
ConnectionString - This is your connection string to your database
-
TableName - This is the table you are going to run the code against
-
SchemaName - Use this for applying a schema to your T-SQL code if you use schemas (default is dbo)
-
ColumnsToOmit - Comma delimited list of columns to omit from the DAL code that is generated
-
NameSpace - Namespace of your application
-
ProcPrefix - Use to add a prefix to your stored procedures (ie. up_)
-
UseShortProperties - True | False to tell code generation to generate the C# short form for get/set properties
-
insertColumnsAsGetdate - Comma delimited list of column names that will automatically be assigned the GETDATE() T-SQL statement on an Insert. (Also omits the fields from the insert statement parameters)
-
updateColumnsAsGetdate - Comma delimited list of column names that will automatically be assigned the GETDATE() T-SQL statement on an Update. (Also omits the fields from the update statement parameters)
The code is supplied as is and if it's broken fix it, you're a programmer aren't you? :P
T4 Templates.zip (4.98 kb)
by William
5. March 2009 19:54
Recently I was working on a web service that required logic than needed the span of time passed in months and days based on a start and end date. I figured someone has already written this before me so why waste my time. EPIC WRONG!!!!!! While I was able to find code (mostly snippets) that did various calculations most of them were either; a) not what I needed or b) fundamentally flawed in the calculations (most choked on month/day crossovers, where the as-of-date was 1 month previous and 1 day later than the sample start date). So I decided that I would take a chapter from the TimeSpan class and create my own AgeSpan class.
This class helps answers the question how many years, months, days, total months, total days from the birth date to the as-of-date. So for example on Mar 04 2009 this class tells me I am 40 years; 5 months and 1 day old or 485 total month or 14762 total days old.
One of the options available is to set the include as of date in the days calculation. What this does is include the as of date inclusively as part of the date range. Basically it adds one day to the days and total days. So taking the example from above I would be 40 years; 5 months and 2 days old or 485 total month or 14763 total days old. This option is set to false by default.
Included in the zip is a unit test project to validate the actual logic. The class is written in c# and it probably can be ported to VB pretty easily or just use the assembly as is.
AgeSpan Class
Download the code: AgeSpan.zip (521.59 kb)