Sunday, June 12, 2005

Using WebUIValidation.js in your own controls

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/using_webuivalidationjs_in_your_own_controls.htm]

ASP.Net includes several validator controls. These controls first validate data on the client using JavaScript. Much of the scripting for this is contained in the WebUIValidation.js file, which is in an IIS directory like \aspnet_client\system_web\1_1_4322. Each page containing any validator already includes this script, therefore you can reuse any of its functions in your own controls. For example, view the source of an aspx.page that has a validator on it, and you'll see a reference to the WebUIValidation.js file. Because it's a bunch of free methods, lets look at this file to see how we can benefit from it.

The file has many functions. I've included descriptions for some of the more relevant ones:

  • ValidatorUpdateDisplay(val)
  • ValidatorUpdateIsValid()
  • ValidatorHookupControlID(controlID, val)
  • ValidatorHookupControl(control, val)
  • ValidatorGetValue(id) - gets the value of a control given its Id
  • ValidatorGetValueRecursive(control)
  • Page_ClientValidate()
  • ValidatorCommonOnSubmit()
  • ValidatorEnable(val, enable) - can enable/disable a specific validator
  • ValidatorOnChange()
  • ValidatorValidate(val)
  • ValidatorOnLoad()
  • ValidatorConvert(op, dataType, val)
  • ValidatorCompare(operand1, operand2, operator, val)
  • CompareValidatorEvaluateIsValid(val) - used by Compare Validator
  • CustomValidatorEvaluateIsValid(val)  - used by Custom Validator
  • RegularExpressionValidatorEvaluateIsValid(val) - used by Regular Expression Validator
  • ValidatorTrim(s) - trims excess white space from input
  • RequiredFieldValidatorEvaluateIsValid(val) - used by Required Field Validator
  • RangeValidatorEvaluateIsValid(val) - used by Range Validator
  • ValidationSummaryOnSubmit()

The ID (such as in the ValidatorGetValue) indicates a string. The 'val' parameter indicates the Validator control itself, not just the ID. I previously wrote about how to use the ValidatorEnable to Disable Validators on the Client. However you can use other methods to get the value of a control or extend existing validator functionality. This is almost essential if you're going to build your own validators by deriving from BaseValidator.

JavaScript can be messy to many developers, and it's helpful to know that such a toolbox of production-ready scripts is already available to your pages.

Wednesday, June 8, 2005

Non-VSS Source Control for Easier Automation of ASP.Net Projects

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/nonvss_source_control_for_easier_automation_of_aspnet_projec.htm]

Like many Microsoft developers, I've used VSS as my primary source control system - and suffered for it. Perhaps the biggest problem I've had with VSS is the inability to batch the creation of ASP.Net web projects. Such applications require a virtual directory, and it seems like VSS hard-codes this info into a binary (not even an XML file) somewhere.

Automating Web project creation is useful for improving process. For example, you could create a batch script that get a version (whether the latest of a branch) from source control, and does all the preparation work to make it ready for development (set config values, create its own databases, make the virtual directories, etc...)

Normally to automate ASP.Net app creation (which I'll discuss more in another blog), you need to:

  1. Get the source code
  2. Create the virtual directory in IIS (with VBS, Directory Services, or even NAnt's tag).
  3. Set the virtual directory in the *.webinfo file
  4. Set the virtual directory in the solution file

The problem becomes when VSS is tightly integrated into Visual Studio, then it creates a fifth step:

  1. Find that binary file, "decode" it, set the virtual directory, and recode.

After *hours* of looking at this, I finally had to move onto other things.

However, my new firm (Ameripay) uses non-VSS source control that isn't tightly integrated into VS.Net, so it doesn't have that limiting 5th step. Consequently, I can now automate the creation of Web projects.

Sunday, June 5, 2005

Creating Excel Workbooks in .Net

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/creating_excel_workbooks_in_net.htm]

Every now and then I find myself wanting to dynamically create an Excel Workbook in .Net. Maybe it's for a data export, or to set up a structured template. Either way, there's only a small set of common utilities needed to get started. However, because Excel's automation model is based in COM (not .Net), it can be difficult to create those utilities. So below are the ones I've started used. Two disclaimers:

  1. I have only used these for simple, internal tools, not enterprise-critical production environments. Therefore they are not streamlined for performance.
  2. I've collected snippets here and there from other articles (which I no longer know where), so the idea isn't original.

My goal is to be able to programmatically create an Excel sheet, like so:

ExcelHelper.Utilities u = new ExcelHelper.Utilities();
u.AddSheet("Test1");
u.SetCellContent("A",new Cell("B",3), "Hello World!");
u.SetCellContent("A",new Cell("B",4), "=B3");
u.Show = true;

In this code, I first create an ExcelHelper.Utilities object which provides the methods to create and manipulate the workbook. I then add a sheet, set specific cell contents, and show the end result. This is sufficient for assembling most simple workbooks.

The code for ExcelHelper is below. Note that we needed to import the Microsoft Excel Core Library (in the COM tab):

using System;
using Microsoft.Office.Core;
using System.Diagnostics;

namespace ExcelHelper
{
  public class Utilities
  {
    public Utilities()
    {
      this.StartExcel();
    }

    private Excel.Application excelApp = null;

    private void StartExcel()
    {
      if( this.excelApp == null )
      {
        this.excelApp = new Excel.ApplicationClass();
//starts an Empty instance of Excel
      
        CreateNewWorkBook();
      }
    }

    private void CreateNewWorkBook()  
//string strFilePath
    {
      Excel.Workbook wb = this.excelApp.Workbooks.Add(Type.Missing);

     
//delete all worksheets but 1:
      DeleteSheet(3);
      DeleteSheet(2);
    }


    public void DeleteSheet(int intIndex)
    {
      ((Excel.Worksheet)this.excelApp.ActiveWorkbook.Sheets[intIndex]).Delete();
    }

    public void AddSheet(string strName)
    {
      Excel.Worksheet newWorkSheet;
      newWorkSheet =
        (Excel.Worksheet)this.excelApp.Worksheets.Add(
        Type.Missing, Type.Missing, Type.Missing, Type.Missing);
      newWorkSheet.Name = strName;
    }

    public void SetCellContent(string strWorkSheet, Cell c, string strContent)
    {
      Excel.Worksheet sheet1 =
        (Excel.Worksheet)this.excelApp.Sheets.get_Item(1);
      ((Excel.Range)sheet1.Cells[c.Row,c.Column]).Value2 = strContent;
    }


    public bool Show
    {
      get 
      {
        return this.excelApp.Visible;
      }
      set
      {
        this.excelApp.Visible = value;
      }
    }

  }
}

Notice that I also created a "Cell" object. This simply takes a value in the familiar Letter(Column)-Number(Row) format and translates it to a Number-Number used by Excel's automation object.

using System;namespace ExcelHelper{  public struct Cell  {    public Cell(int intRow, int intCol)    {      _intRow = intRow;      _intCol = intCol;    }    public Cell(string strCol, int intRow)    {      _intRow = intRow;      _intCol = ConvertStringToIntColumn(strCol);      if (_intCol > 256)        throw new ArgumentException("Column cannot be greater than IV (256). Value was '" + strCol + "'.");    }    private static int ConvertStringToIntColumn(string strCol)     {      strCol = strCol.ToUpper();      //A --> 1, BD --> (26+4), IV      //Can only have max of two characters.      char[] ach = strCol.ToCharArray();            if (ach.Length == 0)        throw new ArgumentException("Column cannot have 0 length. Must be A - IV. Was: '" + strCol + "'.");            else if (ach.Length == 1)        return GetNumberValue(ach[0]);      else        return 26*GetNumberValue(ach[0]) + GetNumberValue(ach[1]);    }    private static int GetNumberValue(char c)     {      return (int)c - 64;    }    private int _intRow, _intCol;    public int Row     {      get       {        return this._intRow;      }    }    public int Column     {      get       {        return this._intCol;      }    }  }}

Thursday, June 2, 2005

Multiple Onclick JavaScript Events on a Single Control

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/multiple_onclick_javascript_events_on_a_single_control.htm]

Change History: This post was updated on July 5, 2005. See Update at bottom.

You can't add two identical events to the same Html control. For example, if you have a button with an onclick event, and you add another onclick at the server, the second event will overwrite the first. This becomes a problem if you need to add an event to some variable control, but you don't know what events that control already has. However, .Net provides a way to handle this.

We can:

  1. See if the variable html control already has an onclick event.
  2. If it does, then dynamically register a new script that calls both the original onclick script as well as the new script.
  3. Replace the variable control's onclick value with that new script.

The following code snippets this. This first block shows a simple JavaScript (with a parameter) being called by a button's onclick event.

function DoStuff1(var1) {
    alert('1: ' + var1);
}

...

...

This snippet shows the server code to check if an onclick event already exists, and add a new wrapper if needed. Note that it persists the onclick values in viewstate to ensure that the wrapper function doesn't wrap itself upon postback. For example, if the first onclick event called DoStuff1(), and we wanted to dynamically add a new onclick function DoStuff2(), we could create a wrapper function Wrapper1() that called both functions, and was called from the onclick. Wrapper1() becomes the new value of the button's onclick.

private void Page_Load(object sender, System.EventArgs e)
{
    string s = this.Button1.Attributes["onclick"];
    if (!Page.IsPostBack)
        OriginalJSFunction = s;
    if (s == null)
        this.Button1.Attributes.Add("onclick","DoStuff2()");
    else
    {
        if (!Page.IsClientScriptBlockRegistered("JS1"))
            Page.RegisterClientScriptBlock("JS1",GetJS(OriginalJSFunction));
        this.Button1.Attributes.Add("onclick","Wrapper1()");
    }
}

private string OriginalJSFunction
{
    get
    {
        object o = ViewState["OriginalJSFunction"];
        if (o == null)
            return "";
        else
            return o.ToString();
    }
    set
    {
        ViewState["OriginalJSFunction"] = value;
    }
}

private string GetJS(string strOriginalFn) //will handle initial JS with params
{
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    sb.Append(@"
       
    ");

    return sb.ToString();
}

 While this approach is tedious, it lets you dynamically add an event to a control without overwriting that control's existing event. This is useful when making your own custom controls that integrate with existing Html controls already on the page.

UPDATE July 5, 2005

Since writing this post, I've come across a cleaner technique. You can run multiple JavaScripts simply by listing them in the onclick event:

 id="Button1" onclick="DoStuff1('Hello1');Method2();Method3()"

Therefore we can simplify our codebehind. This has two main changes: (1) It modifies OriginalJSFunction to append the ";" to the end of a non-function, (2) It then always adds an onclick method, as opposed to checking if there already exists an onclick method.

private void Page_Load(object sender, System.EventArgs e)
{
  string s = this.Button1.Attributes["onclick"];
  if (!Page.IsPostBack)
    OriginalJSFunction = s;
   
  this.Button1.Attributes.Add("onclick",OriginalJSFunction + ";DoStuff2()");
}

private string OriginalJSFunction
{
  get
  {
    object o = ViewState["OriginalJSFunction"];
    if (o == null)
      return "";
    else
      return o.ToString() + ";";
  }
  set
  {
    ViewState["OriginalJSFunction"] = value;
  }
}

Monday, May 30, 2005

Automating process with Dynamically Compiled C#

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/automating_process_with_dynamically_compiled_c.htm]

I had the opportunity to have another article published in .Net Developer's Journal this month: Automating Your Processes - an NAnt Case Study. The article compares various techniques to automate processes, and then makes a plug for using dynamically compiled C#. This gives the best of both worlds - the flexibility of scripting but the structure and support of a compiled OOP language. The article concludes by applying this to an NAnt build process.

At this time, the article requires a subscription to .Net Developer's Journal to view.

Wednesday, May 25, 2005

Disable Validators on the Client

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/disable_validators_on_the_client.htm]

Sometimes you may want client-side activities to disable a validator. For example, suppose a TextBox is only required if a CheckBox is selected. The TextBox could have a RequiredValidator, but we'd like selecting the CheckBox to enable/disable that validator at the client.

One approach is to use JavaScript functions from the WebUIValidation.js file located at: aspnet_client\system_web\1_1_4322. This stores the script used by ASP.Net's validators and is available to all pages. It includes a script, ValidatorEnable, which takes a control and a boolean and sets the control's Enabled property to the boolean value.

For example, you could have the onclick of a checkbox (id=ChkEnable), call the custom javascript below.

function EnableValidator() {
    var blnEnabled = document.Form1.ChkEnable.checked;
    ValidatorEnable(document.getElementById("RequiredFieldValidator1"),blnEnabled);
}

We still need to persist these changes to the server. We can simply enable/disable the validator in the page load:

this.RequiredFieldValidator1.Enabled = this.ChkEnable.Checked;

In summary, ASP.Net provides easy ways to disable a validator based on client activity. This lets us extend the default validator functionality.

Monday, May 23, 2005

Debugging JavaScript in Visual Studio

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/debugging_javascript_in_visual_studio.htm]

Everyone knows that you can use Visual Studio to debug Class Libraries, such as server-side WebForms. However, you can also use it to debug client-side JavaScript. I've seen some developers who don't realize this because they open up the JS as if it were a class file, add the breakpoint, run the application, and never see the breakpoint hit.

You need to first enable script debugging in your browser (list quoted from MSDN):

  1. In Internet Explorer, click the Tools menu and choose Internet Options.
  2. Click the Advanced tab.
  3. Under the Browsing category, clear the Disable Script Debugging checkbox.

Then any JS file will appear in the Running Documents window. You can open the file from there, add the breakpoint, and step through the script.