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

}
Advertisements

About Dan Hurwitz

A consultant specializing in .NET.
This entry was posted in Software Development and tagged , , , , , . Bookmark the permalink.

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

  1. DanielHe says:

    I suggest you’d try CSS to control the display of Create User and Cancel buttons.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s