ASP.NET Talk

ERROR: “Index was out of range” when Accessing the DataKeys Collection in a Paged GridView
14 November 13 11:45 PM | Scott Mitchell

While working on a legacy ASP.NET WebForms application I stumbled into the following error: I had a paged GridView with a LinkButton in a TemplateField that, when clicked, accessed the GridView’s DataKeys collection. When clicking one of the LinkButton’s on the first page everything worked beautifully, but as soon as a user navigated to any page other than the first clicking on the LinkButton resulted in the following exception when attempting to access the DataKeys collection: Index was out of range. Must be non-negative and less than the size of the collection.

The markup for the LinkButton was as follows:

<asp:GridView ID="gvProducts" runat="server"
         AutoGenerateColumns="False" 
         DataKeyNames="ProductID" 
         AllowPaging="True" ...>
    <Columns>
        <asp:TemplateField ShowHeader="False">
            <ItemTemplate>
                <asp:LinkButton ID="lb" runat="server"
                        CommandArgument='<%# Container.DataItemIndex %>' 
                        CommandName="Test" Text="Click Me"></asp:LinkButton>
            </ItemTemplate>
        </asp:TemplateField>
        ...
    </Columns>
</asp:GridView>

Note that the LinkButton’s CommandArgument property is set to the index of the current row being bound to the grid via the Container.DataItemIndex syntax.

Whenever the LinkButton is clicked there is a postback and the GridView’s RowCommand event handler fires. From here I was reading the CommandArgument value to get the row index and then used that to access the DataKeys collection.

protected void gvProducts_RowCommand(object sender, GridViewCommandEventArgs e)
{
    if (e.CommandName == "Test")
    {
        int rowIndex = Convert.ToInt32(e.CommandArgument).ToString();
        object value = gvProducts.DataKeys[rowIndex].Value;
    }
}

This code works great if the GridView is not paged or if viewing the first page of a paged GridView. It blows up, however, when clicking the LinkButton in a paged GridView when viewing any page other than the first. Specifically, it blows up because the value of rowIndex exceeds the bounds of the DataKeys collection.

Container.DataItemIndex returns the index of the data item whereas the DataKeys collection only contains entries for the current page of data being displayed. To put it in simple terms, if you have a paged GridView that displays 10 records per page and you bind 100 records to it, the Container.DataItemIndex value will return a value between 0 and 99 no matter what page you are viewing; the DataKeys collection, on the other hand, will only have 10 records at most (since only 10 records are being displayed per page). So if you are viewing page 2 of the GridView the Container.DataItemIndex values will range from 10..19, but the DataKeys collection is still only going to have 10 records (0..9). So when you go to page 3, say, and click the LinkButton for the first row in that page you are actually running code like:

object value = gvProducts.DataKeys[20].Value;

But the DataKeys collection only has 10 items (0..9). The result is a System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.

The Solution

There are two workarounds here. The first is to set the LinkButton’s CommandArgument property to the value you actually care about. If you want to read the ProductID value for the row just send it on in (rather than sending in the row index and reading the value from the DataKeys collection), like so:

<asp:LinkButton ID="lb" CommandArgument='Eval("ProductID")' ... />

The other option is to do a little modular arithmetic in the RowCommand event handler. Taking the row index mod the number of records per page will give you the appropriate value by which to access the DataKeys collection. GridViewID.PageSize will return the number of records per page so all you need to do is do rowIndex mod pageSize, as the following code snippet shows:

object value = gvProducts.DataKeys[rowIndex % gvProducts.PageSize].Value;

Happy Programming!

Filed under:
ASP.NET Bundling and Minification Returning File Not Found (404)
19 October 13 07:19 AM | Scott Mitchell

ASP.NET 4.5 introduced a nifty feature for script and stylesheet bundling and minification, a technique that can drastically reduce the size of your script and stylesheet files and, more importantly, reduce the number of round-trips the browser must make to the server to fully render a web page. Today I was working with a client who had been using bundling and minification with great success for sometime but after a recent site update his script bundle, while being rendered in the web page, was not returning the bundled, minified script content as expected, but rather was returning a 404 error (File Not Found).

I’m going to provide a more in-depth discussion on the issue at hand, but let me state the exact problem and solution in short – the problem was that the name of the script bundle – e.g., ~/bundles/MyBundle – did not match the bundle name referenced in the Scripts.Render statement – e.g., Scripts.Render(“~/bundles/MyBundleTypo”). Unfortunately, this mismatch didn’t produce any sort of runtime error that would alert us to the crux of the problem, but instead “failed silently” and generated a 404 for the requested bundle (“~/bundles/MyBundleTypo”). I was able to narrow down the problem due to the resulting <script> element that was rendered (and generated a 404). Instead of seeing a <script> element like:

<script src="http://nbaweblog.com/bundles/MyBundle?v=aLsVjoQ4OTEtRxZ322JRn0RdnugNXJ-_IdXTAvkYpyU1"></script>

We instead saw one sans the querystring, like so:

<script src="http://nbaweblog.com/bundles/MyBundleTypo"></script>

Using the appropriate bundle name – e.g., Scripts.Render(“~/bundles/MyBundle”) instead of Scripts.Render(“~/bundles/MyBundleTypo”) – fixed the problem.

And now for the more verbose explanation!

Bundling and minification involves two steps:

  1. Defining the script and stylesheet bundles in the BundleConfig.cs class, and
  2. Rendering the appropriate <script> and <style> references in the ASP.NET WebForms master page or ASP.NET MVC layout view.

Step (1) involves creating one or more ScriptBundle or StyleBundle classes and adding them to a BundleCollection. The following snippet shows the bundling added for the ~/bundles/MsAjaxJs bundle (which is included automatically when creating a new ASP.NET WebForms project in Visual Studio):

bundles.Add(new ScriptBundle("~/bundles/MsAjaxJs").Include(
    "~/Scripts/WebForms/MsAjax/MicrosoftAjax.js",
    "~/Scripts/WebForms/MsAjax/MicrosoftAjaxApplicationServices.js",
    "~/Scripts/WebForms/MsAjax/MicrosoftAjaxTimer.js",
    "~/Scripts/WebForms/MsAjax/MicrosoftAjaxWebForms.js"));

Note the name of the bundle in this instance (~/bundles/MsAjaxJs) and the bundled files: MicorosftAjax.js, MicorosftAjaxApplicationServices.js, and so on.

To render the necessary <script> and <style> references in a web page you add syntax like the following. For an ASP.NET MVC view:

@Scripts.Render("~/bundles/MsAjaxJs")

And for an ASP.NET WebForms web page:

<%: Scripts.Render("~/bundles/MsAjaxJs") %>

When the bundling and minification optimizations are enabled the above markup will generate a <script> element like so:

<script src="http://nbaweblog.com/bundles/MsAjaxJs?v=aLsVjoQ4OTEtRxZ322JRn0RdnugNXJ-_IdXTAvkYpyU1"></script>

Note the querystring, which is used for caching purposes.

My client, however, was getting a different looking <script> element generated, one without the querystring like so:

<script src="http://nbaweblog.com/bundles/MsAjaxJs"></script>

Moreover, the URL, when visited, was returning File Not Found (404), rather than the bundled and minified script content.

The problem, it turned out, was a typo, a transposition of two characters in the Scripts.Render markup. Instead of Scripts.Render(“~/bundles/MsAjaxJs”) my client had inadvertently typed in Scripts.Render(“~/bundles/MsAjxaJs”). Of course, there was no bundle named ~/bundles/MsAjxaJs, but instead of generating a runtime exception (which would have been helpful) we instead just got a 404 for the <script> element.

I actually discovered the typo by using Reflector to scour the code of the System.Web.Optimization assembly in an attempt to ascertain why the rendered <script> element lacked a querystring value. Scripts.Render does not include a querystring under two conditions:

  1. The bundled scripts are registered using an absolute path (e.g., http://www.example.com/js/MyScript.js) rather than a virtual path (e.g., ~/js/MyScript.js), which was not the case here, or
  2. The named bundle could not be found in the bundles table (DING DING DING!!)

And that’s what alerted me to very carefully check to ensure that the bundle names and names specified in Scripts.Render matched precisely, which led me to the typo.

It’s never a comforting sign when you need to dig out Reflector and start parsing through disassembled source code in order to unearth the cause of an error. In my opinion there should be some sort of exception that is raised when requesting a non-existent bundled or minified script from Scripts.Render.

Happy Programming!

Filed under:
Rendering Lists in Irregular Columns Using iText / iTextSharp
16 October 13 01:35 AM | Scott Mitchell

Today’s blog post is going to be a bit obscure, but I stumbled across this problem recently and found a workaround that I thought would be worth sharing for anyone else who found themselves in my place.

iText is an open-source library for programmatically creating PDF documents (iTextSharp is the .NET port of iText). I’ve written before on using iTextSharp to create PDF documents from an ASP.NET page, see:

There’s also an invaluable series of iTextSharp-related posts on Mike Brind’s blog, Create PDFs in ASP.NET.

I recently started work on a project that makes heavy use of iTextSharp to create rather complex PDF documents on the fly. One feature of iText is the ability to lay out text into columns. There are two flavors of columns available:

  • Simple columns – you create simple columns by defining a bounded rectangle, e.g., the lower left and upper right coordinates of the rectangle. The text then flows into the bounds of the column.
  • Irregular columns – with irregular columns you specify an array of coordinates for the left margin(s) and another array of coordinates for the right margin(s). With this approach you can define irregularly shaped columns in order to have the text flow around images and other document elements.

Mike’s Page Layout with Columns post provides a good overview of both simple and irregular columns.

One challenge I bumped into is how iText lays out the elements added to a column. There are two layout modes: composite mode and text mode. To be honest, I am not 100% clear on the fine differences between the two modes, but from my understanding the differences are as follows:

  • Composite mode allows you to add all sorts of elements and the layout defined by those added elements is what dictates the rendered layout. For example, you can add lists and tables paragraphs and other such elements to a column when using composite mode and the layout directives for the lists, tables and paragraphs are respected when the column is rendered.
  • With text mode, on the other hand, you are limited to adding only phrases to the column and any layout directives defined in the phrase(s) are ignored. Consequently, if you add three paragraphs in text mode they all run together, one after another, without space between each paragraph, without indentation, and so on.

Unfortunately, text mode and irregular columns are intertwined, meaning that if you are adding irregular columns you cannot use composite mode for those columns. The downside is that you cannot add lists, tables, or other richer document elements to an irregular column. Additionally, you have to add your own line breaks between paragraphs. For instance, if you examine the code in Mike’s Page Layout with Columns post you’ll see that in his irregular column example he adds a series of Phrases and ends the content of each phrase with two newline characters (\n) to create the whitespace between each paragraph.

So what if you need to add a bulleted or numbered list to an irregular columns, are you out of luck? Well, you’re out of luck if you want to add a List element and have it do the rendering but if you don’t mind doing to rendering yourself you can add your own list-like content. The following code snippet shows how to add both ordered and unordered lists.

First, let’s setup the irregular columns. The following code was lifted directly from Mike’s blog post:

Document doc = new Document();

try
{
    doc.SetMargins(45f, 45f, 60f, 60f);
    Font bodyFont = FontFactory.GetFont("Arial", 12, new BaseColor(0, 0, 0));

    FileStream output = new FileStream(Server.MapPath("~/MyIrregularColumnsExample.pdf"), FileMode.Create);
    PdfWriter writer = PdfWriter.GetInstance(doc, output);
    doc.Open();
    PdfContentByte cb = writer.DirectContent;
    ColumnText ct = new ColumnText(cb);


    float gutter = 15f;
    float colwidth = (doc.Right - doc.Left - gutter) / 2;

    float[] left = { doc.Left + 90f , doc.Top - 80f,
            doc.Left + 90f, doc.Top - 170f,
            doc.Left, doc.Top - 170f,
            doc.Left , doc.Bottom };

    float[] right = { doc.Left + colwidth, doc.Top - 80f,
            doc.Left + colwidth, doc.Bottom };

    ct.SetColumns(left, right);

    ct.Alignment = Element.ALIGN_JUSTIFIED;
    ct.AddText(new Phrase("Lorem ipsum dolor sit amet, ...\n\n", bodyFont));

This code starts be creating a Document and outputting the PDF to a file named MyIrregularColumnsExample.pdf on the file system. Next, the irregular column is defined through a series of left and right coordinates. These coordinates are such that the column will run on the left half of the page and wrap around a rectangle in the upper left corner where an image can be placed. If the intent isn’t clear, the screen shot below should help clarify things; also, you can read Mike’s blog post for details.

Next, the elements for the list are defined as a List<string>:

List<string> items = new List<string>();
items.Add("This is the first item");
items.Add("This is the next item");
items.Add("And here be the last item");

Next we’re ready to add the list! Here’s how you add an ordered list – simply loop through each item in the list and add some space and the appropriate number (1, 2, 3, …).

ct.AddText(new Phrase("Here is an ordered list:\n\n", bodyFont));

for (int i = 0; i < items.Count; i++)
{
    ct.AddText(new Phrase(string.Concat("     ", (i + 1), ".  "), bodyFont));
    ct.AddText(new Phrase(itemsIdea, bodyFont));

    if (i != items.Count - 1)
        ct.AddText(Chunk.NEWLINE);
}
            
ct.AddText(Chunk.NEWLINE);
ct.AddText(Chunk.NEWLINE);

For bulleted lists we can use the unicode character 0x2022 to render the bullet. Or you could choose alternative symbols.

ct.AddText(new Phrase("And here is an unordered list:\n\n", bodyFont));

for (int i = 0; i < items.Count; i++)
{
    ct.AddText(new Phrase(string.Concat("     ", '\u2022', "  "), bodyFont));
    ct.AddText(new Phrase(itemsIdea, bodyFont));

    if (i != items.Count - 1)
        ct.AddText(Chunk.NEWLINE);
}

ct.AddText(Chunk.NEWLINE);
ct.AddText(Chunk.NEWLINE);

That’s all there is to it! Here is a screen shot of the rendered PDF. Note the numbered and bulleted lists in an irregular column.

samplePdf

Happy Programming!

Filed under: ,
IIS7, Global Themes, CSS Files and Subfolders
02 August 12 10:12 PM | Scott Mitchell

Ran into an interesting and esoteric problem today when working with a client. This client had a legacy ASP.NET 2.0 application deployed on IIS6 that used global themes. First, a little background…

A theme is: “a collection of property settings that allow you to define the look of pages and controls, and then apply the look consistently across pages in a Web application, across an entire Web application, or across all Web applications on a server.” Themes commonly include images and CSS files, the latter of which are automatically added to the <head> section of an ASP.NET page that uses said theme. Typically, themes are placed in the App_Theme folder within the web application; however, it is possible to create global themes that can be used across multiple websites on a single web server. When using global themes, the themes are placed in the aspnet_client/system_web/version/Themes folder, located at the webserver’s root (typically C:\Inetpub\wwwroot).

My client had been using IIS6 and global themes. In particular, the theme contained both images and CSS files, along with skins. The CSS files were all located in a subfolder of the theme named styles and the images in a subfolder named images, resulting in a directory structure like so:

MyTheme
  |
  |-- images
  |    |
  |    |-- image1.png
  |    |-- image2.png
  |    |-- ...
  |
  |-- styles
  |    |
  |    |-- main.css
  |    |-- admin.css
  |    |-- ...
  |
  |-- GridView.skin
  |-- DetailsView.skin
  |-- ...

When configuring a page to use MyTheme, the page’s rendered markup would automatically include references to the CSS files defined in the MyTheme\styles subfolder (main.css, admin.css, etc.). This worked swimmingly when used as a global theme in IIS6 or when used directly in the App_Themes folder (on both IIS6 and IIS7). However, when my client deployed to IIS7 using global theme structure above the CSS files were not included in the web page. There were no errors when visiting the page, but it clearly wasn’t styled.

After some poking and prodding I decided to try moving the CSS files out of the styles subfolder and into the MyTheme folder (alongside the skin files) and, voila, the CSS files were automatically included in the rendered page’s output. As to why the theme structure above works as a global theme in IIS6, as to why it works when deployed to the App_Themes folder in both IIS6 and IIS7, but as to why it does not work as a global theme is IIS7 is anyone’s guess. The good news is that there is a workaround and it is pretty straightforward.

Happy Programming!

Filed under:
Export an ADO.NET DataTable to Excel using NPOI
08 June 11 03:10 AM | Scott Mitchell

My latest article on DotNetSlackers looks at how to create Excel spreadsheets using NPOI. NPOI is a free, open-source .NET library for creating and reading Excel spreadsheets and is a port of the Java POI library. In the article I show how to use NPOI to programmatically export data into a spreadsheet with multiple sheets, formatting, and so on. Specifically, my demos look at having a set of objects to export – for example, a set of Linq-to-Sql entity objects – and then crafting an Excel spreadsheet by enumerating those objects and adding applicable rows and columns to the spreadsheet.

Recently, I needed the ability to allow for more generic exports to Excel. In one of the web applications I work on there is an Excel Export page that offers a number of links that, when clicked, populate an ADO.NET DataTable with the results of a particular database view, generate a CSV file, and then stream that file down to the client with a Content-Type of application/vnd.ms-excel, which prompts the browser to display the CSV content in Excel. This has worked well enough over the years, but unfortunately such data cannot be viewed from the iPad; however, the iPad can display a native Excel file (.xls). The solution, then, was to update the code to use NPOI to return an actual Excel spreadsheet rather than a CSV file.

To accomplish this I wrote a bit of code that exports the contents of any ol’ DataTable into an Excel spreadsheet using NPOI. It’s pretty straightforward, looping through the rows of the DataTable and adding each as a row to the Excel spreadsheet. There were, however, a couple of gotcha points:

  1. Excel 2003 limits a sheet inside a workbook to a maximum of 65,535 rows. To export more rows than this you need to use multiple sheets. Zach Hunter’s blog entry, NPOI and the Excel 2003 Row Limit, provided a simple approach to avoiding this problem. In short, I keep track of how many rows I’ve added to the current sheet and once it exceeds a certain threshold I create a new sheet and start from the top.
  2. Excel has limits and restrictions on the length of sheet names and what characters can appear in a sheet name. I have a method named EscapeSheetName that ensures the sheet name is of a valid length and does not contain any offending characters.
  3. When exporting very large Excel spreadsheets you may bump into OutOfMemoryExceptions if you are developing on a 32-bit system and are trying to dump the Excel spreadsheet into a MemoryStream object, which is a common technique for streaming the data to the client. See this Stackoverflow discussion for more information and possible workarounds: OutOfMemoryException When Generating a Large Excel Spreadsheet.

To demonstrate exporting a DataTable to Excel using NPOI, I augmented the code demo available for download from my DotNetSlackers article to include a new class in the App_Code folder named NPoiExport, which you can download from http://scottonwriting.net/demos/ExcelExportToDataTable.zip. This class offers an ExportDataTableToWorkbook method that takes as input a DataTable and the sheet name to use for the Excel workbook. (If there are multiple sheets needed, the second sheet is named “sheetName – 2,” the third, “sheetName – 3,” and so forth.)

The ExportDataTableToWorkbook method follows:

public void ExportDataTableToWorkbook(DataTable exportData, string sheetName)
{
    // Create the header row cell style
    var headerLabelCellStyle = this.Workbook.CreateCellStyle();
    headerLabelCellStyle.BorderBottom = CellBorderType.THIN;
    var headerLabelFont = this.Workbook.CreateFont();
    headerLabelFont.Boldweight = (short)FontBoldWeight.BOLD;
    headerLabelCellStyle.SetFont(headerLabelFont);

    var sheet = CreateExportDataTableSheetAndHeaderRow(exportData, sheetName, headerLabelCellStyle);
    var currentNPOIRowIndex = 1;
    var sheetCount = 1;

    for (var rowIndex = 0; rowIndex < exportData.Rows.Count; rowIndex++)
    {
        if (currentNPOIRowIndex >= MaximumNumberOfRowsPerSheet)
        {
            sheetCount++;
            currentNPOIRowIndex = 1;

            sheet = CreateExportDataTableSheetAndHeaderRow(exportData, 
                                                            sheetName + " - " + sheetCount, 
                                                            headerLabelCellStyle);
        }

        var row = sheet.CreateRow(currentNPOIRowIndex++);

        for (var colIndex = 0; colIndex < exportData.Columns.Count; colIndex++)
        {
            var cell = row.CreateCell(colIndex);
            cell.SetCellValue(exportData.Rows[rowIndex][colIndex].ToString());
        }
    }
}

Whenever a new sheet needs to be generated – either when starting or when the maximum number of rows per sheet is exceeded – the CreateExportDataTableSheetAndHeaderRow method is called. This method creates a header row, listing the name of each column in the DataTable.

protected Sheet CreateExportDataTableSheetAndHeaderRow(DataTable exportData, string sheetName, CellStyle headerRowStyle)
{
    var sheet = this.Workbook.CreateSheet(EscapeSheetName(sheetName));

    // Create the header row
    var row = sheet.CreateRow(0);

    for (var colIndex = 0; colIndex < exportData.Columns.Count; colIndex++)
    {
        var cell = row.CreateCell(colIndex);
        cell.SetCellValue(exportData.Columns[colIndex].ColumnName);

        if (headerRowStyle != null)
            cell.CellStyle = headerRowStyle;
    }

    return sheet;
}

Here’s how you would go about using the NpoiExport class to export a DataTable and then stream it down to the client:

  1. Create and populate the DataTable with the data to export. Remember, the DataTable’s column names are what will appear in the header row so use aliases in the SQL query to provide more description/formatted column names, if you prefer.
  2. Create an instance of the NpoiExport class.
  3. Call the object’s ExportDataTableToWorkbook method passing in the DataTable from step 1 (along with a sheet name of your choice).
  4. Set the Content-Type and Content-Disposition response headers appropriately and then stream down the contents of the Excel document, which is accessible via the NpoiExport object’s GetBytes method.

The following code snippet illustrates the above four steps.

// Populate the DataTable
var myDataTable = new DataTable();
using (var myConnection = new SqlConnection(connectionString)
{
    using (var myCommand = new SqlCommand())
    {
        myCommand.Connection = myConnection;
        myCommand.CommandText = sqlQuery;

        using (var myAdapter = new SqlDataAdapter(myCommand))
        {
            myAdapter.Fill(myDataTable);
        }
    }
}


// Creat the NpoiExport object
using (var exporter = new NpoiExport())
{
    exporter.ExportDataTableToWorkbook(myDataTable, "Results");

    string saveAsFileName = string.Format("Results-{0:d}.xls", DateTime.Now);

    Response.ContentType = "application/vnd.ms-excel";
    Response.AddHeader("Content-Disposition", string.Format("attachment;filename={0}", saveAsFileName));
    Response.Clear();
    Response.BinaryWrite(exporter.GetBytes());
    Response.End();
}

That’s all there is to it. The screen shot below shows an example of the exported report. Note that it lacks the nice formatting, auto-sized columns, and other bells and whistles that are possible with NPOI when constructing a report by hand (as I showed in Create Excel Spreadsheets Using NPOI), but it does make exporting data to Excel an exercise of just a few lines of code. And its exported data that can be opened and viewed from an iPad.

ExcelOutput

The above Excel spreadsheet was created using the ad-hoc query:

SELECT CategoryName AS [Category], 
       Description, 
       (SELECT COUNT(*) 
        FROM Products 
        WHERE Products.CategoryID = Categories.CategoryID) 
            AS [Product Count]
FROM Categories
WHERE CategoryID >= 3

Happy Programming!

Download: http://scottonwriting.net/demos/ExcelExportToDataTable.zip

Filed under:
Terse Markup and CSS for Aligned Form Labels and Inputs
18 April 11 09:46 PM | Scott Mitchell | 3 comment(s)

Like many ASP.NET developers, I am most comfortable working with C# and VB. I know just enough HTML and CSS to be dangerous. I know enough to implement the overarching page layout without using <table>s and instead to use CSS to position, size, and float the elements on the page, but when it comes to certain user interface designs within a page I’m quick to use <table>s. For instance, if asked to create a contact form like the one pictured below, my first inclination would be to use a trusty <table>.

AlignedUI

Recently, my friend and colleague Dave Yates showed me a website he had helped design and implement, StonehengeStyle. I was particularly interested when Dave showed me the contact page, which contained very clean, terse, readable markup without the use of <table>s.

First, each right-aligned text label – Name, Phone, and so on - is implemented as a <label> element. The input elements for collecting the input are simply <input>, <textarea> or <select> elements. For example, the markup for the Name, Phone, and Email Address inputs follows. (Note: I removed the required indicator for brevity; view the contact form’s markup in your browser for the complete markup.)

<label for="name">Name</label> 
<input name="name" type="text" id="name" class="textfield" /> 

<label for="phone">Phone</label> 
<input name="phone" type="text" id="phone" class="textfield" /> 

<label for="email">Email Address</label> 
<input name="email" type="text" id="email" class="textfield" /> 

Couldn’t be simpler, right? No filler <br /> or <p> elements, no <table>s cluttering things up. Heck, no <div>s, even.

The layout of the <label> and <input> elements is handled in the CSS via the following rules. First, all <label> elements are styled such that they are 180 pixels wide with 3 pixel padding on the top, 10 pixel padding on the right, and 2 pixel padding on the bottom. Their text is right-aligned and they clear left floating elements, which is why each <label> appears beneath the one above it.

label {
    clear: left;
    float: left;
    padding: 3px 10px 2px;
    text-align: right;
    width: 180px;
}

The textfield, textarea, and selectlist CSS classes, which assigned to <input>, <textarea>, and <select> elements in this contact form, specify a width of 250 pixels with a bottom margin of 8 pixels.

.textfield, .textarea, .selectlist {
    font-family: Arial,Helvetica,sans-serif;
    font-size: 12px;
    margin: 0 0 8px;
    width: 250px;
}

And that’s all there is to it! Pretty simple and straightforward.

This may not be terribly exciting for seasoned web developers or designers, but for someone like me, with just a working knowledge of HTML and CSS, this markup/CSS pattern is a gem and is how I’ve started doing my contact forms and other similar in-page layouts.

Happy Programming!

Filed under:
My Latest Articles From Around the Web
01 March 11 04:01 AM | Scott Mitchell

In addition to my regular articles on 4GuysFromRolla.com, I’ve recently authored a number of articles that have appeared on other websites:

  • Use ASP.NET and DotNetZip to Create and Extract ZIP Files - This article shows how to use DotNetZip to create and extract ZIP files in an ASP.NET application, and covers advanced features like password protection and encryption. (DotNetZip is a free, open source class library for manipulating ZIP files and folders.)
  • Creating a Login Overlay - Traditionally, websites that support user accounts have their visitors sign in by going to a dedicated login page where they enter their username and password. This article shows how to implement a login overlay, which is an alternative user interface for signing into a website.
  • 5 Helpful DateTime Extension Methods - This article presents five DateTime extension methods that I have used in various projects over the years. The complete code, unit tests, and a simple demo application are available for download. Feel free to add these extension methods to your projects!

To keep abreast of my latest articles - and to read my many insightful witticisms Smile - follow me on Twitter @ScottOnWriting.

Filed under:
Customizing ELMAH’s Error Emails
06 January 11 10:40 PM | Scott Mitchell | 3 comment(s)

ELMAH (Error Logging Modules and Handlers) is my ASP.NET logging facility of choice. It can be added to a new or running ASP.NET site in less than a minute. It’s open source and it’s creator, Atif Aziz, remains actively involved with the project and can be found answering questions about ELMAH, from Stackoverflow to ELMAH’s Google Discussion group. What’s not to love about it?

ELMAH’s sole purpose is to log and notify developers of errors that occur in an ASP.NET application. Error details can be logged to any number of log sources – SQL Server, MySQL, XML, Oracle, and so forth. Likewise, when an error occurs ELMAH can notify developers by sending the error details to one or more email addresses.

The notification email message sent by ELMAH is pretty straightforward: it contains the exception type and message, the date and time the exception was generated, the stack trace, a table of all server variables, and the Yellow Screen of Death that was generated by the error.

image

Prior to sending the notification email, ELMAH’s ErrorMailModule class raises its Mailing event. If you create an event handler for this event you can inspect details about the error that just occurred and modify the email message that is about to be sent. In this way you can customize the notification email, perhaps setting the priority based on the error or cc’ing certain email addresses if the error has originated from a particular page on the website.

To create an event handler for the Mailing event, open (or create) the Global.asax file and add the following syntax:

void ErrorMailModuleName_Mailing(object sender, Elmah.ErrorMailEventArgs e)
{
    ...
}

In the above code snippet, replace ErrorMailModuleName with the name you assigned the ErrorMailModule HTTP Module. This module may be defined in one or two places: the <system.web>/<httpModule> section and/or the <system.webServer>/<modules> section. The following Web.config snippet shows both sections:

<system.web>
    <httpModules>
        <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
        ...
    </httpModules>

    ...    
</system.web>

<system.webServer>
    <modules>
        <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
        ...
    </modules>
    
    ...
</system.webServer>

The name for the module in both sections should be the same - in the above snippet the name is ErrorMail. Consequently, to create an event handler for ELMAH’s Mailing event with the above configuration we would use the syntax:

void ErrorMail_Mailing(object sender, Elmah.ErrorMailEventArgs e)
{
    ...
}

Note that the Mailing event handler’s second input parameter is of type ErrorMailEventArgs. This class provides two helpful properties:

  • Error – the error that was just logged by ELMAH. This property is of type Elmah.Error and has properties like Exception, Message, User, Time, and so on.
  • Mail – the MailMessage object that is about to be sent. This gives you an opportunity to modify the outgoing email.

The following Mailing event handler shows how you could adjust the notification email based on the type of exception that occurred. Here, if the error that just occurred was an ApplicationException then the notification email is set to a High priority, it’s subject it changed to “This is a high priority item!”, and mitchell@4guysfromrolla.com is cc’d.

void ErrorMail_Mailing(object sender, Elmah.ErrorMailEventArgs e)
{
    if (e.Error.Exception is ApplicationException ||
        (e.Error.Exception is HttpUnhandledException && 
            e.Error.Exception.InnerException != null &&
            e.Error.Exception.InnerException is ApplicationException))
    {
        e.Mail.Priority = System.Net.Mail.MailPriority.High;
        e.Mail.Subject = "This is a high priority item!";
        e.Mail.CC.Add("mitchell@4guysfromrolla.com");
    }
}

Note that if an unhandled ApplicationException is what prompted ELMAH to record the error then by this point the original exception will have been wrapped in an HttpUnhandledException. So in the if statement above I check to see if the error’s Exception property is an ApplicationException or if it is an HttpUnhandledException exception with an InnerException that is an ApplicationException. If either of those conditions hold then I want to customize the notification email.

Happy Programming!

Filed under:
Resetting Form Field Values in an ASP.NET WebForm
23 December 10 02:14 AM | Scott Mitchell | 3 comment(s)

A recent question on Stackoverflow.com asked if there was a general method to clear a form in ASP.NET. The person asking the question had a form with many TextBox and DropDownList controls and wanted some way to be able to “reset” all of those values; specifically, the TextBoxes would be cleared out and the DropDownLists would have their first item selected.

At first blush, this seems like a job for the reset button. HTML has long supported the ability to reset a form by clicking a reset button, which is a button of type reset.

<input type="reset" value="Reset Form!" />

The reset button’s functionality can also be invoked from JavaScript by calling the form object’s reset function. The following snippet of script checks to see if there is at least one form in the document and, if so, calls its reset function:

if (document.forms && document.forms[0])
    document.forms[0].reset();

The Problem with Reset…

However, there is a potential issue when resetting form values in an ASP.NET WebForms application using this approach. The issue arises because the reset button (or reset function) does not clear out textboxes and return drop-down lists to their first value, but instead returns the forms fields to their initial values. That is, if you have a page with a textbox whose markup contains a value attribute, like:

<input type="text" value="Hello, World!" ... />

And then a user changes the textbox’s text to “Goodbye!” and then clicks the reset button, the textbox does not go blank – rather, it reverts to “Hello, World!” In an ASP.NET WebForm application anytime there is a postback on the page – such as if there is a DropDownList whose AutoPostBack property is set to True – the page’s markup is re-rendered and the text values and DropDownList selections made by the user are remembered because the re-rendered markup includes the user-entered values. Long story short, if you use the reset button approach described above and have a form where there are postbacks going on, if a user enters values into textboxes (or drop-downs), then does a postback, then clicks the reset button, the form fields will be reset to their values immediately after the postback and not to empty textboxes.

jQuery to the Rescue!

The good news is that with just a couple of lines of jQuery code we can implement the reset functionality we desire, regardless of postbacks. The following two lines of script set the value of all textboxes on the page to an empty string and set the selectedIndex of all drop-down lists on the page to be 0, which selects the first item:

$("input[type=text]").val("");
$("select").attr('selectedIndex', 0);

That’s all there is to it! You could tinker with the selector syntax to limit the affected textboxes and drop-downs to those in a specific <div> or form or whatnot; likewise, you could add additional lines of code if you need to reset checkboxes, radio buttons, or other input fields.

A Server-Side Approach…

If the client-side approach doesn’t cut it for you, you can opt to reset form fields using server-side logic. You could, of course, set the Text property of each TextBox control to an empty string and clear the selections of all DropDownLists, but a more general approach is possible using recursion. Each ASP.NET control has a Controls  property that contains its children controls. Put together, the controls in an ASP.NET page form a control hierarchy. We can recurse through this control hierarchy, examining each control and modifying any TextBoxes and DropDownLists we come across.

The following code snippet illustrates such a recursive method, ClearInputs. Note that ClearInputs is passed in a ControlCollection object. This collection is enumerated and checked for TextBoxes and DropDownLists. If a TextBox is found, its Text property is set to string.Empty; if a DropDownList is found its ClearSelection method is invoked. Finally, the ClearInputs method is called again and passed the current control’s Controls collection for it to be examined.

void ClearInputs(ControlCollection ctrls)
{
    foreach (Control ctrl in ctrls)
    {
        if (ctrl is TextBox)
            ((TextBox)ctrl).Text = string.Empty;
        else if (ctrl is DropDownList)
            ((DropDownList)ctrl).ClearSelection();

        ClearInputs(ctrl.Controls);
    }
}

To reset all TextBox and DropDownList values you’d call this method like so:

ClearInputs(Page.Controls);

To reset the TextBoxes and DropDownLists in a particular control (such as a Panel), you’d call ClearInputs passing in that control’s Controls collection.

Happy Programming!

Filed under: ,
Checking All CheckBoxes in a GridView Using jQuery
04 December 10 02:41 AM | Scott Mitchell | 3 comment(s)

How do I love thee, jQuery? Let me count the ways.

In May 2006 I wrote an article on 4GuysFromRolla.com titled Checking All CheckBoxes in a GridView Using Client-Side Script and a Check All CheckBox, in which I showed how to add a column of checkboxes to a GridView along with a checkbox in that column’s header that enabled the user to check/uncheck all checkboxes in one fell swoop. This check/uncheck all functionality was accomplished using JavaScript.

While the JavaScript presented in the article worked then (and still works today, of course), it is a less than ideal approach for a couple of reasons.

  • First, each checkbox in the grid is programmatically assigned a client-side onclick event handler in the GridView’s DataBound event handler that calls a function that determines whether to check or uncheck the checkbox in the head – having a client-side event handler defined directly in an HTML element violates the design goal of unobstrusive JavaScript.
  • Second, because programmatically assigned client-side attributes are not remembered across postbacks and because these client-side attributes are only assigned when data is bound to the grid, the script is lost when there is a postback that doesn’t cause the grid to have its data re-bound to it. Long story short, the check/uncheck all functionality stops working after such postbacks. I provide a workaround for this, but it’s extra steps, extra script, and another thing that you have to remember to do.
  • Third, the solution entails quite a bit of script, much more than is necessary using modern JavaScript libraries.

When I authored the article jQuery had not yet been released. Fortunately, today we have jQuery (and other JavaScript libraries) at our fingertips. jQuery is a free, open-source JavaScript library created by John Resig. In a nutshell, jQuery allows us to accomplish common client-side tasks with terse, readable script. With jQuery, I can rewrite the entire GridView check/uncheck all functionality with zero lines of server-side code and a scant 25 or so lines of JavaScript.

To demonstrate jQuery’s power, consider a GridView with the following markup:

<asp:GridView ID="gvProducts" runat="server" ...>
    <Columns>
        <asp:TemplateField>
            <HeaderTemplate>
                <asp:CheckBox runat="server" ID="chkAll" />
            </HeaderTemplate>
            <ItemTemplate>
                <asp:CheckBox runat="server" ID="chkSelected" />
            </ItemTemplate>
        </asp:TemplateField> 
        ...
    </Columns>
</asp:GridView>

Note the TemplateField – this is where the two CheckBox controls live. The CheckBox control in the HeaderTemplate (chkAll) is the check/uncheck all checkbox, while the CheckBox control in the ItemTemplate (chkSelected) is the checkbox that appears in each of the grid’s data rows.

Now, I need script that does the following:

  1. When one of the chkSelected checkboxes is checked or unchecked, I need to determine whether the all option needs to be checked or unchecked. In the case where all chkSelected checkboxes are checked, I want to check chkAll. Likewise, in the case when any chkSelected checkbox is unchecked, I want to uncheck chkAll.
  2. When chkAll is checked or unchecked, I need to check or uncheck all chkSelected checkboxes.

To address the first concern I created a function named CheckUncheckAllCheckBoxAsNeeded. This function determines the total number of chkSelected checkboxes in the grid and the number of checked chkSelected checkboxes. If the two numbers match then chkAll is checked, otherwise it’s unchecked.

function CheckUncheckAllCheckBoxAsNeeded() {
    var totalCheckboxes = $("#<%=gvProducts.ClientID%> input[id*='chkSelected']:checkbox").size();
    var checkedCheckboxes = $("#<%=gvProducts.ClientID%> input[id*='chkSelected']:checkbox:checked").size();

    if (totalCheckboxes == checkedCheckboxes) {
        $("#<%=gvProducts.ClientID%> input[id*='chkAll']:checkbox").attr('checked', true);
    }
    else {
        $("#<%=gvProducts.ClientID%> input[id*='chkAll']:checkbox").attr('checked', false);
    }
}

This function is executed whenever one of the chkSelected checkboxes is checked or unchecked. This event wiring is handled in the $(document).ready event handler. Also, the CheckUncheckAllCheckBoxAsNeeded function is called right off the bat in case the grid’s checkboxes are already all checked when the page loads.

$(document).ready(function () {
    $("#<%=gvProducts.ClientID%> input[id*='chkSelected']:checkbox").click(CheckUncheckAllCheckBoxAsNeeded);

    ...

    CheckUncheckAllCheckBoxAsNeeded();
});

Finally, we need to check/uncheck all chkSelected checkboxes when chkAll is checked or unchecked. This logic is also in the $(document).ready event handler (where the ellipsis is positioned in the above snippet).

$("#<%=gvProducts.ClientID%> input[id*='chkAll']:checkbox").click(function () {
    if ($(this).is(':checked'))
        $("#<%=gvProducts.ClientID%> input[id*='chkSelected']:checkbox").attr('checked', true);
    else
        $("#<%=gvProducts.ClientID%> input[id*='chkSelected']:checkbox").attr('checked', false);
});

Pretty neat and a whole heck of a lot simpler than the technique I initially showcased in Checking All CheckBoxes in a GridView Using Client-Side Script and a Check All CheckBox. A more detailed look at this code, along with a downloadable working example, will be on 4Guys within the next couple of weeks.

UPDATE [2010-12-07]: A 4Guys article that provides much more detail and screen shots and a downloadable demo is now available: Checking All Checkboxes in a GridView Using jQuery. Also, special thanks to Elijah Manor, who offered a number of suggestions on how to improve and tighten up my jQuery script.

Happy Programming!

Filed under: ,
Just Where Is WebResource.axd?
28 October 10 10:25 PM | Scott Mitchell | 7 comment(s)

I stumbled upon and answered a question at Stackoverflow this morning – Where is WebResource.axd? – and thought it might be worth to elaborate a bit on the question and answer here, on my blog.

But first, imagine you are developing a Web control or a library or a framework that requires certain external resources, such as images, JavaScript, and CSS. When developing your control/library/framework you may have such external content sitting in a particular folder in your application, but when you get ready to package your control/library/framework you want the end product to be a single assembly (that is, a single DLL file), and not an assembly plus a folder for images and a folder for JavaScript files and a folder for CSS files. In other words, you do not want to require that your users – other web developers – have to add a bunch of folders and images/JavaScript/CSS files to their website to start using your control/library/product; rather, you want everything to work once the developer drops your assembly into their Bin folder.

Such functionality is possible by using embedding resources. An embedded resource is a resource file – like an image, JavaScript, or CSS file – that is embedded within the compiled assembly. This allows a control/library/framework developer to embed any external resources into the assembly, thereby having the entire application existing in one single file. Consider ASP.NET’s validation controls. These controls require that certain JavaScript functions be present in order for them to use client-side validation. When using the validation controls you don’t need to add any JavaScript files to your website; instead, the JavaScript used by these controls is embedded in one of the built-in ASP.NET assemblies. But if the external resources are embedded in the assembly, how do you get it out of the assembly and onto a web page?

The answer is WebResource.axd. WebResource.axd is an HTTP Handler that is part of the .NET Framework that does one thing and one thing only – it is tasked with getting an embedded resource out of a DLL and returning its content. What DLL to go to and what embedded resource to take are specified through the querystring. For instance, a request to www.yoursite.com/WebResource.axd?d=EqSMS…&amp;t=63421… might return a particular snippet of JavaScript embedded in a particular assembly. The d querystring parameter contains encrypted information that specifies the assembly and resource to return; the t querystring parameter is a timestamp and is used to only allow requests to that resource using that URL for a certain window of time.

To see WebResource.axd in action, create an ASP.NET Web page that includes some validation controls, visit the page in a browser, and then do a View/Source. You will see a number of <script> tags pulling in JavaScript from WebResource.axd like so:

<script src="/YourSite/WebResource.axd?d=fs7zUa...&amp;t=6342..." type="text/javascript"></script>

<script src="/YourSite/WebResource.axd?d=EqSMSn...&amp;t=6342..." type="text/javascript"></script>

Here, WebResource.axd is pulling out embedded JavaScript from an assembly and returning that JavaScript to the browser. If you plug in those URLs into your browser’s Address bar you’ll see the precise JavaScript returned.

Ok, so now that we know what WebResource.axd is and what it does the next question is, where is it? Clearly, there’s no file named WebResource.axd in your website – what’s going on here? Here’s my answer from the Stackoverflow question:

.axd files are typically implemented as HTTP Handlers. They don't exist as an ASP.NET web page, but rather as a class that implements the IHttpHandler interface. If you look in the root Web.config (%WINDIR%\Microsoft.NET\Framework\version\Config\Web.config) you'll find the following entry:

<add path="WebResource.axd" 
     verb="GET" 
     type="System.Web.Handlers.AssemblyResourceLoader" 
     validate="True" />

This entry says, "Hey, if a request comes in for WebResource.axd then use the HTTP Handler AssemblyResourceLoader in the System.Web.Handlers namespace.

The code for this class is a bit lengthy, so I can't post it here, but you can use a disassembler like the free Reflector to view this class's source code. You could probably get the original source code (with comments) by using the NetMassDownloader tool.

So there you have it. WebResource.axd is an HTTP Handler built into the .NET Framework that retrieves embedded resources from assemblies.

To learn more about WebResource.axd and how to go about embedding resources in an assembly, refer to my article, Accessing Embedded Resources through a URL using WebResource.axd.

Happy Programming!

Filed under:
Returning Dynamic Types from an Ajax Web Service Using C# 4.0
26 October 10 06:40 AM | Scott Mitchell | 4 comment(s)

Over at 4Guys I’m authoring a series of articles showing different techniques for accessing server-side data from client script. The most recent installment (Part 2) shows how to provide server-side data through the use of an Ajax Web Service and how to consume that data using either a proxy class created by the ASP.NET Ajax Library or by communicating with the Ajax Web Service directly using jQuery.

When returning data from a service it’s not uncommon to create a specialized Data Transfer Object (or DTO), and in Part 2 I create two such DTO classes. Here’s the basic design pattern:

  1. Create a DTO class that has properties that model the data you wan to return. For instance, the following DTO class can be used to transmit CategoryID, CategoryName, and Description information about one or more categories.
  2. public class CategoryDTO
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
        public string Description { get; set; }
    }
  3. In the service, get the data of interest from your object layer. In the demo for this article series I use Linq-To-Sql as my data access layer and object model. Here is code from the Ajax Web Service’s GetCategories method that retrieves information about the categories in the system:
  4. [WebMethod]
    public CategoryDTO[] GetCategories()
    {
        using (var dbContext = new NorthwindDataContext())
        {
            var results = from category in dbContext.Categories
                            orderby category.CategoryName
                            select category;
    
            ...
        }
    }
  5. Now the results need to be mapped from the domain object to the DTO. This can be done manually or by using a library like AutoMapper. In the above example, we would iterate through the results to create an array of CategoryDTO objects, which is what would be returned.

If you are using C# 4.0 you can choose to live in a looser-typed world. Rather than having the Ajax Web Service return a strongly-typed value (namely, an array of CategoryDTO objects) you could instead opt to have a more ethereal return type – dynamic! Having a return type of dynamic allows you to return an anonymous type, meaning you don’t need to create a DTO nor do you need to map the domain object to the DTO. Instead, you’d just create an anonymous type, like so:

[WebMethod]
public dynamic GetCategories()
{
    using (var dbContext = new NorthwindDataContext())
    {
        var results = from category in dbContext.Categories
                        orderby category.CategoryName
                        select new
                        {
                            category.CategoryID,
                            category.CategoryName,
                            category.Description
                        };

        return results.ToArray();
    }
}

Note the dynamic keyword as the method’s return type. Also note that results is a query that returns an enumeration of anonymous types, each of which has three properties – CategoryID, CategoryName, and Description. The call to the ToArray method executes the query and returns the array of anonymous types as the method’s output. Because the anonymous type’s properties. The client-side script calling this method can work with the returned anonymous type using the exact same code as with the strongly-typed return type.

Happy Programming!

Enumerating Through XML Elements Using LINQ-to-XML
28 September 10 09:25 PM | Scott Mitchell | 1 comment(s)

4Guys reader Dan D. recently emailed me with an inquiry surrounding my article series, Building a Store Locator ASP.NET Application Using Google Maps API, specifically on how to access a different set of XML elements within the XML data returned from the Google Maps API’s geocoding service. Google’s geocoding service is offered as a URL that, when requested, returns information about a particular address. For instance, if you point your browser to http://maps.google.com/maps/api/geocode/xml?address=1600+Pennsylvania+Ave,+Washington+D.C.&sensor=false you should see an XML response that indicates whether the address is valid, the formatted address, the components that make up the address, and geographical information about the address, including the latitude and longitude coordinates.

This geocoding service is used by the Store Locator application in two ways:

  1. To validate the user-entered address. If the user enters an ambiguous address, like Springfield, then the geocoding service will return possible matches. These are displayed to the user, allowing her to choose which address she meant.
  2. To determine the latitude and longitude coordinates of the user-entered address. These coordinates are used to retrieve those stores that are nearby.

The Store Location application includes a method named GetGeocodingSearchResults that, when called, makes an HTTP request to the geocoding service and returns the results as an XElement object, one of the key components of LINQ-to-XML.

Dan’s question follows:

I have a question with regards to accessing the elements contained within the address_components[] array.  Specifically, I would like to return the long and short names for locality and country.  I was wondering if you could post an small article on how to iterate through the XML array components loaded into the XElement.

The address_components[] array Dan refers to is the set of <address_component> elements returned by the geocoding service. Again, visit http://maps.google.com/maps/api/geocode/xml?address=1600+Pennsylvania+Ave,+Washington+D.C.&sensor=false. Note how there are multiple <address_component> elements detailing the type and long and short names for each component of the address. For the address 1600 Pennsylvania Ave, Washington D.C. there are the following address components:

<result>
  <address_component>
   <long_name>1600</long_name>
   <short_name>1600</short_name>
   <type>street_number</type>
  </address_component>
  <address_component>
   <long_name>Pennsylvania Ave NW</long_name>
   <short_name>Pennsylvania Ave NW</short_name>
   <type>route</type>
  </address_component>
  <address_component>
   <long_name>Washington</long_name>
   <short_name>Washington</short_name>
   <type>locality</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>Washington</long_name>
   <short_name>Washington</short_name>
   <type>administrative_area_level_3</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>District of Columbia</long_name>
   <short_name>District of Columbia</short_name>
   <type>administrative_area_level_2</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>District of Columbia</long_name>
   <short_name>DC</short_name>
   <type>administrative_area_level_1</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>United States</long_name>
   <short_name>US</short_name>
   <type>country</type>
   <type>political</type>
  </address_component>
  <address_component>
   <long_name>20500</long_name>
   <short_name>20500</short_name>
   <type>postal_code</type>
  </address_component>
  ...
</result>

Note that each <address_component> element has a <long_name> and <short_name> child element, and one or more <type> child elements.

To simple iterate through each <address_component> element we could use the following code:

var results = GoogleMapsAPIHelpersCS.GetGeocodingSearchResults(“...”);

var addressComponents = results.Element("result").Elements("address_component");
foreach (var component in addressComponents)
{
    var longName = component.Element("long_name").Value;
    var shortName = component.Element("short_name").Value;

    var types = new List<string>();
    foreach (var type in component.Elements("type"))
        types.Add(type.Value);

    // At this point you can do whatever it is you want to do 
    // with the longName, shortName, and types information for
    // this component...
    if (types.Contains("locality") || types.Contains("country"))
        Response.Write(string.Format("<p>LongName = {0}, ShortName = {1}, Types = {2}</p>",
                                    longName, 
                                    shortName, 
                                    string.Join(", ", types.ToArray())
                                )
                    );
}

Here, we reference the set of <address_component> elements using results.Element(“result”).Elements(“address_components”), where results is the XElement object returned from the GetGeocodingSearchResults method. The Element(“results”) call gets a reference to the <result> XML element, while Elements(“address_component”) gives us the enumerable collection of <address_component> elements, which we then can loop through.

Inside the loop we get the values of the <long_name> and <short_name> XML elements and then loop through the set of <type> elements, the value of each to a List of strings (types). Finally, we can do what Dan is interested in doing – determine if the address component is for the locality or country and, if so, do something with the long and short names. Here, I simply display them via a Response.Write statement.

Another option is to use LINQ to create an anonymous type that models the information of interest. The following statement creates a variable named addressComponents2 that is an enumeration of anonymous objects that have three properties: LongName, ShortName, and Types, which contain the values of the <long_name>, <short_name>, and <type> elements for each <address_component>.

var results = GoogleMapsAPIHelpersCS.GetGeocodingSearchResults(“...”);

var addressComponents2 =
        from component in results.Element("result").Elements("address_component")
        select new
        {
            LongName = component.Element("long_name").Value,
            ShortName = component.Element("short_name").Value,
            Types = (from type in component.Elements("type")
                        select type.Value).ToArray()
        };

We can now filter the results using the Where method:

var filteredAddressComponents = addressComponents2
                                    .Where(addr => addr.Types.Contains("locality") ||
                                                    addr.Types.Contains("country"));

And now enumerating over filteredAddressComponents returns just those address components for the locality or country types. The following loop walks through each of these and emits the LongName, ShortName, and Types property values. Note how these are actual properties and not strings, meaning we have strong typing, which brings with it the benefits of IntelliSense and compile-time support.

// At this point you can use a foreach loop to 
// walk through the various components
foreach (var addr in filteredAddressComponents)
{
    Response.Write(string.Format("<p>LongName = {0}, ShortName = {1}, Types = {2}</p>",
                                addr.LongName, 
                                addr.ShortName, 
                                string.Join(", ", addr.Types.ToArray())
                            )
                );
}

Happy Programming!

Filed under:
Adding a RESTful Service to My Boggle Solver
11 September 10 02:47 AM | Scott Mitchell | 5 comment(s)

This blog post has been deprecated. Please see Updating My Online Boggle Solver Using jQuery Templates and WCF for an updated discussion on the solver service, the data it returns, and how to call it from a web page.

My immediate and extended family enjoys playing games, and one of our favorites is Boggle. Boggle is a word game trademarked by Parker Brothers and Hasbro that involves several players trying to find as many words as they can in a 4x4 grid of letters. At the end of the game, players compare the words they found. During this comparison I've always wondered what words we may have missed. Was there some elusive 10-letter word that no one unearthed? Did we only discover 25 solutions when there were 200 or more?

To answer these questions I created a Boggle solver web application (back in 2008) that prompts a user for the letters in the Boggle board and then recursively explores the board to locate (and display) all available solutions. This Boggle solver is available online - fuzzylogicinc.net/Boggle. My family uses it every time we get together and play Boggle. For more information on how it works and to get your hands on the code, check out my article, Creating an Online Boggle Solver.

Recently, I’ve been working on some projects that have involved creating RESTful web services using WCF. Being Friday, I decided to have a little fun and add a RESTful interface to the Boggle solver. This was actually quite easy to do and took all of 5 minutes.

Creating the Boggle Solver Service

I started by adding a new item to my website of type WCF Service, naming it Solver.svc. This created three files:

  • Solver.scr
  • ISolver.cs
  • Solver.cs

In the contract (ISolver.cs) I added a single method, Solve, that accepts two inputs: a string representing the board and a string indicating the minimum number of letters for a word to be considered a solution. (Boggle rules allow for words of three letters or more, but house rules only count words that are four letters or longer.) I then used the WebGet attribute to indicate that the board and length input parameters would be specified via the querystring fields BoardID and Length, and that the resulting output should be formatted as JSON.

[ServiceContract]
public interface ISolver
{
    [OperationContract]
    [WebGet(UriTemplate = "?BoardID={board}&Length={length}", ResponseFormat=WebMessageFormat.Json)]
    BoggleWordDTO[] Solve(string board, string length);
}

Note that the Solve method returns an array of BoggleWordDTO objects. This is a new class I created to represent the data to transmit from the service. This class has two properties:

  • Word – a string value that represents a word found in the Boggle board, and
  • Score – the score for that solution. According to the official rules, three and four letter words are worth 1 point, five letter words are worth 2, six letter words worth 3, seven letter words worth 5, and eight letter words and longer worth 11.

The Solve method implementation (Solver.cs) is pretty straightforward. It starts with a bit of error checking to ensure that the passed in board and letter information is kosher. Next, it creates a BoggleBoard object, specifying the minimum number of letters for a solution and the board contents. Then the BoggleBoard object’s Solve method is invoked, which performs the recursion and computes the set of solutions (as an object of type BoggleWordList). The solutions are then converted into an array of BoggleWordDTO objects, which is then returned to the client.

public BoggleWordDTO[] Solve(string board, string length)
{
    ...

    // Create the BoggleBoard
    BoggleBoard bb = new BoggleBoard(
                        letters,
                        board[0].ToString(), ..., board[15].ToString()
                    );

    // Solve the Boggle board
    var solutions = bb.Solve();

    // Populate and return an array of BoggleWordDTO objects
    return solutions
                .Select(s => new BoggleWordDTO()
                {
                    Word = s.Word,
                        Score = s.Score
                })
                .ToArray();
}

Because the service is configured to return the data using JSON, the results are serialized into a JSON array.

In addition to creating the Solver-related files and writing the code I noted, I also had to add <system.serviceModel> configuration to Web.config to permit HTTP access to the service and to enable ASP.NET compatibility. The reason I had to enable ASP.NET compatibility is because the dictionary used by the solver is a text file stored on disk, and the solver gets the path to that text file using Server.MapPath (namely, HttpContext.Current.Server.MapPath(“…”)). Without ASP.NET compatibility, HttpContext.Current is null when the service attempts to solve and then the call to Server.MapPath blows up. Also, I had to specify the Factory attribute in the <%@ ServiceHost %> directive of the Solver.svc file.

[UPDATE: 2010-09-10] Ben Amada posted a helpful comment pointing me to the existence of the HostingEnvironment.MapPath method, which does the same work as Server.MapPath but doesn’t require an HttpContext object. I updated this code accordingly. I also updated the code that cached the dictionary in memory, replacing the use of HttpContext.Current.Cache with HttpRuntime.Cache, which I probably should have been using all along. The code has been updated. Thanks, Ben!

Using the Boggle Solver Service

To use the service, just point your browser (or your code/script) to:  http://fuzzylogicinc.net/Boggle/Solver.svc?BoardID=board&Length=length. The board value should be entered as a string of the characters in the Boggle board, starting from the upper left corner and reading to the right and down. For example, if you had the board:

r e i b
t m f w
i r a e
r h s t 

You would use a board value of reibtmfwiraerhst. letter should be a number between 3 and 6, inclusive.

So, to find all solutions to the above board that are four or more letters, you’d visit: http://fuzzylogicinc.net/Boggle/Solver.svc?BoardID=reibtmfwiraerhst&Length=4

Doing so would return the following (abbreviated) JSON:

[{"Score":1,"Word":"amir"},{"Score":2,"Word":"amirs"},{"Score":1,"Word":"awes"},{"Score":1,"Word":"bier"},{"Score":1,"Word":"ears"},{"Score":1,"Word":"east"},...]

The above JSON represents an array of objects, where each object has two properties, Score and Word.

So how can this service be used? Well, with a bit of JavaScript you can call the service from a browser and display the results dynamically. I’ve included a rudimentary example in the download (which you can find at the end of this blog post) that prompts the user to enter the 16 characters for the board and the minimum number of letters. It then uses jQuery’s getJSON function to make a call to the service and get the data back. The JSON array is then enumerated and a series of list items are constructed, showing each solution in a bulleted list.

Here is the web page when you visit it and enter a boggle board and the minimum number of letters (but before you click the “Find All Words!” button.

BoggleBoard2

Clicking the “Find All Words!” button executes the following jQuery script:

$.getJSON(
    "Solver.svc",
    {
        "BoardID": $("#board").val(),
        "Length": $("#length").val()
    },
    function (words) {
        var output = "No solutions exists!";

        if (words.length > 0) {
            output = "<h2>" + words.length + " Solutions!</h2><ul>";

            var score = 0;

            $.each(words, function (index, word) {
                score += word.Score;
                output += "<li>" + word.Word + " (" + word.Score + " points)</li>";
            });

            output += "</ul><p>Total score = " + score + " points!</p>";
        }

        $("#solutions").html(output);
    }
);

Note that the above script calls the Solver.svc service passing in the BoardID and Length querystring parameters. The textbox where the user enters the board has an id of board while the minimum letter drop-down list has an id of length. The function defined in the call is what is executed when the result comes back successfully. Here, the jQuery each function is used to enumerate the array and build up a string of HTML in a variable named output that produces a bulleted list of solutions. The total number of solutions and total number of points is also included in output. Finally, the contents of output are dumped to a <div> on the page with an id of solutions.

Here’s the page after clicking the “Find All Words!” button. Nothing fancy, of course, and not nearly as useful or eye-pleasing as the website’s results page, but it does illustrate one way you can use the Boggle Solver.svc service.

BoggleBoard3

Download the Code!

You can download the complete Boggle solver engine, web application, and WCF RESTful service from http://aspnet.4guysfromrolla.com/code/BoggleSolver.zip.

Happy Programming, and Happy Boggling!

Enhancing the Store Locator ASP.NET MVC Application to Include Directions
27 August 10 10:44 PM | Scott Mitchell | 2 comment(s)

The info popup includes a Directions link.Earlier this year I wrote an article on 4GuysFromRolla.com showing how to build an ASP.NET WebForms store locator application using Google Maps. Recently, I ported the store locator application to ASP.NET MVC and detailed building the application in Implementing the Store Locator Application Using ASP.NET MVC.

Recently, a reader wrote in and asked what steps would be necessary to include a “Directions” link with each marker in the map so that, when clicked, the user would see the driving directions from the address they entered and the store of interest. I decided to update the ASP.NET MVC application to include this new feature request. Now, the results page shows a “Directions” link in both the grid of nearby stores and in the info window that pops up when you click a map marker. Clicking the “Directions” link opens a new browser window and loads Google Maps, showing the directions from the user-entered address to the selected store’s address.

To show the driving directions I send the user to the following URL: http://maps.google.com/maps?f=d&source=s_d&saddr=startingAddress&daddr=destinationAddress.

When the user is sent to the store locator page, the user-entered address (a/k/a, the starting address) is passed through the querystring via a field named Address, so we already know the starting address. But how do we get our hands on the destination address? Recall that view is passed a model that is a collection of NearbyStoreLocation objects; the NearbyStoreLocation class has properties like Address (the street address), City, Region, PostalCode, and so forth. We can build up the address by concatenating these various address parts.

Rather than requiring the view to build up the address, I added a new read-only property to the NearbyStoreLocation class named FormattedAddress, which returns an address Google Maps can parse by piecing together the address-related properties into a string.

public string FormattedAddress
{
    get
    {
        var addrPieces = new List<string>(5);
        if (!string.IsNullOrEmpty(this.Address))
            addrPieces.Add(this.Address);
        if (!string.IsNullOrEmpty(this.City))
            addrPieces.Add(this.City);
        if (!string.IsNullOrEmpty(this.Region))
            addrPieces.Add(this.Region);
        if (!string.IsNullOrEmpty(this.CountryCode))
            addrPieces.Add(this.CountryCode);
        if (!string.IsNullOrEmpty(this.PostalCode))
            addrPieces.Add(this.PostalCode);

        return string.Join(", ", addrPieces.ToArray());
    }
}

In the view, the link to the directions can be build like so:

<a target="_blank" href="http://maps.google.com/maps?f=d&source=s_d&saddr=<%=Server.UrlEncode(Request.QueryString["Address"]) %>&daddr=<%=Server.UrlEncode(store.FormattedAddress) %>">Directions</a>

And that’s it! Adding the Directions link to the info popup window is a tad more involved because the quotation marks must be escaped using \”.

To get your hands on the complete, working code, go to: http://www.4guysfromrolla.com/code/GoogleMapsMVC.zip

Happy Programming!

More Posts Next page »

Archives

My Books

  • Teach Yourself ASP.NET 4 in 24 Hours
  • Teach Yourself ASP.NET 3.5 in 24 Hours
  • Teach Yourself ASP.NET 2.0 in 24 Hours
  • ASP.NET Data Web Controls Kick Start
  • ASP.NET: Tips, Tutorials, and Code
  • Designing Active Server Pages
  • Teach Yourself Active Server Pages 3.0 in 21 Days

I am a Microsoft MVP for ASP.NET.

I am an ASPInsider.