How best to report a bug

As a software developer, bugs are a fact of life.  In fact, I welcome bugs, up to a point, because that tells me that someone is actually using my creation.  Too often when there are no bugs, especially early in the development cycle, that means that the end user is not actually using the product.  Then, inevitably, the bugs surface in production at the most inopportune time.

That said, there are good ways and less good ways of reporting a bug to the developer.  (This has much in common with reporting problems to your doctor or auto mechanic, as well.)  Here are some guidelines for reporting problems:

If there are multiple users, it is usually best to designate one person as the gatekeeper passing bug reports to the developer.  Often times, that gatekeeper will already know the answer and not need to bother the developer.

A statement that something does not work, with no way for me to reproduce or better understand the problem, does little more than tell me there is a problem.  That is necessary and helpful, but it does not allow me to solve the problem.

The best way to report issues is to send an e-mail (or log an entry in the bug tracking system) with the following:
1.       Clear & specific subject – “Grid data not saved” is a good, specific subject; “Problem with the program” is not a good subject.
2.       Preferably one problem per e-mail or bug report, to make it easier to track specific issues.
3.       Specific steps to reproduce the problem, if possible.

This last point is the most important. A statement such as “I was searching for a job/client/order and got an error message” does not allow me to see the problem.  But clear, reproducible steps, which consistently show the problem, allow me to attack the bug.

For example, here are the steps used to document a bug I was recently working on in a program used to analyze interviews:

Steps:
1.  Admin->Test->Restore
2.  Load canon003-30101-30202
3.  Demarcate mode
4.  Slide down to the end of the Interview pane.
5.  Click on separator between U16 & U17 (“Unclear S” & “I’m a little”) to remove it.
6.  Click on separator between U16 & the new U17 (“I’m a little horse now” & “<Laughter>”) to remove it.
7.  Click on space between “S.” & “I’m”.

The specifics of the progam being tested are not important here.  What is important is the level of detail.  As you can see, the steps provide the detail necessary to reproduce the problem.  Sometimes it is difficult to discern, let alone write down, the necessary steps. Sometimes the critical detail can be something as non-obvious as the size of the window on your screen or where you click – to the pixel – on your screen, for example.   If you can’t determine the steps to reproduce it, provide as much detail as possible – for example, does it happen with specific interviews or all interviews.  Does it happen on other machines, or only your machine.  Does it happen for other people following the same steps?

Finally, once you have a set of reproducible steps, try simplifying until you have the bare minimum set of steps which will still yield the problem.  Often times the initial set of steps includes lots of extraneous steps which obfuscate the real issue and make it harder to test. On the other hand, sometimes it takes many preceding steps to get to the point at which a problem manifests.  Just try to get to the bare minimum.

Once I can reproduce the problem, there is a very good chance I can solve it.  However, if I cannot reproduce it, or at least see it reproduced, there is almost no hope of fixing it.

Posted in Software Development | Tagged , | Leave a comment

When is a bug not a bug?

This sounds like the intro to a bad geek joke.  (Answer:  When it is a feature?)

But seriously, this is a valid question.  Consider the following situation. 

You have some code that looks like it should not work. But it does. The program is in production, for gosh sakes.  Works great, every day, all day, this very feature.  So you pull your hair out trying to figure how this clearly defective code does not cause a problem.

After all, one of the laws of the universe says that when something doesn’t seem right, it probably isn’t.

Usually, the cause of the “bug” turns out to be operator error, e.g. editing a stored procedure in one database, while the connection string for the web site is pointing to a different database.  Or there might be multiple copies of the source code extant, and you are looking at a version different from the one that is actually running.

Not this time.  The specific “bug” which raised this question in my mind occurred in some JavaScript I had written for a web site.  Similar situations can crop up in any language or environment.  That said, the loosey-goosey nature of JavaScript makes it especially vulnerable.

I was trying to track down a bug where some, but not all, automated e-mails were not getting properly sent.  The error message was cryptic (although in hindsight it was perfectly clear, as is usually the case) and I was exploring all avenues.   Although the problem eventually turned out to be caused by a changed password for one e-mail account, at this stage that was not at all clear.

The page which actually sends the e-mails is called ReportsPage.aspx.  It is a strange beast in that it exists only as a vehicle for a CrystalReportViewer.  However, the fact that CrystalReports plays a tangential role here is of no consequence.  For once, the problem is not with CrystalReports.

ReportsPage is called client-side by JavaScript attached to every button in the site which might want to display or e-mail a report.  That way there is no visible postback and the report just pops up in a new browser window.

The JavaScript function which actually opens the page is called OpenReportsPage.  It takes several arguments, including the name and type of the report,  an ID pointing to the specific job or client or whatever is being reported on, and a Boolean indicating if the report is to be e-mailed.  Here is the function signature in the JavaScript:

function OpenReportsPage(ReportName, ReportType, KFID, bEmail)

 

In addition, there is an overloaded version, which accepts a Boolean that indicates if the report requires a range of dates.   If true, then the script looks at specific controls on the page at runtime to get the actual start and end dates.  It has the following signature:

function OpenReportsPage(ReportName, ReportType, KFID, bEmail, bGetRangeValues)

 

The problem was that when I examined the source view of the page in the browser, I found that many of the calls to OpenReportsPage had an extra Boolean argument. And that did not seem to cause any problem.  So was this a bug or not?

Now, if you immediately know the answer to this conundrum, then you are truly among the JavaScript cognoscenti.  If so, just hold on for a moment and don’t spoil it for everyone else.

A bit of backstory here:  I learned C# before JavaScript. One of the things I quickly learned about JavaScript is that it shares many features and much of its syntax with C#, excepting the loosey-goosey stuff alluded to previously.  For example, JavaScript is not strongly typed, and variables can be whatever type they happen to be first.

I knew about optional and default parameters in JavaScript, so I could understand how a call to OpenReportsPage with too few parameters might work. But too many?  That would certainly never fly in C#.

I searched my code over and over for the missing overloaded version of the function with an extra bool.  As a matter of fact, I remember adding that parameter to the function a year or more ago, then removing it a short time later.  But if there was no version of the function with six parameters, then why wasn’t there some sort of bad behavior?

That is when I learned about JavaScript’s Arguments object and understood why this code did not break.  It turns out that not only can a JavaScript function call have too few arguments, unlike in C#, but also unlike C#, it can also have too many.  Any “extra” arguments are accessible via the Arguments object.

The point here is not a discussion of how the Arguments object works in JavaScript, but rather to answer the original question, slightly rephrased:  Is this a bug?  Is it a problem that function calls have the incorrect number of arguments, even if no mayhem ensues?

After all, you might argue, the program runs perfectly fine and does not complain about the extra, now vestigial, arguments.  Clearly, they are left over from when the function had that extra argument , and when I removed the argument from the function, I forgot to update all the calls.

In C# that would never happen because not only is it illegal, but Visual Studio will warn me and it won’t build.

I decided this was in fact a bug, because it seriously clouds the maintainability of the code. If I were actually using the extra argument, it would be a different story. But there is no place for vestigial code in any production project.

Posted in Software Development | Tagged | Leave a comment

Fix Search in Win7 Windows Explorer

It was so frustrating. I would type in a search term in Windows 7 Windows Explorer that I knew, for a fact, to be contained in many of the text files in the folder, but the Search result consistently said “nothing found”.  That was just stupid.

Of course, there were several mitigating factors. 

For one, all the files I am searching have an extension of .log.  On previous versions of Windows, especially XP (I can’t speak to Vista because I managed to skip that one), there was a problem with searching that had to do with the file extension not being one of the favored extensions, e.g. .doc.  So if the text files happen to have an extension of .log, as so many of mine do, they were not searched. The fix for that was a Registry hack.  However, that is not the case here, with Windows 7.

The second issue has to do with indexing.  I have disabled indexing on most of my drives and folders.  The folks at Microsoft must think we spend our entire day doing nothing but searching, and so are willing to sacrifice all of our computing power, at the most inopportune times, to index the drive.  Not I, and I bet not a lot of other folks either.  I would much rather have my occasional search take 5 seconds rather than 1, or even one half second (which is an order of magnitude of slowness, I might add), than to have my machine periodically and randomly appear to freeze for tens of seconds at a time while it decides to do indexing.  If computers were so damn smart, you’d think they would at least be capable of figuring out a true idle time to do the indexing, but no. That would be asking too much.  So I turn off indexing.

But wait! They got you by the short hairs.  In the infinite wisdom of some idiot, or idiots, in Redmond, the default search configuration only searches the content of files if the location is indexed.  If it is not indexed, it searches the file names, but not the content. Well, duh.  Why in the world would I ever not want to search the content?  If they are so afraid of lengthy searches, give a warning the first time, but make the default to search through everything.

I will say that I have worked with many Microsofties over the years, including some in Redmond, and I found most of them to be wonderful, smart and thoughtful people, just trying to do a good job and produce good software, so I don’t mean to demean all the developers at Microsoft.  But they shoot themselves in the foot with some of their design decisions.  Frankly, this smells like a marketeer’s idea.

There is an easy fix however, and it does not even involve the Registry.

Open Windows Explorer.  Click the Tools menu item, then Folder Options, then select the Search tab, as shown here:

FolderOptions

In the “What to search” box, the default is the first radio button.  Select the 2nd radio button – “Always search file names and contents (this might take several minutes)”.  Note the ominous warning. 

Click OK.

There!  All done.  (Doesn’t that feel like you are really sticking it to The Man?)  Your searches will now work as expected.

Posted in Windows | Tagged , , , | Leave a comment

Tales of a Version Upgrade

Face it, upgrading versions can be a major pain in the butt.  Especially when it involves moving third-party stuff like Crystal Reports from Dev to QA to Production. The wrong library, or a missing library, causes the whole thing to crash to a halt.

And so it was recently when I upgraded my development environment from Visual Studio 2008 to VS 2010.  (I know, kind of late isn’t it? But such is life. ) That necessitated upgrading Crystal Reports from 2008 (internal version 12) to “Crystal Reports for Visual Studio 2010″ (Version 13.0.2 SP2).

Here is the situation: there is my Dev machine, a QA server, and a Production server. For a long time, all the apps were developed and deployed with .NET 2.0 and VS 2008.  A new app making its way through QA uses VS 2010 and .NET 4.0, along with the new Crystal Reports.  In addition, I have decided to upgrade one of our two websites to the new versions as well. 

Getting this all to work without breaking the “unchanged” website has proven quite a challenge.  So far, I have been navigating the pitfalls on the QA server.   Hopefully, when the changes finally reach production, we will have ironed out all the hitches.

The Dev Machine

After installing VS 2010 on my Dev machine (leaving VS 2008 in place, which still works fine), I went to the SAP Crystal Reports website, downloaded “Crystal Reports for Visual Studio 2010″ which integrates into VS,  and installed that. (My recollection was that it was covered by a prior purchase of CR 2008, but I can’t say for sure. The main landing page for CR for VS  on the SAP website is at http://www.sdn.sap.com/irj/sdn/crystalreports-dotnet.)

The upshot was a new folder in c:\Program Files.  Whereas CR2008 lived in c:\Program Files\Business Objects, the new stuff lives in C:\Program Files\SAP BusinessObjects.

Here is a screen shot of the CR 2008 folders in Program Files (sanitized a bit):

CRFolders2008

Here are the equivalent folders for the new version of Crystal Reports.  It looks like they call it “Crystal Reports 2011″, although it goes w/ Visual Studio 2010. And the internal version is 13, although you can’t see that from this screenshot.

CRFolders2011

Note that in the previous version, the highlighted folder is called crystalreportviewers12 while in the newer version it is just crystalreportviewers, with no version number.  That becomes a point of confusion later.

Now when I open a web site which uses Crystal Reports, if it has not done so already, VS offers to convert the solution, with this dialog.

CRConversion

If VS detects the web site itself is from an earlier version of .NET, it offers this as well:

OldDotNetFound

The upshot of all this is that web.config is significantly shortened (a number of sections are no longer included) and all the references in it to Crystal libraries are modified with the updated version, as in:

<add assembly=”CrystalDecisions.CrystalReports.Engine, Version=13.0.2000.0, Culture=neutral, PublicKeyToken=692fbea5521e1304″/>

In addition, the @Register directive on all the pages which use Crystal Reports components is updated, as in:

<%@ Register assembly=”CrystalDecisions.Web, Version=13.0.2000.0, Culture=neutral, PublicKeyToken=692fbea5521e1304″ namespace=”CrystalDecisions.Web” tagprefix=”CR” %>

New web sites, of course, just use the new stuff.

So far, so good. Now you can develop or maintain your web site with Crystal Reports components and it works great in Dev.  Then comes…

The Server

Once deployed to a server, things look quite a bit different.

First you have to download and install the Crystal Reports redistributable.  I downloaded and installed this file from the CR web site.  I clicked on the “Redist Install 32-bit” link under Support Pack 2 to download  CRforVS_redist_install_32bit_13_0_2.zip, which I then installed on the server.

It inserted the same folders under Program Files shown above for the Dev  machine.  However, they are not actually used. Instead, there is a new folder called aspnet_client, that needs to be installed into the virtual root of the website, as shown in this screen shot of the root directory for the web site..

Server folder

Note that the crystalreportviewers folder now has the number 13 appended.

Where did this folder come from?  When the redistributable is run on the server, it puts a copy of this folder in the default web site virtual root, i.e. c:\inetpub\wwwroot.  You just have to know to copy it to each of your website root directories, if they are different from inetpub.

Then when you deploy the updated web site, most critically with the updated web.config, it should all work.

The one remaining question in my mind is why the Dev machine gets the CrystalReportViewer JavaScript files from c:\Program Files and the server needs to find it as a subdirectory of the virtual root.   I posed this question on the SAP Crystal Reports Forum and got this response from Ludek Uher:

There are a few things to consider;
1) When running the app on the development computer, you have the option of running the app under File System (Cassini) or HTTP. When choosing File System, the viewer used will be from the C:\Program Files (x86)\SAP BusinessObjects\Crystal Reports for .NET Framework 4.0\Common\Crystal Reports 2011\crystalreportviewers directory.
Using HTTP, the directory will be C:\inetpub\wwwroot\aspnet_client\system_web\4_0_30319\crystalreportviewers13

2) On deployment, the MSI / MSM does not know if an app will be installed under default or custom app pool. Even if it knew an app would go under a custom app pool, it would have no idea which one. So, the viewer is / should be installed and configured under the default app pool (pointing to C:\inetpub\wwwroot\aspnet_client\system_web\4_0_30319\crystalreportviewers13).

So there you have it.  I hope this is helpful.

Posted in Crystal Reports, Software Development | Tagged , , , | Leave a comment

Opening a New Window from a TreeView Menu Item

The task was simple enough:  add a menu item to my company web site for my blog .  As you might have discerned, I use WordPress.com to manage and host my blog.  All I wanted to do was make the blog accessible to my web site visitors.  This called for a Blog menu item on my site, which when clicked, would open the blog site in a new window.

Like many ASP.NET web sites, I use a SiteMapDataSource in conjunction with a TreeView control to provide the menus. The SiteMapDataSource looks for a file in the virtual root called Web.Sitemap.  This file is a relatively simple XML file with a series of siteMapNode tags, which can be nested to provide a hierarchy of menu items.  Here is an abbreviated version of my Web.sitemap file:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<siteMap xmlns=”http://schemas.microsoft.com/AspNet/SiteMap-File-1.0” >
 <siteMapNode url=”root” title=”root”  description=””>
  <siteMapNode url=”Home.aspx” title=”Home” description=”Home page”/>
  <siteMapNode url=”Services.aspx?topic=services” title=”Services”>
   <siteMapNode url=”Services.aspx?topic=projects”
title=”.NET Applications”>
     <siteMapNode url=”Services.aspx?topic=webapps”
title=”ASP.NET Web Apps”/>
     <siteMapNode url=”Services.aspx?topic=folde”
title=”Fast Online Data Entry”/>
   </siteMapNode>
   <siteMapNode url=”Services.aspx?topic=database”
title=”Microsoft SQL Server”/>
  </siteMapNode>
   <siteMapNode url=”Resources.aspx” title=”Resources”/>
  <siteMapNode url=”About.aspx” title=”About Us”/>
 </siteMapNode>
</siteMap>

Notice that each node has a url attribute, which specifies where to navigate to for this menu item, and a title attribute, which is the text displayed in the menu.

So far, so good.  Here is the code in the master page declaration (MasterPage.master) to put this menu in place:

<asp:SiteMapDataSource ID=”smds” runat=”server”
               ShowStartingNode=”false” />
<asp:TreeView ID=”tvNavbar” runat=”server”
   BackColor=”Transparent”
   BorderStyle=”None” CssClass=”TextMenu”
   DataSourceID=”smds”
   NodeIndent=”25″ Width=”200″
   NodeWrap=”false” NodeStyle-VerticalPadding=”10″
   Orientation=”Vertical”
   OnTreeNodeDataBound=”tvNavbar_TreeNodeDataBound”
   SelectedNodeStyle-CssClass=”TextMenuSelected”
   StaticDisplayLevels=”2″>
</asp:TreeView>

Notice that the ShowStartingNode attribute of the SiteMapDataSource is set false to suppress the root level of the menu hierarchy.

I have also set several of the attributes of the TreeView to specify the look and feel, such as BackColor, BorderStyle, CssClass (which references a style class in the site CSS file), Orientation, and so on.  Crucially, the DataSourceID points to the SiteMapDataSource.

So back to my original goal of opening the blog entry in a new window.  The typical entry in the sitemap file implements a normal menu item which navigates to a page in the site:

<siteMapNode url="Services.aspx?topic=services" title="Services">

The typical way to open a link in a new window is to include the target attribute, as for example in this anchor tag:

<a href="http://danhurwitz.wordpress.com" target="_blank">Blog</a>

So translating this to the sitemap file, you would expect something like this to work:

<siteMapNode url="http://danhurwitz.wordpress.com" Title="Blog" target="_blank"/>

However, when I put this into the sitemap file, the target attribute had zero effect: the page opened in the current window.  So a different approach was needed. 

Hence the need for the OnTreeNodeDataBound attribute.  This event is raised every time a new node is bound to the TreeView.  But to make this work , I need some sort of flag in the node to tell the event handler code to add the Target attribute. So I put a hash mark (#) in the url of the siteMapNode, and remove the target attribute:

<siteMapNode url=http://danhurwitz.wordpress.com/# Title="Blog"/>

Then in the event handler, I test for the presence of the hash mark, and if it is found, add the target attribute.

protected void tvNavbar_TreeNodeDataBound(object sender, TreeNodeEventArgs e)
{
   if (e.Node.NavigateUrl.Contains("#"))
      e.Node.Target = "_blank";
}

Works great!

Posted in Software Development | Tagged , , , , , , , , , , | 3 Comments

CreateUserWizard (almost) Figured Out – Part 3 – the Code-Behind

This is the third and final entry in a series of posts about the CreateUserWizard.  The initial post provided an overview of the problem and the design.  The second covered the .aspx page with the CUW control declaration.  This final installment will cover the code-behind.

The full source code of the code-behind will be listed at the end.

The CreateUserWizard (CUW) control declaration offers a tremendous amount of power and flexibility.  However, most of the attributes declared for a control are text strings, with the attribute value typed in right there.  That is OK most of the time, but frankly, gets a bit tedious when using a lot of RegExValidator controls.

It would be so much better if you could stash a RegEx string someplace and just call it for the ValidationExpression.  And that is what is done here. As you saw in the previous installment, the declaration of the ValidationExpression in the RegularExpressionValidator included a databinding expression (note the use of single quotes and the specific characters – they are required exactly for it to work):

ValidationExpression='<%# Helper.strPhoneRegEx %>'/>

This databinding expression calls a public static (in the C# sense, Shared in VB.NET) string in my Helper class.  I stash all my regex expressions there.  Getting this to actually work is a bit tricky.  For now, here is a code sample that will create those RegEx strings.  Put this in the App_code folder as Helper.cs.  Included in this Helper class are two other methods used in code-behind at the end of this article, CleanHtml and FindControlRecursive.

public class Helper
{
   // e-mail regex modified from Recipe 4-11 in "Regular Expression Recipes
   // for Windows Developers" by Nathan A. Good, Apress
   // Case insensitive
   // Accepts optional additional e-mail addresses, separated by a semicolon.
   // Zero or more spaces can surround the semicolon
   private static string strEmailRegExLogic =
      @"[\w\d!#$%&'*+-/=?^`{|}~]+(\.[\w\d!#$%&'*+-/=?^`{|}~]+)*
        @([a-zA-Z\d][-a-zA-Z\d]*[a-zA-Z\d]\.)*[a-zA-Z][-a-zA-Z\d]*[a-zA-Z]";
   public static string strEmailRegEx =
      "(" + strEmailRegExLogic + @"){1}([ ]*;[ ]*" + strEmailRegExLogic + ")*";

   // strPhoneRegEx allows "617-123-4567", "(617)123-4567", "(617) 123-4567", or
   //    "617 123-4567" all w/ optional leading 1
   public static string strPhoneRegEx = @"(1[\ -])?\(?\s*\d{3}\s*
      [\ \)\.\-]?\s*\d{3}\s*[\-\.]?\s*\d{4}";

   public static string CleanHtml(string strIn)
   {
      // this removes malicious code from text
      strIn = Regex.Replace(strIn, @"<%", "<x%");
      strIn = Regex.Replace(strIn, @"Response.Write", "xResponse.Write");
      strIn = Regex.Replace(strIn, @".Cook", ".xCook");
      strIn = Regex.Replace(strIn, @".cook", ".xcook");
      strIn = Regex.Replace(strIn, @".Query", ".xQuery");
      strIn = Regex.Replace(strIn, @".Write", ".xWrite");
      strIn = Regex.Replace(strIn, @"Eval", "xEval");
      strIn = Regex.Replace(strIn, @"javascript", "javaXscript");
      strIn = Regex.Replace(strIn, @"&#", "&X#");

      return strIn;
   }   

   public static Control FindControlRecursive(Control rootControl, string controlID)
   {
      //  copied from MSDN doc
      if (rootControl.ID == controlID) return rootControl;

      foreach (Control controlToSearch in rootControl.Controls)
      {
         Control controlToReturn =
            FindControlRecursive(controlToSearch, controlID);
         if (controlToReturn != null) return controlToReturn;
      }
      return null;
   }

In order to make this work, it is necessary to call Page.DataBind in Page_Load.  This causes all the binding expressions on the page to execute, and our RegEx’s are retrieved from the helper class.

All the really interesting stuff happens in the CreatingUser event handler, which is raised before the user is actually created.  The event handler takes a LogInCancelEventArgs argument, which, as the name implies, allows you to cancel the event.  So the idea is to test if the page is valid, and if not, cancel the operation.  The trick here is to remember to first call the Page.Validate() method:

Page.Validate();
if (!Page.IsValid)
{
   e.Cancel = true;
   return;
}

If you’ve gotten this far in the code, you know at least that there are no validation errors on the page, and you can proceed with your custom validation.

In the example here, as described in the first installment, I need to test this person against the corporate database. I do this by passing the first and last names, phone number and e-mail address to a stored procedure called spVerifyUser. This sproc returns a PersonID, if the person exists in the corporate database, and any error messages . A typical error message might be “E-mail address does not exist” or “A person with this phone number does not exist”.  The error messages are coded into the sproc.

The sproc is executed using the ExecuteNonQuery method on a Command object (DataHelper.BuildCommand is a helper method in one of my standard libraries), inside a try/catch block. If all goes well, and the user is allowed to be created, then the new user is added to the “User” role (one of several imaginatively named roles defined for a user in the security database).

What is left?

As I mentioned in the first installment, and the reason for the “almost” in the title, I still need to figure out how to get access to the declarations for the Create User and Cancel buttons, for both esthetic and practical reasons. I’m sure they are available in some template, it has just been elusive so far. When I get it figured out, maybe I will add a fourth installment to this series. 

Meanwhile, here is the complete C# code-behind in CreateUser.aspx.cs.

using System;
using System.Data;
using System.Data.SqlClient;	
using System.Configuration;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class CreateUser : System.Web.UI.Page
{
   protected void Page_Load(object sender, EventArgs e)
   {
   Page.DataBind();	//  force databinding expressions that get the regex
   }

   protected void btnContinue_Click(object sender, EventArgs e)
   {
      Response.Redirect("Login.aspx");
   }

   protected void cuw_CreatingUser(object sender, LoginCancelEventArgs e)
   {
      //  Test that there are no validation errors 
      Page.Validate();
      if (!Page.IsValid)
      {
         e.Cancel = true;
         return;
      }

      //  test aginst corp DB if this is a valid user
      WizardStepBase step = (WizardStepBase)cuw.WizardSteps[0];	//  hard-coded to 1st step
      TextBox txtFirstName = (TextBox)Helper.FindControlRecursive(step, "txtFirstName");
      TextBox txtLastName = (TextBox)Helper.FindControlRecursive(step, "txtLastName");
      TextBox txtPhoneNumber = (TextBox)Helper.FindControlRecursive(step, "txtPhoneNumber");
      TextBox Email = (TextBox)Helper.FindControlRecursive(step, "Email");

      string strFirstName = Helper.CleanHtml(txtFirstName.Text);
      string strLastName = Helper.CleanHtml(txtLastName.Text);
      string strPhoneNumber = txtPhoneNumber.Text;	//  does not require cleaning because of RegEx validator 
      string strEmailAddress = Email.Text;		//  ditto

      string strUserRole = string.Empty;	// "u" for user, "s" for staff
      string strErrorMsg = string.Empty;
      long lngPersonID = 0;

      object[,] arParams = new object[,] 
              {
               {"FirstName", DbType.String, strFirstName, "Input"},
               {"LastName", DbType.String, strLastName, "Input"},
               {"PhoneNumber", DbType.String, strPhoneNumber, "Input"},
               {"EmailAddress", DbType.String, strEmailAddress, "Input"}
              };
      //  must build the SqlCommand here in order to have the cmd object returned
      //    in order to get the return parameter, rather than use DataHelper.ExecuteNonQuery
      SqlCommand cmd = DataHelper.BuildCommand(CommandType.StoredProcedure, "spVerifyUser_Demo", arParams, "Database");

      SqlParameter param = cmd.Parameters.Add("@PersonID", SqlDbType.BigInt, 0);
      param.Direction = ParameterDirection.Output;

      param = cmd.Parameters.Add("@ErrorMsg", SqlDbType.VarChar, 500);
      param.Direction = ParameterDirection.Output;

      try
      {
         cmd.ExecuteNonQuery();

         if (cmd.Parameters["@PersonID"].Value != DBNull.Value)
            lngPersonID = (Int64)cmd.Parameters["@PersonID"].Value;

         if (cmd.Parameters["@ErrorMsg"].Value != DBNull.Value)
            strErrorMsg = (string)cmd.Parameters["@ErrorMsg"].Value;

         lblErrorMsg.Text = string.Empty;
      }
      catch (Exception ex)
      {
         e.Cancel = true;
         lblErrorMsg.Text = ex.Message + Helper.strErrorMsgHelp;
         return;
      }

      if (lngPersonID <= 0 )
      {
         e.Cancel = true;
         lblErrorMsg.Text = strErrorMsg + Helper.strErrorMsgHelp;
         return;
      }
      else
      {
         try
         {
            Roles.AddUserToRole(cuw.UserName, "User");
         }
         catch (Exception ex)
         {
            e.Cancel = true;
            lblErrorMsg.Text = ex.Message + Helper.strErrorMsgHelp;
            return;
         }
      }

   }		//  close cuw_CreatingUser

}
Posted in Software Development | Tagged , , , , , | 1 Comment

CreateUserWizard (almost) Figured Out – Part 2 – The .aspx File

This is the second in a series of posts about the CreateUserWizard.  The initial post provided an overview of the problem and the design.  This post covers the .aspx page with the CUW control declaration.  The final installment will cover the code-behind.

The CreateUserWizard (CUW) control declaration can be used with essentially no attributes other than ID and runat, and some empty placeholder templates, but then you have the demo mode (described in the the previous installment), not the real power the control is capable of.

By default the CUW presents a Create User button. Wanting a Cancel button as well, I set DisplayCancelButton to true and the CancelButtonText, CancelButtonType, and CancelDestinationPageUrl properties.  This latter sends you back to the Login page (outside the scope of this article) to try again.

Note that both the DuplicateUserNameErrorMessage and DuplicateEmailErrorMessage properties are the same.  This is a continuation of the facade of using the EmailAddress as the UserName.

I routinely insert logging in all the apps I develop.  It is invaluable in finding and fixing problems.  There I noticed that each new user was automatically getting logged in as soon as she was created. This was most definitely not what we wanted.  So I set the LogInCreatedUser to false.  (The default is true.) Problem solved.

Actually, this last point is not as unambiguous as I would like.  On redirection to the Login page after leaving the CreateUserPage,  the current user is the newly created user, even before she actually logs in, if LogInCreatedUser is set true in the CUW, but not if it is set to false.  However, one of the fields maintained in the security database for each user is “Online”, which tells if that user is currently online.  Although this boolean field is not visible in the WAT in Visual Studio, it can be seen if you get all the current users by calling Membership.GetAllUsers, and databind the resulting collection of Users to a GridView.  The details of doing so are outside the scope of this article, but suffice it to say that when looking at the security data in this way, a user newly created with LogInCreatedUser set false shows up as Online, even though she is not logged in.

Two of the many events offered by the CUW control are handled here: ContinueButtonClick and CreatingUser.  The former  is raised when the user clicks the Continue button after the user is created.  It just takes them back to the Login page.

The really interesting event is CreatingUser. It is raised before the user is created. As you will see in the final installment of this series, it is where all the validation is taken care of – both on the page and custom server-side.  If the potential new user does not pass muster, the account is not created.

In the production version of this app, every field on the page had at least a RequiredFieldValidator control.  In addition the phone number and e-mail address fields had a RegExValidator, and the two sets of comparison fields had a CompareValidator.  In other words, it was easy to fail validation just from the UI, not counting what is happening on the server.

In the version here, for the sake of brevity, I have removed all the validation controls except one, just to show you  how to handle validation errors on the page. It is not so obvious as it should be.

In this page, as in most ASP.NET pages, most of the control attributes are text strings. That is OK most of the time, but frankly, gets a bit tedious when using a lot of RegExValidator controls, because you have to copy and paste the same long, arcane string over and over.  Much better to store that value someplace and just refer to it in the control declaration, rather than enter the actual string.

This is easily accomplished with data binding, but the syntax is a little tricky.  Look at the ValidationExpression in the RegularExpressionValidator.

ValidationExpression='<%# Helper.strPhoneRegEx %>'/>

This topic continues with the final installment, which will cover the code-behind.

Here is the full content of CreateUser.aspx, minus most of the validation controls.

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
 CodeFile="CreateUser.aspx.cs" Inherits="CreateUser" %>

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
 <h2>CreateUserWizard Demo</h2>
 <!--  Keep in mind that EmailAddress is actually UserName -->
 <asp:CreateUserWizard ID="cuw" runat="server" CancelButtonStyle-CssClass="SmBtn"
  CancelButtonText="Cancel" CancelButtonType="Button" CancelDestinationPageUrl="~/Login.aspx"
  CreateUserButtonStyle-CssClass="SmBtn" DisplayCancelButton="true" DuplicateUserNameErrorMessage="This e-mail address is already in use."
  DuplicateEmailErrorMessage="This e-mail address is already in use." LoginCreatedUser="false"
  OnContinueButtonClick="btnContinue_Click" OnCreatingUser="cuw_CreatingUser">
  <WizardSteps>
   <asp:CreateUserWizardStep ID="cuws" runat="server">
    <ContentTemplate>
     <table cellpadding="3" cellspacing="0" border="0">
      <tr>
       <td align="left" colspan="3">
        Please enter your information.
        <br />
        All fields are required.
        <br />
        <br />
       </td>
      </tr>
      <tr>
       <td align="right">
        First Name:
       </td>
       <td colspan="2">
        <asp:TextBox ID="txtFirstName" runat="server" Width="200" />
       </td>
      </tr>
      <tr>
       <td align="right">
        Last Name:
       </td>
       <td colspan="2">
        <asp:TextBox ID="txtLastName" runat="server" Width="200" />
       </td>
      </tr>
      <tr>
       <td align="right">
        Phone Number:
       </td>
       <td>
        <asp:TextBox ID="txtPhoneNumber" runat="server" Width="200" />
        <asp:RegularExpressionValidator ID="revPhoneNumber" runat="server" ControlToValidate="txtPhoneNumber"
         CssClass="TxtWrn" Display="Dynamic" ErrorMessage="Invalid phone number" SetFocusOnError="true"
         ValidationExpression='<%# Helper.strPhoneRegEx %>' />
       </td>
       <td>
        Any of your phone numbers on record (with area code).
       </td>
      </tr>
      <tr>
       <!-- HACK --
        Since control requires UserName, but I want to use email address,
        will ask for it twice, but mislabeled. -->
       <td align="right">
        E-mail Address:
       </td>
       <td colspan="2">
        <asp:TextBox ID="UserName" runat="server" Width="200" />
        <asp:RegularExpressionValidator runat="server" ID="revEmail" ControlToValidate="UserName"
         CssClass="TxtWrn" Display="Dynamic" ErrorMessage="Invalid email address" ValidationExpression='<%# Helper.strEmailRegEx %>'
         SetFocusOnError="true" ToolTip="Invalid email address" />
       </td>
      </tr>
      <tr>
       <td align="right">
        Confirm E-mail:
       </td>
       <td colspan="2">
        <asp:TextBox ID="Email" runat="server" Width="200" />
       </td>
      </tr>
      <tr>
       <td align="right">
        Password:
       </td>
       <td>
        <asp:TextBox ID="Password" runat="server" TextMode="Password" Width="200" />
       </td>
       <td rowspan="2" valign="top">
        <%# Helper.strPassword6Chars1PunctHelpText%>
       </td>
      </tr>
      <tr>
       <td align="right">
        Confirm Password:
       </td>
       <td>
        <asp:TextBox ID="ConfirmPassword" runat="server" TextMode="Password" Width="200" />
       </td>
      </tr>
      <tr>
       <td align="right">
        Security Question:
       </td>
       <td>
        <asp:TextBox ID="Question" runat="server" Width="200" />
       </td>
       <td>
        Example: My High School
       </td>
      </tr>
      <tr>
       <td align="right">
        Security Answer:
       </td>
       <td>
        <asp:TextBox ID="Answer" runat="server" Width="200" />
       </td>
       <td>
        Example: Brookline High
       </td>
      </tr>
      <tr>
       <td align="center" colspan="3" style="color: red">
        <asp:Literal ID="ErrorMessage" runat="server" EnableViewState="False" />
       </td>
      </tr>
     </table>
    </ContentTemplate>
   </asp:CreateUserWizardStep>
   <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
    <ContentTemplate>
     Congratulations! Your Portal account has been created.
     <br />
     <br />
     <asp:Button ID="btnContinue" runat="server" Text="Back to Login" CssClass="SmBtn"
      CommandName="Continue" />
    </ContentTemplate>
   </asp:CompleteWizardStep>
  </WizardSteps>
 </asp:CreateUserWizard>
 <asp:Label ID="lblErrorMsg" runat="server" CssClass="TxtWrn" />
</asp:Content>
Posted in Software Development | Tagged , , , , , , , | Leave a comment