Data Files for Unit Tests

Recently I had an opportunity to try to explore a couple of different ways of putting data files into testing projects. Often you may have code that reads a config file, gets data out of a CSV, or modifies or reads some other file on disk. I have found that when considering the problem of getting the tests to run in a CI environment (TeamCity), using coding utilities that make testing a bit easier to run (ReSharper) and needing the test to work on other developers computers, that putting your data files as embedded resources is the way to go.

The alternative when using MsTest is something called the TestItemAttribute.  I first found this out on StackOverflow. ​ Trying to use this methodology, I ran up against confusing and conflicting documentation and blogs regarding .testsettings that came in 2010 and still are somewhat compatible in 2012.  The replacement in 2012 is .runsettings which I also found had confusing documentation.  Many of the questions on these topics in StackOverflow are old enough to get people using them in 2010, with some trying to use the compatibilty in 2012.  After all this, the current version of Resharper (7.1.3) doesn't seem to be able to use the .runsettings so you end up with a test that works in the Visual Studio test runner but not Resharper's.   I didn't even get to the point of trying to get it to run in TeamCity.

properties.png

So back to embedded resources as a way of giving your unit tests something to work with.​  The first step to this (after you have your testing project is to add the file(s) to that project.  Once you have added them, you need to change the Build Action property to Embedded Resource.  This basically copies the file into the DLL assembly that your project produces.

Now you need to get that resource available so your test can use it.  Generally you might be testing some other class that deals with files and you want to put a file into a temp location for the class under test to use it.  ​

class TestUtility
    {
        public static string WriteResourceToTempLocation(string resourceLocation,string directory)
        {
            System.Reflection.Assembly currAssembly = System.Reflection.Assembly.GetExecutingAssembly();

            Stream stream = currAssembly.GetManifestResourceStream(resourceLocation);

            var directoryPath = Path.Combine(Path.GetTempPath(), directory);

            Directory.CreateDirectory(directoryPath);
            var filePath = Path.Combine(directoryPath, resourceLocation);
            using (var fs = File.Create(filePath))
            {
                byte[] bytesInStream = new byte[stream.Length];
                stream.Read(bytesInStream, 0, bytesInStream.Length);
                fs.Write(bytesInStream, 0, bytesInStream.Length);
                fs.Flush();
            }
            GC.Collect();
            return filePath;
        }

        public static void CleanupTempLocation(string directory)
        {
            var directoryPath = Path.Combine(Path.GetTempPath(), directory);
            Directory.Delete(directoryPath, true);
        }
}

This class provides some helper methods that can be easily used in the [TestInitialize] and [TestCleanup] methods of your test class.​

The last key to using this is being able to locate the resource in your dll.  The resource name and location will be appended to whatever the default namespace is as set in your project settings.​ This is generally what you named the project.

So if your project default namespace is "Foo.Bar.FileProcessing.Tests",​ and your testfile (testfile.txt) is located in a folder in your project named TestFiles.  then the resource location used in the above code would be "Foo.Bar.FileProcessing.Tests.TestFiles.testfile.txt"

Restoring SQL Express Databases

When I set up my latest computer, I paid special attention to making sure everything was backed up and I am really glad I did.  I had several issues with bad hard drives fairly early on and recently had another issue with some older drives that I have on the computer dying.  The only purpose these other drives had was to store the log files of my SQL Express Databases.   When they died, I actually didn't notice for several days until I tried to access one of these databases for some work on a development project.  All of my databases were offline.

Luckily I had set up an automated backup off all of the DBs.  Getting this setup using the Free SQL Express isn't obvious and requires (or at least helps to have) a great app that is out there called ExpressMaint.  This lets me run a Scheduled task that automatically backs up all my databases.   Those backups are then backed up by windows backup to a USB hard drive and by Carbonite online.

These databases were easy to restore after I changed the settings on my server to have the logs be on a different drive and made sure to restore to the good drives.   

Transitioning to Java

So my work, in the wisdom of someone that is not me has decided that we should standardize the language and environments that we program in to Java and Eclipse.  I am sure you can already sense my excitement.  I'm not particularly afraid of the Java language or even think it will particularly hard to learn the language.   Its nearly identical to C# in syntax and even a lot of the framework is the same, I think.  I think my biggest transition will be the tools around programming.  This means going from Visual Studio to Eclipse.

I'm sure if you are reading this 2nd paragraph then you are at least somewhat interested in programming so I am going to drop all pretense of talking to someone who doesn't have a technical background.    Eclipse is the defacto open source standard development environment for Java.   I previously set up Eclipse on my work computer and that was pretty straight forward.  I like the "Add Software" feature that you can use to add plugins and other bits to the IDE.  I don't like just how complex the UI is.  We are using Maven which is software that helps manage your project by downloading any dependencies that your project may have.  Maven uses a file called pom.xml to manage your project and the repositories that it retrieves dependencies from.   Opening pom.xml in Eclipse gives a page of form fields that you can fill out.  I was able to figure out that this translated to the xml, but it was non-obvious to me how to actually see the XML.   I finally noticed a series of small tabs at the bottom of the window that related to other sections of the project and finally the XML itself in the last tab.

Luckily I am working on a project with a couple of experienced Java developers who will be there to guide me and set up some of the things on the project that I would just do the wrong way.  My first assignment was to take an XSD and use JAXB on it to create a series of classes for the project.  I found a Maven plugin that would run JAXB and create the classes.  One of my problems is that all the documentation surrounding setting up this program assumes I know what I am doing.  For instance, the 2nd instruction indicated I should edit my project file.  It took me a while to figure out that that was the pom.xml.  Couldn't they just say pom.xml?   Probably not, because everything is so flexible that it can probably be called something else.

Once I had set up the plugin, nothing was immediately apparent on what I should to to initiate the generation of my classes.  It turns out something was not set up right fairly deep in a settings dialog box (Java Build Path), causing the project not to compile right. Once I got that set, the files did generate automatically. 

There is a great deal to be said about how Visual Studio installs, checks for its dependencies, and has everything ready to roll when it is finally installed.   So far Eclipse and its associated plugins and servers make you jump through a lot of hoops to get things running right.  Everything is installed separately (I use "installed" loosely as more often than not, you just get a set of binaries that you put into a folder under your C: drive) and because you are installing these things in any particular place you have to let Eclipse and your system know where they all are.  Is it really that big a deal to have an installer that just puts stuff where it "should" go and then set all the settings correctly?

A Custom Ellipsis Plug-in for JQuery

I had a requirement to be able to show a long description for an item in a limited space.  The descriptions were coming from a 3rd party database and could be of any length.  The design called for the description area being two lines tall.  There is a CSS attribute called text-overflow: ellipsis.  It had several problems, however.  First of all it only worked on a single line basis. Mostly it had the big issue of not working at all in Firefox as it was a non-standard CSS call.

I found a jquery plugin to duplicate the functionality of the CSS call but like the CSS call it only worked on a single line.  My design spec also called for a More/Less link to be affixed to the block of text to expand it/ contract it.  The More/Less also had to support different cultures.

//ellipsis plugin http://devongovett.wordpress.com/2009/04/06/text-overflow-ellipsis-for-firefox-via-jquery/ + comments + custom mods
(function($) {
    $.fn.ellipsis = function(lines, enableUpdating, moreText, lessText) {
        return $(this).each(function() {
            var el = $(this);
            var resetDescription = function(height, originalText) {
            el.html(originalText);
            el.animate({ "height": height }, "normal", null, function() {
                el.ellipsis(true, true, moreText, lessText);
          });
            }

            if (el.css("overflow") == "hidden") {
 
                var originalText = el.html();
                var availWidth = el.width();
                var availHeight = el.height();
 
                var MoreLessTag;
                if (moreText) {
                    enableUpdating = true;
                    MoreLessTag = " <a class='MoreLessTag' href='#' >" + moreText + "</a>";
                }
                else MoreLessTag = "";
 
                var t = $(this.cloneNode(true))
                    .hide()
                    .css({
                        'position': 'absolute',
                        'overflow': 'visible',
                        'max-width': 'none',
                        'max-height': 'none'
                    });
                if (lines) t.css("height", "auto").width(availWidth);
                else t.css("width", "auto");
                el.after(t);
                t.append(" <a class='MoreLessTag' href='#' >" + lessText + "</a>");
 
                var fullHeight = t.height();
 
                var avail = (lines) ? availHeight : availWidth;
                var test = (lines) ? t.height() : t.width();
                var foundMin = false, foundMax = false;
                if (test > avail) {
                    //Binary search style trimming of the temp element to find its optimal size
                    var min = 0;
                    var max = originalText.length;
                    while (min <= max) {
                        var trimLocation = (min + max) / 2;
                        var text = originalText.substr(0, trimLocation);
                        t.html(text + "&hellip;" + MoreLessTag);
 
                        test = (lines) ? t.height() : t.width();
                        if (test > avail) {
                            if (foundMax)
                                foundMin = true;
 
                            max = trimLocation - 1;
                            if (min > max) {
                                //If we would be ending decrement the min and regenerate the text so we don't end with a
                                //slightly larger text than there is space for
                                trimLocation = (max + max - 2) / 2;
                                text = originalText.substr(0, trimLocation);
                                t.html(text + "&hellip;" + MoreLessTag);
                                break;
                            }
                        }
                        else if (test < avail) {
                            min = trimLocation + 1;
                        }
                        else {
                            if (foundMin && foundMax && ((max - min) / max < .2))
                                break;
                            foundMax = true;
                            min = trimLocation + 1;
                        }
                    }
                }
 
                el.html(t.html());
                t.remove();
 
 
                if (moreText) {
                    jQuery(".MoreLessTag", this).click(function(event) {
                        event.preventDefault();
                        el.html(originalText);
                        el.animate({ "height": fullHeight }, "normal", null, function() {
                        });
                        el.append(" <a class='MoreLessTag' href='#' >" + lessText + "</a>");
                        jQuery(".MoreLessTag", el).click(function(event) {
                            event.preventDefault();
                            resetDescription(availHeight, originalText);
 
                        });
                    });
                }
                else {
                    var replaceTags = new RegExp(/<\/?[^>]+>/gi);
                    el.attr("alt", originalText.replace(replaceTags, ''));
                    el.attr("title", originalText.replace(replaceTags, ''));
                }
 
                if (enableUpdating == true) {
                    var oldW = el.width();
                    var oldH = el.height();
                    el.one("resize", function() {
                        if (el.width() != oldW || (lines && el.height != oldH)) {
                            el.html(originalText);
                            el.ellipsis(lines, enableUpdating, moreText, lessText);
                        }
                    });
                }
            }
 
        });
    };
})(jQuery); 

The following features are added from the original:

  • More/Less link with expansion
  • multiple lines
  • title and alt text if no more/less text is provided

This hasn't been tested extensively under different conditions.

Things I would do if I had an infinite amount of time:

  • More Testing
  • Ability to override the More/Less text click event

Enjoy – I hope someone finds this useful.  This was my first foray into doing a jQuery plugin.  Even though a good chunk of the code was copied, I still learned quite a bit.

Altering the Look of the DNN FAQ Module

I had an issue with the way that the DNN module for FAQ's was behaving. Some extra space was appearing around my lists of questions and breaking up the way the page was supposed to look.  It turns out that the questions were getting these extra <p> tags around them.  Unfortunately, because the <p> tags were unexpected, the FAQ module was doing strange things like wrapping the <p> tags with <b> tags (it also wraps the whole thing in an <a> tag too to open up the question), which isn't really proper HTML. I was able to edit the template of the FAQ Module to stop the <b> tags but there was still the issue of the wierd spacing that was being created by the <p> tags.

The FAQ module was originally written against a different default rich text editor than what DNN ships nowadays (FCKeditor).  The old editor wouldn't add <p> tags to the text of your questions.  The FCK editor does and although it can be disabled by changing a config file, there are good reasons for keeping it, such as keeping your text wrapped correctly for proper HTML.  Using this config change would also change the behavior for the rich text editor through out all portals and modules, which would have bad side effects.

An option I have seen posted in a couple of places was to use the plain text module, but the problem with that is that changing that option seems to change all editor boxes sitewide, including the answer box.  Editing the source and leaving it without the <p> tags works but is not a good option because there are these special set of instructions for any editor who happens to want to update a question. This jumping through hoops isn't a good idea.

The solution is style sheets. Using Firebug, I could easily determine that the FAQ module wraps itself in a div with the class DNN_FAQsContent.  Now I can affect the <p> tags style within this div without affecting <p> tags throughout the site.  I decided to use the portal.css because I have several portals and I didn't want to unexpectedly affect them.  I was also pretty sure that I would want these effects even if I later changed skins.  So I didn't want to change the skin.css.

 

/* MODULE-SPECIFIC */
.DNN_FAQsContent a.SubHead p
{
    display:inline;
    margin: 0px 0px 0px 0px;
}

 

Let me explain what I did.  Since the specific <p> tags I wanted to attack were in the div with the class DNN_FAQsContent I started with that identifier.  The <p> tags were also (probably improperly) surrounded by an <a> tag with a class of SubHead.  This was all very easily discovered by using Firebug.  I wanted to make sure I wasn't affecting the <p> tags that are in the answers.   Once I used the CSS to identify the tags I wanted to change, I applied the changes.  display:inline changes the <p> tag from being a block element to being an inline element.  This is more along the lines of how the the FAQ module originally displayed the question.  I also took out any margins.

SCA Online OP Improvements, Link to Dynamic Forms Module - Part 2

In Part 1, I did all the background programming to put the settings in and display them the correct way in the settings page, but I haven't actually used the settings to create the link yet.  I also was blogging as I was programming which I now know why this is a rarity- It takes a long time and although the side trips down a buggy path may be interesting, its distracting from getting the actual work accomplished.  I finished this up yesterday without blogging about it and it went much faster.  So this is all done now and I probably won't linger too long on any of the problems I had.

The purpose of all the settings was to enable a link to be made from my Online OP module to the Datasprings Dynamic Forms module I have set up to take Award Reccommendations.   The first part was to customize this form a bit more so that it would even recognize this data.  The Dynamic Forms Module allows for data to come in in several ways, cookie, session, and querystring.   I decided to use the querystring to send this info forward.  In the advanced options of the question I found the setting I was looking for.

I set this for the Name and Branch field of the Awards Form and I also set up a hidden field for the internal member id.  I would use this to pass the id forward so it could link to the person in the OP.

Once the fields were set up, I modified the email result so that it could contain a link directly to the person that was being recommended.  The form completion events have 2 events, one to send an email to the crown, the other to send it to the person submitting a recommendation.  Both of these would recieve the link.   Each already had the main email set up so I only had to add the link at the bottom of the existing email text.  The main trick (if you can call it a trick) was to put the $(memid) field into the link target.  $(memid) was the hidden field I had set up to read a querystring I was sending.

Now to set up the Online OP module to provide the link.  The first thing I did was put the link in near the Awards header in the OP Record screen.

 

<h2 class="SCAMemberAwardsHeader">
        Awards Received</h2>
    <asp:Literal ID="litNoAwards" runat="server" Visible="false">No Awards Recorded.<br /></asp:Literal>
    <asp:HyperLink runat="server" ID="hlRecommend" CssClass="RecommendLink" >Recommend this person for an award.</asp:HyperLink>
    <div class="break"></div>
    <asp:Panel ID="pnlAwards" runat="server" Visible="true">
        <asp:DataGrid ID="dgAwards" runat="server" AutoGenerateColumns="False" CssClass="ResultList">

 

I also set up the link so it would float off to the right, under the picture of the person if there is one but inline with the Awards header.  This is from the module.css

 

.RecommendLink
{
    display:block;
    float:right;
    margin:15px 0px 15px 0;
}

 

 Next I had to generate the link.  I checked the settings to see if I should generate the link and then if I should, which tab should I direct to.  The querystring key's are determined by the short name of the fields in the Dynamic Forms module.

 

if(Convert.ToBoolean(Settings["LinkToRec"]))
            {
                hlRecommend.NavigateUrl = Globals.NavigateURL(Convert.ToInt32(Settings["AwardRecTabId"]), "",
                                                              "Recommended=" + h1MemberName.InnerText, "memid=" + memid, "branch=" + Residence);
            }
            else
            {
                hlRecommend.Visible = false;
            }

 

I initially had some trouble with the Friendly Url Provider I was using (iFinity Friendly Url Provider) and the way it was handling some special characters such as commas and single quotes (apostrophes), but this was resolved by using the latest dll provided on the site for DNN 5. 

Writing this blog after the fact is definately the way to go. Its like providng your own code review, which is very handy on a project you work on by yourself.

 

SCA Online OP Improvements, Link to Dynamic Forms Module - Part 1

A lot of coding blogs write about problems they have already solved and while I know its not unique to do so, I want to write down stuff as I do them for this project, rather than summarizing a problem I have already solved. Although this series of blogs involves the SCA, it will be more about the process or working on a DNN (DotNetNuke) module and the programming that goes into it.

If you are not familiar with the SCA Online OP, it is a module I wrote and maintain for the Kingdom of Atenveldt, a branch of the SCA.  The OP part of the module stands for Order of Precedence.  This is basically a ranking system that we use in the SCA loosely based on historical practices of knowing who is ranked above who.  I have taken that practice and put a lot of work around it to not only list the people and what awards they have in the right order, but enable viewing pictures of the people, descriptions of the awards, information about the local groups the people reside in and information about the members that reign over them.  All of this can be found on the Atenveldt Online OP.

Since I will have a lot of free time this memorial day weekend, I am putting some effort into some improvements I have wanted to do to the module for some time.  The first improvement I want to do is to link the OP to the new forms module I just installed for making an award recommendation for a person.  The forms module supports having a field get data out of a querystring, so it should be a simple task of creating a dyamic link that will point from a person's record in the OP to the award recommendation form.  Even though this module will never be in widespread use and will most likely be a single installation on the Atenveldt site, I try to add features that stay flexible and have the possibility of transferring over to anther DNN installation.

Step 1.  Settings.

I figure the module needs to be pointed to from the settings.  I am imaging a checkbox that indicates that we want to have a reference link to the award rec. form, along with a drop down of all the installed Dynamic Forms modules installed in the portal.

I have a settings page already created. So I will add the controls to the form.  Here is what the settings form looks like.

<%@ Control AutoEventWireup="false" CodeFile="SCAOnlineOPSettings.ascx.cs" Inherits="JeffMartin.DNN.Modules.SCAOnlineOP.SCAOnlineOPSettings"
    Language="c#" %>
<%@ Register TagPrefix="dnn" TagName="Label" Src="~/controls/LabelControl.ascx" %>
<table>
    <tr>
        <td valign="top" class="SettingsLabelColumn">
            <dnn:label id="dlBasePortal" runat="server" controlname="ddlPortalList" />
           </td><td valign="top">
            <asp:DropDownList runat="server" ID="ddlPortalList">
            </asp:DropDownList>
        </td>
    </tr>
    <tr>
        <td valign="top" class="SettingsLabelColumn">
            <dnn:Label ID="dlLinktoReccomendationForm" runat="server" ControlName="chkLinkToRec" />
        </td>
        <td valign="top">
            <asp:CheckBox ID="chkLinkToRec" runat="server" />
        </td>
    </tr>
    <tr>
        <td valign="top" class="SettingsLabelColumn">
            <dnn:Label ID="dlReccomendationForm" runat="server" ControlName="ddlDynamicForms" />
        </td>
        <td valign="top">
            <asp:DropDownList runat="server" ID="ddlDynamicForms">
            </asp:DropDownList>
        </td>
    </tr>
</table>
 

 

Now that the form fields are created, I will put some backing logic to them.  First I need to fill the drop down list with all the modules that are the DataSprings Dynamic Forms that I am looking for.  As I am writing this code, I realize that I want to put a message indicating that I can't detect the Dynamic Forms module being installed.   This will be convenient to test as I don't have the dynamic forms module installed yet on my local build installation.   As I look at the code a bit more, I decide to just hide the setting if I can't detect the module existing.  So I put a placeholder module around the two table rows that contain the info needed for setting up the dynamic forms stuff.

 

ModuleController mc = new ModuleController();
ModuleInfo mInfo = mc.GetModuleByDefinition(PortalId, "Dynamic Forms");
if (mInfo == null)
{
    phAwardRecsSettings.Visible = false;
}
else
{
    phAwardRecsSettings.Visible = true;
    ArrayList moduleTabs = mc.GetModuleTabs(mInfo.ModuleID);
    ddlDynamicForms.DataTextField = "ModuleTitle";
    ddlDynamicForms.DataValueField = "TabId";
    ddlDynamicForms.DataSource = moduleTabs;
    ddlDynamicForms.DataBind();
}

 

 You may notice I am programming in C#.  This is just something I would rather do than keep flipping between VB for this and C# at work.  The modules work perfectly fine. 

The next step for me is to try this out.  I don't like going to far without making sure this code works.  This keeps it fresh in my mind if I have to debug it rather than debugging something I did an hour ago if I wait that long before testing it out.  After testing that nothing was different when I didn't have the dynamic forms installed, I installed the dynamic forms module, imported my form from the live website, and tested again... It works!

 New Settings Display Correctly

Now that everything is displaying correctly - I need to actually make the settings save (and be read).  I want to make sure the drop down box shows the correct value when the settings are loaded and the check box is correctly checked or unchecked.  Here are the methods:

public override void LoadSettings()
       {
           try
           {
               if (!Page.IsPostBack)
               {
                   var basePortalId = ((string) Settings["BasePortal"]);
                   if (basePortalId == null)
                       ddlPortalList.SelectedValue = "0";
                   else
                       ddlPortalList.SelectedValue = basePortalId;
 
                   var awardRecTabId = ((string) Settings["AwardRecTabId"]);
                   if (awardRecTabId != null)
                       ddlPortalList.SelectedValue = awardRecTabId;
 
                   bool linkToRec = Convert.ToBoolean(Settings["LinkToRec"]);
                   chkLinkToRec.Checked = linkToRec;
               }
           }
           catch (Exception exc)
           {
               Exceptions.ProcessModuleLoadException(this, exc);
           }
       }
 
       public override void UpdateSettings()
       {
           try
           {
               var objModules = new ModuleController();
               objModules.UpdateModuleSetting(ModuleId, "BasePortal", ddlPortalList.SelectedValue);
               objModules.UpdateModuleSetting(ModuleId, "AwardRecTabId", ddlDynamicForms.SelectedValue);
               objModules.UpdateModuleSetting(ModuleId, "LinkToRec", chkLinkToRec.Checked.ToString());
 
               Response.Redirect(Globals.NavigateURL(), true);
           }
           catch (Exception exc)
           {
               Exceptions.ProcessModuleLoadException(this, exc);
           }
       }

When I go to test, doh... there is a problem.

DNN Critical ErrorLets see what the Event Log has to say about that. 

It turns out after quite a trip down the debugging rabbit hold that the above code doesn't compile in ASP.NET 2.0 (I may upgrade my site shortly).  The 'var' keyword got slipped in there by an overzealous ReSharper reformat.  After changing the settings in ReSharper, fixing the 'var', I found that the LoadSettings() actually gets called BEFORE the Page_Load... this means that I need to bind my controls in the LoadSettings().  Just because I am a bit paranoid about this changing in some future version, I put in a BindControls() method that I can call from Page_Load too.  I also protect for binding twice unnecessarily.  The final code for the settings is below.

using System;
using System.Collections;
using DotNetNuke.Common;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Services.Exceptions;
 
namespace JeffMartin.DNN.Modules.SCAOnlineOP
{
    partial class ScaOnlineOpSettings : ModuleSettingsBase
    {
        #region Web Form Designer generated code
 
        protected override void OnInit(EventArgs e)
        {
            //
            // CODEGEN: This call is required by the ASP.NET Web Form Designer.
            //
            InitializeComponent();
            base.OnInit(e);
            this.Load += new System.EventHandler(this.Page_Load);
        }
 
        /// <summary>
        ///        Required method for Designer support - do not modify
        ///        the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
        }
 
        #endregion
 
        private void Page_Load(object sender, EventArgs e)
        {
            try
            {
                if (!Page.IsPostBack)
                {
                    BindControls();
                }
            }
            catch (Exception exc)
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }
 
        private bool boundOnce;
 
        private void BindControls()
        {
            if (!boundOnce)
            {
                PortalController pc = new PortalController();
                ArrayList Portals = pc.GetPortals();
                ddlPortalList.DataTextField = "PortalName";
                ddlPortalList.DataValueField = "PortalID";
                ddlPortalList.DataSource = Portals;
                ddlPortalList.DataBind();
 
                ModuleController mc = new ModuleController();
                ModuleInfo mInfo = mc.GetModuleByDefinition(PortalId, "Dynamic Forms");
                if (mInfo == null)
                {
                    phAwardRecsSettings.Visible = false;
                }
                else
                {
                    phAwardRecsSettings.Visible = true;
                    ArrayList moduleTabs = mc.GetModuleTabs(mInfo.ModuleID);
                    ddlDynamicForms.DataTextField = "ModuleTitle";
                    ddlDynamicForms.DataValueField = "TabId";
                    ddlDynamicForms.DataSource = moduleTabs;
                    ddlDynamicForms.DataBind();
                }
                boundOnce = true;
            }
        }
 
 
        public override void LoadSettings()
        {
            try
            {
                if (!Page.IsPostBack)
                {
                    BindControls();
                    string basePortalId = ((string) Settings["BasePortal"]);
                    if (basePortalId == null)
                        ddlPortalList.SelectedValue = "0";
                    else
                        ddlPortalList.SelectedValue = basePortalId;
 
                    string awardRecTabId = ((string) Settings["AwardRecTabId"]);
                    if (awardRecTabId != null)
                        ddlPortalList.SelectedValue = awardRecTabId;
 
                    bool linkToRec = Convert.ToBoolean(Settings["LinkToRec"]);
                    chkLinkToRec.Checked = linkToRec;
                }
            }
            catch (Exception exc)
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }
 
        public override void UpdateSettings()
        {
            try
            {
                ModuleController objModules = new ModuleController();
                objModules.UpdateModuleSetting(ModuleId, "BasePortal", ddlPortalList.SelectedValue);
                objModules.UpdateModuleSetting(ModuleId, "AwardRecTabId", ddlDynamicForms.SelectedValue);
                objModules.UpdateModuleSetting(ModuleId, "LinkToRec", chkLinkToRec.Checked.ToString());
 
                Response.Redirect(Globals.NavigateURL(), true);
            }
            catch (Exception exc)
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }
    }
}

Now that the settings are in and saved, I actually have to make use of them in the module.   I will save that for Part 2.

Check that Source code you get from the Internet.

I was looking at a page that did some timings of an API we use here at work and noticed that it was showing ms (milliseconds) rather than s (seconds).   It reminded me a bit of Verizon math as it was fairly obvious that these timings were completing in about 6 seconds and not 6 milliseconds.  I sent an email to my colleague who wrote the page and he indicated he had used it from a comment on a post at Scott Hanselman's site.  I noticed that the code there was using the kernel methods QueryPerformanceCounter and QueryPerformanceFrequency so I looked them up in MSDN and found that the QueryPerformanceFrequency was a count per second and not count per millisecond and that the code in the comment was wrong (formatted wrong).  Scott had long since closed the comments on this post and since I didn't have a way of pointing out the error at that source and with Scott's encouragement, I am writing this blog post.

Using code found on the internet is a great way to save time but you should understand what you are using and how it works.  Ultimately you are responsible for the function of the code when you send it off to your colleagues or boss.  It also turns out that this timing could more easily be done with the System.Diagnostics.Stopwatch class which uses the same kernel methods without cluttering your user code with the extern calls.  It also provides the results in a Timespan which makes it more clear just how long the results of your test took.