A comment was recently left on a previous post of mine asking how to go about adding a button to a wizard and handling the click. I thought I'd answer the question with another post as there is usually more you'd like to do than just adding a button (we certainly do!!!!)

Adding a button to the Wizard and handling the onclick event

Adding a button to the ASP.NET wizard is pretty simple and pretty easy to manage. You just have to define the StartNavigationTemplate, StepNavigationTemplate and FinishNavigationTemplate. These are what define the rendering of the buttons at the bottom of the wizard.

<asp:Wizard ID="wzd" runat="Server" Width="100%" DisplaySideBar="false">
     <WizardSteps>
         <asp:WizardStep ID="step1" runat="server" StepType="Start">
             ... omitted ...
         </asp:WizardStep>
         <asp:WizardStep ID="step2" runat="server" StepType="Step">
             ... omitted ...
         </asp:WizardStep>
         <asp:WizardStep ID="step3" runat="server" StepType="Finish">
             ... omitted ...
         </asp:WizardStep>
     </WizardSteps>
     <StartNavigationTemplate>
         <table cellpadding="3" cellspacing="3">
             <tr>
                 <td>
                     <asp:Button ID="btnCancel" runat="server" Text="Cancel"
                         CausesValidation="false" 
                         OnClientClick="return confirm('Are you sure you want to cancel');" 
                         OnClick="btnCancel_Click" 
                      />
                 </td>
                 <td>
                     <asp:Button ID="btnNext" runat="server" Text="Next >>"
                         CausesValidation="true" 
                         CommandName="MoveNext" 
                      />
                 </td>
             </tr>
         </table>
     </StartNavigationTemplate>
     <StepNavigationTemplate>
         <table cellpadding="3" cellspacing="3">
             <tr>
                 <td>
                     <asp:Button ID="btnCancel" runat="server" Text="Cancel"
                         CausesValidation="false" 
                         OnClientClick="return confirm('Are you sure you want to cancel');" 
                         OnClick="btnCancel_Click" 
                      />
                 </td>
                 <td>
                     <asp:Button ID="btnPrevious" runat="server" Text="<< Previous"
                         CausesValidation="false" 
                         CommandName="MovePrevious" 
                      />
                      <asp:Button ID="btnNext" runat="server" Text="Next >>"
                         CausesValidation="true" 
                         CommandName="MoveNext" 
                      />
                 </td>
             </tr>
         </table>
     </StepNavigationTemplate>
     <FinishNavigationTemplate>
         <table cellpadding="3" cellspacing="3">
             <tr>
                 <td>
                     <asp:Button ID="btnCancel" runat="server" Text="Cancel"
                         CausesValidation="false" 
                         OnClientClick="return confirm('Are you sure you want to cancel');" 
                         OnClick="btnCancel_Click" 
                      />
                 </td>
                 <td>
                      <asp:Button ID="btnFinish" runat="server" Text="Finish"
                         CausesValidation="true" 
                         CommandName="MoveComplete" 
                      />
                 </td>
             </tr>
         </table>
     </FinishNavigationTemplate>
 </asp:Wizard>
In the ASP.NET code above, I've added 3 steps to my wizard, each with a different StepType. The StepType is what defines which NavigationTemplate the ASP.NET wizard uses.
In the example above:
  • Step 1 will use the "StartNavigationTemplate"
  • Step 2 will use the "StepNavigationTemplate"
  • Step 3 will use the "FinishNavigationTemplate"

The trick with making your own NavigationTemplates is to make sure you use the "CommandNames" that the ASP.NET wizard requires. You can see on the "Next", "Previous" and "Complete" buttons that there are no "OnClick" handlers, rather we have used "MoveNext", "MovePrevious" and "MoveComplete" as the CommandNames on the buttons. These CommandNames are what the wizard uses to fire the appropriate event.

In all the cases above, I've added a button that allows the user to "Cancel". When the user clicks on "Cancel", it fires a quick javascript confirm. If the user presses "OK", then the normal code-behind button event handler is raised.

image

protected void btnCancel_Click(object sender, EventArgs e)
{
    // Do something with the click ...
    Response.Redirect("home.aspx");
}

Finding a button in a NavigationTemplate

Sometimes we want (or need) to hide a button on one of the wizard steps for whatever reason.
For example, if you are using a wizard for an e-commerce checkout, you might disable the Next button on the first step if there are no items in your shopping basket.

This code below is from someone else, so I can't take credit for it.... I'm not sure where we got it from, but it works.

public enum WizardNavigationTempContainer
{
    StartNavigationTemplateContainerID = 1,
    StepNavigationTemplateContainerID = 2,
    FinishNavigationTemplateContainerID = 3
}

private Control GetControlFromWizard(Wizard wizard, WizardNavigationTempContainer wzdTemplate, string controlName)
{
    System.Text.StringBuilder strCtrl = new System.Text.StringBuilder();
    strCtrl.Append(wzdTemplate);
    strCtrl.Append("$");
    strCtrl.Append(controlName);

    return wizard.FindControl(strCtrl.ToString());
}

The ASP.NET wizard does some weird stuff with naming, and you can't use the standard FindControl() to find the control. If you're using .NET 3.5, you could probably implement the above "GetControlFromWizard" as an Extension Method.

Hiding the button

Now that we know how to find the button in the wizard step, we can hide/disable... do anything we want to the button.

protected void Page_Load(object sender, EventArgs e)
{
    if (Page.User.Identity.IsAuthenticated == true)
    {
        Button btnNext = GetControlFromWizard(wzd, WizardNavigationTempContainer.StartNavigationTemplateContainerID, "btnNext") as Button;
        btnNext.Enabled = false;
    }
}

In the code above, I'm checking if a user is Authenticated.. and if they are, then I hide the button. As I said before, you could implement what condition you wanted to do here.

Putting it all together

Using the techniques I've used here, and the techniques described in my previous post, you can actually make some pretty nice looking wizards using the ASP.NET Wizard control. We use the methods I've shown here, and the methods in my previous post together and we've produced some pretty user-friendly wizards.

Recently I was asked by a friend of mine to screen scrape a website. What he wanted was the results of a form submission. There were a number of fields on the form (mostly dropdowns), and he wanted me to run every possible permutation of these dropdown lists. All up, this resulted in just over 8,000 pieces of data.

It ended up being REALLY easy (a bit easier than I thought). So I thought I'd share how I went about doing it. Most of these methods were gleaned from other peoples posts, then tailored to my specific needs.

My approach was to tackle this in 2 separate parts:

  1. Make the requests to the remote server, and save all the possible permutations to disk
  2. Load up and then scrape the files to extract the data I needed.

Where to Start

I start by firing up Fiddler and make a request to the server just using the browser.

From this I can get the following information:

  • The URL I need to send the request to
  • All the form fields I need to send with the request

image

In my case, the server wasn't an ASP.NET server, so I didn't have to worry about VIEWSTATE at all.

Making the request through code

This is one place where I was surprised how EASY it was to make the request. The last time I had to do this was back in the .NET 1.1 days, and it was a little bit harder.

private void SendRequest()
{
    try
    {
        WebClient webClient = new WebClient();

        // Create a new NameValueCollection instance to hold some custom parameters to be posted to the URL.
        NameValueCollection vars = new NameValueCollection();

        // Add necessary parameter/value pairs to the name/value container.
        vars.Add("ddlGender", "male");
        vars.Add("ddlAge", ">30");

        // Upload the NameValueCollection.
        byte[] responseArray = webClient.UploadValues(URL, "POST", vars);

        // Save the response.
        string fileName = "webRequest.html";

        System.IO.File.WriteAllBytes(System.IO.Path.Combine(filePath, fileName), responseArray);

    }
    catch (Exception ee)
    {
        // Log error (omitted for brevity)
    }

}

In my case above, the variables "URL" and "filePath" are set in the constructor of the class.

You will note in the example above that in my case I am using POST to get the data from the server. This can easily be changed to GET (or whatever HTTP method you need).

I save all the response data to a file (which I then parse to get the data I want)

Parsing the HTML files

To parse the files also ended up being extremely easy for me. In the past I've tried to use regular expressions to extract the data from files, but plain and simple... I DO NOT understand regular expressions. I've tried, and where I need to I can get them to work, but my poor little brain doesn't have enough room to remember regular expressions.

This time I used the HTML Agility Pack available through CodePlex. It rocks... Coming from a .NET world, I can understand and use this easily, and its pretty fast as well. For those who haven't heard of the HTML Agility Pack here is an excerpt from their CodePlex homepage.

This is an agile HTML parser that builds a read/write DOM and supports plain XPATH or XSLT (you actually don't HAVE to understand XPATH nor XSLT to use it, don't worry...). It is a .NET code library that allows you to parse "out of the web" HTML files. The parser is very tolerant with "real world" malformed HTML. The object model is very similar to what proposes System.Xml, but for HTML documents (or streams).

private void ParseFile(string fileName)
{
    if (!System.IO.File.Exists(System.IO.Path.Combine(filePath, fileName)))
    {
        return;
    }

    HtmlDocument doc = new HtmlDocument();

    doc.Load(System.IO.Path.Combine(filePath, fileName));

    HtmlNode greenBlockContainer = doc.DocumentNode.SelectSingleNode("//div[@class=\"green-block-container\"]");

    HtmlNodeCollection greenBlocks = greenBlockContainer.SelectNodes("//div[@class=\"green-block\"]");

    string s1 = greenBlocks[0].SelectNodes("//div[@class=\"result-block-left\"]")[0].InnerText;
    string s2 = greenBlocks[0].SelectNodes("//div[@class=\"result-block-right\"]")[0].InnerText;
    // etc...
    
    // Do something with the data... omitted.
    
}

Here is what the code above does:

  1. Checks whether the file exists... if not returns
  2. Creates a HtmlDocument (from the HtmlAgilityPack)
  3. Load the html file (fileName) into the HtmlDocument
  4. Extract the nodes we are looking for. Here we can use XPath type queries to get elements
  5. Use the "InnerText" property to get the value of the node and assign it to a string (I then used this string to populate a DataTable)

The default style of the ASP.NET Wizard control is not the best. For the sidebar to work, and display all the wizard steps in a wizard requires quite a bit of space. And in all the wizards we end up creating, we don't want the user to jump from one step to the next using the sidebar. We make the user click from step to step linearly.

We've spent a little time lately changing the way the wizard looks and have come up with the following look and feel.

wizardDetails

The top HeaderTemplate contains the current step the user is on (derived from the WizardStep Title) and an indication of the total number of step (we use three different classes in indicate the current step, completed step and incomplete step). The "steps" also have tooltips added to let the user know what steps are coming. (See image below)

 Tooltip when Mouse hovers over the step number

The WizardStep in the image above is all the information regarding "Billing and Shipping" details (this is just standard Wizard Step stuff)

How its done

The markup for the wizard aspx code is as follows:

<asp:Wizard ID="wzd" runat="Server" Width="100%" DisplaySideBar="false">
    <HeaderTemplate>
        <table style="width: 100%" cellpadding="0" cellspacing="0">
            <tr>
                <td class="wizardTitle">
                    <%= wzd.ActiveStep.Title%>
                </td>
                <td>
                    <table style="width: 100%; border-collapse: collapse;">
                        <tr>
                            <td style="text-align: right">
                                <span class="wizardProgress">Steps:</span>
                            </td>
                            <asp:Repeater ID="SideBarList" runat="server">
                                <ItemTemplate>
                                    <td class="stepBreak">&nbsp;</td>
                                    <td class="<%# GetClassForWizardStep(Container.DataItem) %>" title="<%# DataBinder.Eval(Container, "DataItem.Name")%>">
                                        <%# wzd.WizardSteps.IndexOf(Container.DataItem as WizardStep) + 1 %>
                                    </td>
                                </ItemTemplate>
                            </asp:Repeater>
                        </tr>
                    </table>
                </td>
            </tr>
        </table>
    </HeaderTemplate>
    <SideBarTemplate>
    </SideBarTemplate>
    <WizardSteps> ... removed... </WizardSteps>
</asp:Wizard>

We add the following event handler to Page_Load in our code-behind

protected void Page_Load(object sender, EventArgs e)
{
    wzd.PreRender += new EventHandler(wzd_PreRender);
}

And have the following methods in the code-behind

protected void wzd_PreRender(object sender, EventArgs e)
{
    Repeater SideBarList = wzd.FindControl("HeaderContainer").FindControl("SideBarList") as Repeater;

    SideBarList.DataSource = wzd.WizardSteps;
    SideBarList.DataBind();

}

public string GetClassForWizardStep(object wizardStep)
{
    WizardStep step = wizardStep as WizardStep;

    if (step == null)
    {
        return "";
    }

    int stepIndex = wzd.WizardSteps.IndexOf(step);

    if (stepIndex < wzd.ActiveStepIndex)
    {
        return "stepCompleted";
    }
    else if (stepIndex > wzd.ActiveStepIndex)
    {
        return "stepNotCompleted";
    }
    else
    {
        return "stepCurrent";
    }
}

Explanation of the code above

When the wizard goes into PreRender, we bind the collection of WizardSteps to the Repeater in our HeaderTemplate

The "GetClassForWizardStep" is a helper method we have to determine what wizard step we're on, and render the appropriate class in the table cell.

Style Sheet Rules

The rules I'm using above to generate the page are as below...

/* WIZARD */
.stepNotCompleted
{
    background-color: rgb(153,153,153);
    width: 15px;
    border: 1px solid rgb(153,153,153);
    margin-right: 5px;
    color: White;
    font-family: Arial;
    font-size: 12px;
    text-align: center;
}

.stepCompleted
{
    background-color: #4d4d4d;
    width: 15px;
    border: 1px solid #4d4d4d;
    color: White;
    font-family: Arial;
    font-size: 12px;
    text-align: center;
}

.stepCurrent
{
    background-color: #e01122;
    width: 15px;
    border: 1px solid #e01122;
    color: White;
    font-family: Arial;
    font-size: 12px;
    font-weight: bold;
    text-align: center;
}

.stepBreak
{
    width: 3px;
    background-color: Transparent;
}

.wizardProgress
{
    padding-right: 10px;
    font-family: Arial;
    color: #333333;
    font-size: 12px;

}

.wizardTitle
{
    font-family: Arial;
    font-size: 120%;
    font-weight: bold;
    color: #333333;
    vertical-align: middle;

We use DotNetNuke as a base for most of our applications. We are primarily in the space of content management, mixed with custom code. DNN handles content management pretty well for all our situations. The ability for us to create modules in DNN to do whatever we want is pretty awesome.. And after you get used to some quirks (and jumping through some pretty funky hoops, its actually pretty easy to use).

One thing we came across recently was doing some zebra striping with jQuery, and using a table-less layout skin (DIV's only). When you hovered over a table row, the whole page would jump up about 6-ish pixels. I'd noticed it, and hoped no-one else would, but alas it was discovered, and left up to me to fix.

I won't go into the complete set of styles and the full skin file, but I'll post enough to give a few hints.

    <!-- B. MAIN -->
    <div class="main">
        <!-- B.1 MAIN CONTENT -->
        <div class="main-content">
            <!-- Content unit - One column -->
            <div id="topPane" runat="server" class="column1-unit" visible="false">
                <hr class="clear-contentunit" />
            </div>
            <div id="contentPane" runat="server" class="column1-unit" visible="false">
                <hr class="clear-contentunit" />
            </div>
            <!-- Content unit - Two columns -->
            <div id="leftPane" runat="server" class="column2-unit-left" visible="true" />
            <div id="rightPane" runat="server" class="column2-unit-right" visible="false" />
            <hr class="clear-contentunit" />
            <div id="bottomPane" runat="server" class="column1-unit" visible="false">
                <hr class="clear-contentunit" />
            </div>
        </div>
        <hr class="clear-contentunit" />
        <!-- C. FOOTER AREA -->
        <div class="footer">
            <span class="credits">
                <dnn:TERMS ID="TERMS" runat="server" Text="Terms & Conditions" />
                |
                <dnn:PRIVACY ID="PRIVACY" runat="server" Text="Privacy Policy" />
            </span>
&nbsp;&nbsp;&nbsp;
<span>Copyright &copy; Ellington Management &amps; Information Services, <%=DateTime.Now.ToString("yyyy")%>. All rights reserved.
</span> </div> </div>

The key here is make sure you add "visible=false" to all of your DIVs. When DotNetNuke adds a module to a pane, it also sets visible=true on the DIV. This way we make sure we don't get any empty DIV's rendered on the page. I don't 100% know why the empty div collapsed when you hover over a row with jQuery, but this solved the problem.

In the skin above, the only DIV element I have "visible=true" on, is the leftPane. I want this DIV to render, regardless of whether there is a module added to it or not. The repercussion we get if its not "visible=true" is that the DIV doesn't render, and the rightPane ends up rendering on the left hand side of the page.

Make sure you ALWAYS have a contentPane (Mental note to self)

Make sure you ALWAYS have a pane in your skin called "contentPane". I learnt this the hard way (very hard). We designed a skin for a customer and they effectively wanted 4 panels (top-left, top-right, bottom-left and bottom-right)... In all my wisdom I added panes with these names, and for the most part everything worked.... The big thing that didn't work was users logging out.

The reason is this: when you click the logout link, it redirects to a URL with ctl=logoff. When DotNetNuke sees Request.QueryString["ctl"], it loads this module and inserts it into the ContentPane... which in my case didn't exist. DNN does some error checking and if it can't find "ContentPane" it doesn't add the control. Kudos to DNN for good error checking. Bad form on my part for not realising that a "contentPane" is needed.

Users get impatient when something is taking a long time to run server-side. This usually results in having a form submitted more than once. Ok, maybe its not impatience, maybe its that they didn't think they clicked the button.... I don't know why.... I just have to fix the problem of duplicate records in the database.

We use a pretty simple method of tracking whether a user has submitted a form more than one. This is usually used in conjunction with other methods, such as BusyBoxes or javascript click prevention. Ultimately, this method is our absolute failsafe to ensure we only submit the form once.

This has been used and tested (and approved) by a number of different payment gateways and banks we use in Australia.

How do we do it

All our pages inherit from a common base page (inherited from System.Web.UI.Page). We override the OnInit method and add a hidden field which is a Guid. We only set this field when the page first loads.


protected override void
OnInit(EventArgs e) { Literal lit = new Literal(); lit.ID = "__PAGEGUID"; lit.Visible = false; this.Controls.Add(lit); if (!IsPostBack) { lit.Text = Guid.NewGuid().ToString(); } base.OnInit(e); } public Guid GetPageGuid() { Literal lit = this.FindControl("__PAGEGUID") as Literal; if (lit == null) { throw new System.Exception("Could not find __PAGEGUID control"); } else { Guid g = new Guid(lit.Text); return g; } }

This gives us a unique reference for every page we use. When its comes time to checking that a page is unique we do the following.

NOTE: In the scenario below, we're tracking that users don't click the "Order" button more than once when we're firing an order through to Microsoft Dynamics NAV.

/*
 * Ensure we only submit the order once.
 */
try
{
    OrderHelper.EnsureOrderIsUnique(this.GetPageGuid());
}
catch (DuplicateOrderException dupExc)
{
// Do something with the error. (Code omitted)
}

The method that does the "EnsureOrderIsUnique" is described below. I've used a static List to store the page Guids in this case. When we take credit card payments, we store the Guid in the database along with the payment information. Its up to you how to store the Guid... it just needs to be accessible by all processes.


private static
List<Guid> _submittedOrders = new List<Guid>(); /// <summary> /// Keeps track of all the Order Guid we've placed (similiar to Payment Gateway). /// If an order is not unique, then a DuplicateOrderException is thrown. /// </summary> /// <param name="orderGuid"></param> public static void EnsureOrderIsUnique(Guid orderGuid) { lock (((ICollection)_submittedOrders).SyncRoot) { if (_submittedOrders.Contains(orderGuid)) { throw new DuplicateOrderException("Order has already been placed"); } _submittedOrders.Add(orderGuid); } }

Thats it.... pretty simple really. And it works under a number of different scenarios:

  • people double-clicking on a button at the end of a wizard
  • people finishing a wizard, pressing back on the browser, then pressing finish again
  • people opening up new browser windows and submitting both..

 

I've mentioned in a previous post that we use Aspose.Words combined with Aspose.Pdf to create PDF documents/reports in all our applications.

To do this we use a method within the Aspose.Words.Reporting.MailMerge class called ExecuteWithRegions using the overload that passes in a DataTable. Within Aspose.Words this effectively loops through the fields in DataTable and creates what you'd normally refer to as a sub-report.

Within our applications we use classes to represent our business objects. As we produce quite a few reports that rely on a number of different objects, we've created a helper method that turns our business objects and/or a List of business objects into DataTables.

We came into a problem recently when one of our fields was defined as "int?" (same as Nullable<int>). The problem we had was trying to add a column on type "int?" to a DataTable. Make sense when you think about it... databases have had the concept of nullable fields for as long as I've been creating databases.

Anyway... rather than add a field of type "int?" to the DataTable, I needed to add a field of type "int" as a column. Sounded simple... sort of... A fair bit of Googling and pulling together ideas from a couple of different blog posts led me to the following code.

The code below takes a List of objects (simple... this doesn't handle complex types) and then returns a DataTable which is a representation of the object.

/// <summary>
 /// Converts a Generic List into a DataTable
 /// </summary>
 /// <param name="list"></param>
 /// <param name="typ"></param>
 /// <returns></returns>
 private DataTable GetDataTable(IList list, Type typ)
 {
     DataTable dt = new DataTable();

     // Get a list of all the properties on the object
     PropertyInfo[] pi = typ.GetProperties();

     // Loop through each property, and add it as a column to the datatable
     foreach (PropertyInfo p in pi)
     {
         // The the type of the property
         Type columnType = p.PropertyType;

         // We need to check whether the property is NULLABLE
         if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
         {
             // If it is NULLABLE, then get the underlying type. eg if "Nullable<int>" then this will return just "int"
             columnType = p.PropertyType.GetGenericArguments()[0];
         }

         // Add the column definition to the datatable.
         dt.Columns.Add(new DataColumn(p.Name, columnType));
     }

     // For each object in the list, loop through and add the data to the datatable.
     foreach (object obj in list)
     {
         object[] row = new object[pi.Length];
         int i = 0;

         foreach (PropertyInfo p in pi)
         {
             row[i++] = p.GetValue(obj, null);
         }

         dt.Rows.Add(row);
     }

     return dt;
 }

The key points from the code above are:

  • using PropertyType.IsGenericType to determine whether the property is a generic type
  • using ProprtyType.GetGenericTypeDefinition() == typeof(Nullable<>) to test whether its a nullable type
  • getting the underlying type using PropertyType.GetGenericArguments() to get the base type.

To use the code above, you can do the following. The example below is a fairly contrived example, but it should highlight what I'm trying to do below.

public class Person
{
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public DateTime? DateOfDeath { get; set; }
}

public class Example
{
    public static DataTable RunExample()
    {
        Person edward = new Person() { Name = "Edward", DateOfBirth = new DateTime(1900, 1, 1), DateOfDeath = new DateTime(1990, 10, 15) };
        Person margaret = new Person() { Name = "Margaret", DateOfBirth = new DateTime(1950, 2, 9), DateOfDeath = null };
        Person grant = new Person() { Name = "Grant", DateOfBirth = new DateTime(1975, 6, 13), DateOfDeath = null };

        List<Person> people = new List<Person>();

        people.Add(edward);
        people.Add(margaret);
        people.Add(grant);

        DataTable dt = GetDataTable(people, typeof(Person));

        return dt;
    }
}

And this will return a DataTable that looks like the following (I'm an Aussie, so the date format is dd/MM/yyyy):

Name (string) DateOfBirth (DateTime) DateOfDeath (DateTime)
Edward 1/1/1900 15/10/1990
Margaret 9/2/1950 [NULL]
Grant 13/6/1975 [NULL]

NOTE: as a general rule we try to NOT use nullable fields in the database, as nullable fields do some REALLY weird things to queries. Pretty much the only field types we use as nulls are DateTime fields. In my opinion, nullable fields get used far too often within databases and there is usually an alternative.

Whenever anyone anywhere mentions Reflection on a blog post, straight away there is a reply saying "you shouldn't use Reflection as its slow". I thought I'd take a look at how "slow" reflection really is in the real world.

We use reflection in a couple of instances in our applications. One situation where we use Reflection is in a previous post about getting a friendly name from an enumeration. Another situation is where we convert a List of business objects into a DataTable (we need to do this produce PDF documents in our application using ASPOSE.Words - I'll post about this later). Another common situation you'll see is object hydration.

Getting a friendly name from an enumeration

There are a couple of other options I've seen/used in the past to get friendly names from Enums.

These consist of:

  1. using Reflection to get the value of an attribute on the enum (code from my previous post)
  2. storing the enum in a table in the database and looking it up at runtime
  3. storing the enum in some other way (e.g. XML) and looking it up at runtime
  4. retrieving the friendly name from a resources file at runtime
  5. using a switch/case statement to get the values of the enum

I'm going to test all of the above options and see how we go for speed.

The Results

I'm sure some of you won't want to read through how I did it, and what methods I used... so for those people here are the results.

Startup Time

Method Time Taken
(ticks*)
Time Taken
(ms)
Reflection 36,639 3.66
Database Warm-up 637,757 63.78
Database (Stored Procedure) 9,672 0.97
Switch / Case Statement 2,744 0.27
Resources 932,752 93.28
Xml Warm-up 14,712 1.47
Xml 8,225 0.82

* - A tick is 100 nanoseconds.

The above data represents a average of 5 runs, only getting the value of the enum once. This represents the cold-start time for each different method (basically I'm trying to exclude any form of caching). What we can see from the above results, is that by far, a SWITCH / CASE statement is the fastest method we have available. I've split out Database warm-up and database stored proc runtime into 2 separate runs (likewise with XML). I'm a database guy, and everyone knows that establishing a connection to the database is pretty costly. What was a surprise to me, is that the longest method was getting the value from a resources file.

But looking at the numbers above....and putting it into a bit of context, the longest method above took 93ms.... In terms of speed, this is still pretty fast, and certainly within tolerable levels for a Web UI.

100 Iterations

I've also run the results for 100 iterations. Why 100? Maybe I'm rendering a list of data, and this is one of the columns I want to display. I know 100 is a fairly big list to render, but lets just pretend....

Method Time Taken
(ticks*)
Time Taken
(ms)
Switch / Case Statement 74 0.0074
Resources 37,809 3.7809
Reflection with cache 67,015 6.7015

Again, we see that using a switch/case statement outperforms everything else MASSIVELY. I'm actually pretty surprised how fast it is. For 100 iterations of resources and reflection, we get response times of 3.7ms and 6.7ms respectively. Again, putting this in context, we're not talking about a process that's going to put the brakes on any website I develop.

What is interesting, is that using Resources is initially slow to startup, but then runs pretty past afterwards.

How I got the Values of the enum

I'll post the code I used to get the values of the enumerations.

EnumHelper - Reflection

This is the same method I used in a previous blog post. Its posted below as well in the Reflection with Cache code.

Database

This is pretty simple stuff. Basically open the connection to the database and run a stored procedure.

public static string GetDescription(Enum en)
       {
           using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SqlCommon"].ConnectionString))
           {
               SqlCommand cmd = new SqlCommand();

               conn.Open();
               cmd.Connection = conn;

               cmd.CommandType = CommandType.StoredProcedure;
               cmd.CommandText = "Enum_GetDescription";

               cmd.Parameters.Add("enumName", SqlDbType.VarChar).Value = en.ToString();

               SqlDataReader dr = cmd.ExecuteReader();

               if (dr.Read())
               {
                   return Convert.ToString(dr["Description"]);
               }
           }

           return en.ToString();
       }

Switch / Case statement

Basically pass in the enum, and return a string. The only drawback I can think of about this one, is that you'd need to write one of these for each enumeration you use in your application.

public static string GetDescription(UserColours en)
{
    switch (en)
    {
        case UserColours.AliceBlue: return "Alice Blue";
        case UserColours.BrightPink: return "Bright Pink";
        case UserColours.BurntOrange: return "Burnt Orange";
        case UserColours.DarkGreen: return "Dark Green";
        case UserColours.SkyBlue: return "Sky Blue";
        default: return en.ToString();
    }
}

Resources

Again, just read the value from a resources file, using the enumeration ToString() as the Name 

public static string GetDescription(Enum en)
{
    string s = Resources.UserColoursResource.ResourceManager.GetString(en.ToString());

    if (!string.IsNullOrEmpty(s))
        return s;

    return en.ToString();

}

image

Xml File

Load up the Xml file, and use XPath to find the friendly name of the enum

public static string GetDescription(Enum en)
{
    XmlDocument xmlDoc = new XmlDocument();

    xmlDoc.Load(HttpContext.Current.Server.MapPath("Enums.xml"));

    XmlNode node = xmlDoc.SelectSingleNode(String.Format("enums/enum[@value = \"{0}\"]", en.ToString()));

    if (node != null)
    {
        return node.Attributes["description"].Value;
    }

    return en.ToString();
}

 

<?xml version="1.0" encoding="utf-8" ?>
<enums>
    <enum value="BurntOrange" description="Burnt Orange" ></enum>
    <enum value="BrightPink" description="Bright Pink" ></enum>
    <enum value="DarkGreen" description="Dark Green" ></enum>
    <enum value="SkyBlue" description="Sky Blue" ></enum>
    <enum value="AliceBlue" description="Alice Blue" ></enum>
</enums>

Resources with caching

This is effectively the same as my standard EnumHelper class library, except I've added a caching using the HttpContext Cache.

public static class RelfectionEnumHelper
{
    private static object padLock = new object();

    public static string GetDescription(Enum en)
    {
        Type type = en.GetType();

        MemberInfo[] memInfo = type.GetMember(en.ToString());

        if (memInfo != null && memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

            if (attrs != null && attrs.Length > 0)
            {
                return ((DescriptionAttribute)attrs[0]).Description;
            }
        }

        return en.ToString();
    }


    public static string GetDescriptionUsingCache(Enum en)
    {
        System.Web.Caching.Cache cache = System.Web.HttpContext.Current.Cache;

        if (cache[en.ToString()] == null)
        {
            lock (padLock)
            {
                if (cache[en.ToString()] == null)
                {
                    cache.Insert(en.ToString(), GetDescription(en));
                }
            }
        }

        return cache[en.ToString()].ToString();
    }
}

 

Conclusion

Personally, I'm going to stick with using my EnumHelper library with caching. I'm really not that fussed about shaving 3ms of a page hit...

The reasons I'll stick with this library are:

  • its nice and simple. The enum and its friendly name are located in the one spot. Developers are lazy.... if they have to maintain things in 2 or 3 different locations... plain and simple they won't!
  • its fast enough for me. None of my clients ever ask us to make it go faster. (There are usually other way we can make things run faster anyway)

Wherever possible we use enumerations in our applications. Often the name of the enumeration isn't what you want to print on the screen. It could be because the enumeration is 2 words that have been joined together, or you want the description to start with a number (not that we do this... but you could).

Example

You might have a enumeration for Colours that a user is allowed to select from. The values available for this enumeration may be (hypothetically):

  • Burnt Orange
  • Bright Pink
  • Dark Green
  • Sky Blue

To define this in code, you'd use an enumeration like this:

public enum UserColours
{
    BurntOrange = 1,
    BrightPink = 2,
    DarkGreen = 3,
    SkyBlue = 4
}

The Problem

Normally, in an ASP.NET application we'd render an enumeration as a Drop Down List. Problem is we don't want to show "BurntOrange" as a value in the drop down list, we want to show a nice friendly option like "Burnt Orange".

The Solution

We've created a static helper class that uses reflection to get a Description for each member of the enumeration. I've also got this as an extension method for ASP.NET 3.5. This post is for the helper class, but it can very easily be converted to an extension method for ToString().

To get a friendly name, we decorate each member of the enumeration with the DescriptionAttribute from the namespace System.ComponentModel. Our enums end up looking like this:

using System.ComponentModel;

namespace Ellington.EnumHelperExamples
{
    public enum UserColours
    {
        [Description("Burnt Orange")]
        BurntOrange = 1,

        [Description("Bright Pink")]
        BrightPink = 2,

        [Description("Dark Green")]
        DarkGreen = 3,

        [Description("Sky Blue")]
        SkyBlue = 4
    }
}

When we need to then retrieve the friendly name from the enum, we have the following helper class:

using System;
using System.ComponentModel;
using System.Reflection;

namespace Ellington.EnumHelperExamples 
{
    public static class EnumHelper
    {
        /// <summary>
        /// Retrieve the description on the enum, e.g.
        /// [Description("Bright Pink")]
        /// BrightPink = 2,
        /// Then when you pass in the enum, it will retrieve the description
        /// </summary>
        /// <param name="en">The Enumeration</param>
        /// <returns>A string representing the friendly name</returns>
        public static string GetDescription(Enum en)
        {
            Type type = en.GetType();

            MemberInfo[] memInfo = type.GetMember(en.ToString());

            if (memInfo != null && memInfo.Length > 0)
            {
                object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
{ return ((DescriptionAttribute)attrs[0]).Description;
} } return en.ToString(); }

}

}

Now, when we want to get the friendly name from the enum, all we do is call:


EnumHelper.GetDescription(UserColours.BrightPink);

The code above, basically takes in the enumeration, uses Reflection to find the "DescriptionAttribute" on the member, and then returns the Description. If the DescriptionAttribute is not present on the enum, then we just call a ToString() on the enum to get the name.

As we're not hitting anything that is scary from a security point of view, the whole thing works in a partial trust environment.

Introduction

I compiled a list of the libraries we use day-to-day in my company. There are a couple of home-grown libraries, but most are commercially available, or open source.

"Yeah great... another person telling us which libraries they use...". I hear you all say... The reason I've done this is because nearly all our sites operate under a partial trust environment. To get some of these libraries working under partial trust I've done the following:

  • I've recompiled them myself
  • I've had to provide detailed instructions on how to replicate issues
  • I've been told "BAD LUCK", we're not going to bother getting our dll working, so find a new one. (This was my personal favourite)

All this and the only error you usually get is the dreaded "SecurityException" and an obfuscated stack trace.

So, here they are... the libraries I use with great success.

Libraries I Use

BusyBoxDotNet

This library is used to display a modal popup when a long-running process is occurring.

 image

We use it for a couple of reasons:

  1. to let the user know they really did press that "FINISH" button 
  2. to stop the user from pressing that "FINISH" button again

This library relies on the following:

  • ICSharpCode.SharpZipLib.dll

To get this to work under partial trust, we needed to recompile both dll's.

BusyBoxDotNet website

Aspose.Words, Aspose.Pdf

These 2 libraries combined make up an AWESOME PDF generator. The ability to have one of our clients create their own report in Word using mail merge fields, and for us to deploy just a .doc file to a server is FANTASTIC.

It took a little while for us to get this working under a partial trust environment. A couple of issues were logged with ASPOSE to rectify some things, but it all worked out in the end. This library is now working extremely well. Getting it working fast (less than 0.5 seconds per document) with Unicode characters and using all fonts (not just the default PDF fonts of Times and Courier) was an interesting exercise. I'll try to publish an article in the near future about how we got this working...

Basically we can go from a Word document like this below...

image

To a PDF document like this...

image 

Aspose Website

Aspose.Cells

This is a relatively new library for us and it does mostly what we need. There are a few things I've noticed about not running under partial trust. This is usually around setting and executing excel formulas in cells. Lucky for us, we use this mostly as a reporting tool and just dump tabular data into a spreadsheet.

iTextSharp

For more general PDF manipulation, we use iTextSharp. We use this library for the following (using a helper library we've created):

  • joining 2 or more PDF files into 1 to email to a user (rather than making a user download 4 PDF files, we roll them all up into 1 PDF and send that to them instead.... it make it easier for them to print that way)
  • watermarking an existing PDF

You can use iTextSharp to do PDF generation, but its extremely low-level. Given the amount of times end-users change their mind when it comes to documents generated from our systems, it was well worth the money to ASPOSE to allow users to create their documents in Word.

To get iTextSharp working under partial trust, we had to sign and recompile the whole library (and a related library from memory)

Microsoft Enterprise Library

We just use the Data Access Layer functionality from EntLib version 3.1 (and probably just a really small portion of that). We run all our data access through stored procedures and usually return either DataSets or DataReaders (depending on what we need).

To get this working under partial trust was a MAMMOTH effort. The compiled libraries didn't come signed or partially trusted... thus they didn't work. We had to recompile the whole enterprise library.
NOTE: I think the compiled libraries available for download are now signed.

Enterprise Library website

NLog

NLog is a really good logging library. We've use it in every project we start now.

We believe that when designing and writing applications, supportability is key.
When logging information we keep the following information:

  • every stored procedure we run (including all the aspnet membership SPs). We re-wrote them to include logging. Basically every stored proc run logs to a table before it exits
  • every page execution (who, when and how long it took to run). I do this as a page handler (I'll try to write about this in the future)
  • any exception (we have a global error page that handles errors which logs and display a friendly error to the user)
  • anything else we way think necessary to help with diagnosis or supportability. eg run-times of reports

To get NLog working under partial trust required us to recompile the whole library from source.

Nlog Website

AJAX - AjaxControlToolKit, System.Web.Extensions

What web application could you start now without including AJAX somewhere in there???. For AJAX we use:

  • System.Web.Extensions
  • AjaxControlToolkit
  • NhsCui toolkit - We used this primarily for the "time picker" control. A very nice one... I like it. This was a pain to get working under partial trust though... In the file "CommonAssemblyInfo.cs" there is an attribute which requests full trust. In this web application this wasn't necessary, and works when you remove this attribute.

    [assembly: PermissionSet(SecurityAction.RequestMinimum, Name = "FullTrust")]

NVelocity

We use NVelocity as our templating engine. Basically we use it as a mail-merge engine where we really just want either HTML or text as our output. We have wrapped a helper library around the standard NVelocity library as we don't use any of the functionality that reads templates from .vm files. We always pass in the template (usually stored in the DB) and our "merge fields" and retrieve the output from NVelocity.

NVelocity website

Home Grown Libraries we use for nearly every project

There are a couple of other home-grown libraries we use in almost every project.

ForceSSL
Running our applications on our hosting server, we can't get access to any IIS settings, specifically relating to SSL. Instead we have a HttpHandler that intercepts the page request, and redirects it to the same page, but using SSL.

Post Code Picker
This control is similar to a Date Picker or Colour Picker. This control was written as a Extender using the AjaxControlToolkit and then turned into a composite user control. The user enters in the "Post Code" (Zip Code) and a list of available Suburbs (and States) appear retrieved from a web service.

image

image

Convert RTF to text
One of our applications requires us to interface with an application that stores data in a BLOB field formatted as Rich Text (RTF). We've written a convertor that takes the raw RTF data and converts it to raw text (removes all formatting codes etc.). I'll try to publish how we do this later as well.

ABN Lookup helper
The Australian Government allows us to access company records using a publicly exposed web service. In Australia (as with most countries I guess) each company has an ABN (Australian Business Number). This web service is great, as is stops duplicate records from being entered in applications we've written and verifies that the ABN that a client has given us is really theirs.

We've written a helper wrapper around the web service and drop this into our applications as a library, rather than reference the web service directly in every application we write.

Payment Provider (Commerce Starter Kit)
In quite a few of our applications we have to interface with a payment gateway to process credit card transactions. We've taken the Payment Gateway provider that came with the original Commerce Starter Kit and re-rolled it. This allows us to:

  • use the provider based model to connect to different gateways
  • plug in a test gateway that approves all transactions (this is good for testing as most payment gateways use the cents value of the transaction to approve/decline a payment). eg 00 cents will pass, but 02 cents will return an error... Error number 02..

Nearly all our websites are hosted up on WebCentral. WebCentral operate all their websites under a partial (hosted) trust environment.

Internally we develop all our sites using this partial trust setup as it just causes too many headaches to develop in full trust and then try to retrofit everything to work under partial trust.

We originally received instructions on how to setup a machine to run in partial trust, but it involved editing config files located under the C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG directory. While this is fine, if I wanted to test something quick and simple (and didn't care about partial trust), I'd have to rename files, restart IIS, close Cassini... blah blah blah... too hard..

A little while ago I discovered how to run just a site as partial trust. It involves putting the hostedtrust.config file in the same directory as the web.config file. This works well for us, as it means we can add all the files we require to a project, which are all then added to SourceSafe. When we get the latest version, we also get all required files to run in partial trust.

Instructions

We were given the hosted trust setup from WebCentral as they dictate.

Copy this file into the root directory of your website.

image

In your web.config file add the following into the "system.web" configuration section.

<!-- **************************************************** -->
<!-- WEBCENTRAL HOSTED TRUST CONFIG -->
<securityPolicy>
    <trustLevel name="Hosted2" policyFile="web_hostedtrust2.config"></trustLevel>
</securityPolicy>
<!-- Comment out line below to run in FULL TRUST -->
<trust level="Hosted2" originUrl="" />

<!-- **************************************************** -->

Below is a screen shot of what my web.config file looks like.

image

If you ever need to run the site in full trust to test something out, just comment out the "trust" element.

What libraries I use (and don't use)

In my next post I'll list out the libraries I use in Partial Trust... and the one's I don't use and why.

I've had pretty good success getting sites working that need server-side image manipulation, ZIP creation and PDF creation / manipulation.

More Posts Next page »