Sunday, October 23, 2005

CodeSmith CodeGeneration: Super XSLT

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

It's very common to have an XML data file, and then based on that to generate something else. Several years ago EXtensible Stylesheet Language Transformation (XSLT) was developed to handle this. The intent is that given an initial file, XSLT lets you program a template to transform that file into something else. For example, you could store just the raw data in an Xml file, and then have an XSLT template transform that into an html file. This new file is usually bigger and more redundant because it's doing something with the data, like adding extra html to format and present it. Another example would be storing base data in an Xml config file, and then transforming it to a lengthy SQL script.

The problem is that XSLT requires a entirely new skillset, isn't as powerful as a full-fledged language (like C#), and lacks the supporting development tools and IDE that make writing and debugging easier. You could use C# and the System.Xml namespace to manually transform the file, but that's tedious.

Another solution is to use a code-generation tool like CodeSmith. CodeSmith can take an Xml document, and given its XSD Schema, create a strongly typed object (which includes intellisense and collection capabilities). You can program a template in the CodeSmith IDE using C#. It's essentially like Super XSLT. By letting you use a language that you already know (either C# or VB), creating the strongly typed object, and using a standard IDE with debugging, using CodeSmith for batch (i.e. compile time) xml transformations can be a real time saver. It's also probably much easier for other developers to read and maintain a C#-based template than an XSLT one.

Tuesday, October 18, 2005

Validation Tips for Validators

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

ASP.Net provides a convenient, pre-built validation system. It consists of individual validation controls (like required fields), custom validators, potentially your own controls derived from BaseValidator, and a summary control to tie it all together

Sometimes we want to force, or bypass, validation on the client. We can do this by calling ASP.Net's own auto-generated functions. Drag a Required Validator onto a WebForm, run it, view the page source, and you'll see some javascript auto-generated by ASP.Net. This includes both a Page_ClientValidate and __doPostBack function.

GoalExplanationHow to do it
Force validation on the client Be able to check as much at the client before posting back. call Page_ClientValidate()
Bypass client validation checksBe able to have certain controls bypass validation checks. For example you may two unrelated sections on a page, and you'll want to click a "calculate" or "search" button on the first section without triggering the unrelated validators on the other section.call postBack: __doPostBack('','')

Also keep in mind as you develop your validation system:

  • Validators (including custom) don't fire if their ControlToValidate fields are empty (except for RequiredValidator itself).
  • Recall that disabled html fields do not persist their value to the server. This may throw off validators on fields that the user can disable at run time.
  • You can debug you client side validators, as they are just JavaScript.

ASP.Net provides custom validators. These are great for fine-tuning your validation. Some tips to keep in mind:

  • You can embed extra attributes in the validator's innerHtml, and then reference them in the client & server functions. This is useful to make validators reusable (control independent).
  • You can omit the controlToValidate property to ensure that validation only fires on postback as opposed to when focus leaves that individual control. This is very useful when validating groups of controls.
  • You can use the methods in WebUIValidation.js to access and compare values.
  • You need both client and server validation functions.

Here's a sample custom validator:

Client Code - Validator Html:

<asp:CustomValidator id="CustomValidator1" runat="server" ErrorMessage="CustomValidator"
MaxLength="10"
ControlToValidate="File1"
ClientValidationFunction="ValidateMaxLength"
OnServerValidate="ValidateMaxLength">
asp:CustomValidator
>

Here we added the MaxLength attribute. We can reference it in both the client and server validation functions.

Client Code - JavaScript validation function:

    <script language="javascript">

       function ValidateMaxLength(source, arguments)
       {
              var intMaxLength = source.MaxLength;
              if (arguments.Value.length > intMaxLength)
                  arguments.IsValid = false;
              else
                  arguments.IsValid = true;
       }
    script>

Server Code - validation function:

    protected void ValidateMaxLength (object source,    ServerValidateEventArgs value)     {      try       {        int intMaxLength = Convert.ToInt32(        ((System.Web.UI.WebControls.CustomValidator)source        ).Attributes["MaxLength"] );        if (value.Value.Length > intMaxLength)          value.IsValid = false;        else          value.IsValid = true;      }      catch       {        value.IsValid = false;      }    }

Thursday, October 13, 2005

Trigger a page method from a User Control

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

[UPDATE 11/20/2006] - New post with code sample

User controls let you group UI-functionality together into a resuable control. For example, headers, footers, menus, and groups of controls like userId-passwords and addresses all make good user controls. Effective components need to both pass data in (i.e. be able to have the page set the values of the user control), and pass data out (be able to have the page retrieve the values from fields in the UserControl). Besides just passing data, we also want to be able to trigger events. For example you could click a button on a UserControl or Page, and that triggers a method from the UC. But how do you do it the other way - click a button on the user control and have it trigger a method on the page. There are several ways.

  • One way of using delegates, as mentioned in this article I wrote back in early 2004 for 4GuysFromRolla.

However, I have since seen better ways, and I would suggest either:

The last two links are from MSDN, and therefore have code samples in both VB.Net and C#.

Thursday, October 6, 2005

Rapid Development with the XmlSerializer

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

Sometimes you'll want to save your business objects to an xml file. All the Xml parsing and manipulation can be tedious, but the .Net Framework provides a great class to save us from this: XmlSerializer. This class serializes objects (i.e. their internal state, like property values) to and from Xml. You could add methods to your objects like WriteToXml and ReadFromXml to encapsulate this logic. For example, the following employee class has a few methods, and the Xml Serialization methods:

    public class Employee
    {
        public Employee()
        {
        }

    #region Public Properties

        private string _strName;
        public string Name
        {
            get {return _strName;}
            set {_strName=value;}
        }

        private int _intId;
        public int EmployeeId
        {
            get {return _intId;}
            set {_intId=value;}
        }

    #endregion

    #region Public Methods

        public bool AddOrder()
        {
            return true;
        } //end of method

 
    #endregion

    #region Xml Serialization

    public void WriteToXml(string strPath)
    {
      System.Xml.Serialization.XmlSerializer x = new XmlSerializer(typeof(Employee));
      System.IO.StreamWriter sw = null;

      try
      {
        sw = new StreamWriter(strPath);
        x.Serialize(sw,this);
      }
      finally
      {
        if (sw != null)
          sw.Close();
      }
    }

    public static Employee ReadFromXml(string strPath)
    {
      System.Xml.Serialization.XmlSerializer x = new XmlSerializer(typeof(Employee));
      System.IO.StreamReader sr = null;

      try
      {
        sr = new StreamReader(strPath);
        Employee e = (Employee)x.Deserialize(sr);
        return e;
      }
      finally
      {
        if (sr != null)
          sr.Close();
      }
    }

    #endregion

    } //end of class

Both methods require a physical file path. The Write is an instance method that returns void (it writes the current state of the object). The Read is a static method that returns an instance of the type.

Serializing this to Xml produces the following file. Notice how the values of the properties are automatically handled. While this example is a direct mapping of object properties to xml nodes, XmlSerializer provide attributes for more advanced features.

xml version="1.0" encoding="utf-8"?>
<
Employee
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Name>TimName>
  <EmployeeId>3EmployeeId>

Employee>

We can also verify the read-write integration with the following test. First we create an object, then we save it to Xml, then we read it into another object, and finally compare the properties.

    [Test] public void SaveToXml()
    {
      string strPath = @"C:\temp\emp.xml";

      Employee e= new Employee();
      e.EmployeeId = 3;
      e.Name = "Tim";

      //Save
      e.WriteToXml(strPath);

      //Get
      Employee e2 = Employee.ReadFromXml(strPath);

      Assert.AreEqual(e.EmployeeId,e2.EmployeeId);
      Assert.AreEqual(e.Name,e2.Name);

    }

XmlSerializer makes this easy, and is a useful concept to have.

Tuesday, October 4, 2005

CSS Tip: Scrolling inline tables

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

A common UI problem is displaying a large amount of information in only a little space. Different user requirements allow for different solutions. For example, perhaps the user has 50 items, but only needs to see 10 at a time. The entire list is still small enough that you can bring all the data to the client - don't need to page through it with a previous/next button doing server hits. Technically, this could be implemented by putting all 50 items in a sub-page, and then displaying that sub-page in the main page as an inline frame. This would fulfill the user requirements:

  • You could set the frame size to be only 10 items.
  • All the data is still at the client - so it's quick
  • The user can conveniently scroll through the list.

While this functionally works, it's technically tedious. Having a separate sub-page means extra JavaScript to pass values between the main and sub page (such as when you save and you want to see the item selected). It also means one more page to keep track of. An alternative solution is to use CSS to inset the table within a div region:

  <body MS_POSITIONING="FlowLayout">
    <form id="Form1" method="post" runat="server">
      <P>This is an inline table test:P>
      <P>
      P>
      <table width="100%">
        <tbody>
        <tr>
          <td width="50%">
                <div style="BORDER-RIGHT: 1px inset; BORDER-TOP: 1px inset; Z-INDEX: 1; OVERFLOW: auto; BORDER-LEFT: 1px inset; BORDER-BOTTOM: 1px inset; HEIGHT: 100px">
        <TABLE id="Table1" cellSpacing="1" cellPadding="1" width="100%" border="1">
          <tbody>
          <TR>
            <TD>aaaaaTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>bbbbbTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>cccccTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>dddddTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>eeeeeeeeeeTD>
            <TD>bbbTD>
          TR>
          <TR>
            <TD>fffffffffTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>gggggggggTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>hhhhhhTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>iiiiiiiiTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>jjjjjjjTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>kkkkkkkkkTD>
            <TD>bbbTD>
          TR>
                    <TR>
            <TD>llllllllllllTD>
            <TD>bbbTD>
          TR>
        tbody>
        TABLE>
      div>
          td>
          <td>Other stufftd>
        tr>
      tbody>
      table>

      <P>P>
    form>
  body>

This technique is quick to develop, has the same user benefit, and is all around cleaner.

Sunday, October 2, 2005

Anchor onclick vs. Href

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

ASPX, including all its "special" features like postbacks and validators, ultimately renders as standard Html. So it's good to understand how certain Html components, like anchors, work.

A simple anchor just has an target page in the Href, look like so:

AnchorLink

Clicking this anchor will always redirect to PageB.aspx. The Href property can also contain a method to call. For example, ASP.Net renders LinkButtons as anchors whose Href calls the postback method:

href="javascript:__doPostBack('LinkButton1','')">LinkButton

Anchors can also have an onclick event. You could have this method call a message box, set hidden fields, or do whatever else you needed. However, the Href will not be called if the onclick returns false. For example, you could create a "Do you want to save?" yes-no MessageBox like so:

AnchorLink

The JavaScript confirm() method displays a yes-no confirm box, and returns either true or false depending on what the user clicked. Therefore if the user clicks no, the messageBox returns false, and the href is never activated. This concept applies whether the Href contains a navigation page or JavaScript. For example, you could add the confirm box to the LinkButton, and block the postback if the user answered "No" to a certain question.

Knowing the relationship between the anchor onclick and href lets you develop a more interactive and rich UI.

Thursday, September 29, 2005

Creating an Excel Reader utility class

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

Yesterday I created a Cell data structure to help read and store a single cell from Excel. Now let's use that to build an ExcelReader utility class.

Let's first think about our goals:

  • The Reader should be able to get specific values from Excel given the path to the workbook, and worksheet index.
  • In Excel, cells can have formulas that produce results. For example, the cell formula may be "= 1 + 1", but its result - what you see if you print the sheet - would be '2'. We want to be able to get both the value (formula) and text (displayed to user).
  • Reading the sheet will be slow, so we want a way to bundle the retrieval of ranges into a single call that gets a 2D array of object values. For example, given the upperLeft cell A1, and the lowerRight cell C2, return a 2x3 object array, for a total of six values. Such a feature would let us quickly read large portions of the sheet.

Disclaimer: I have only used this for personal pet projects. I have not performance tested it for production.

Given that, here's the code for our ExcelReader:

using System;
using Microsoft.Office.Core;
using System.Reflection;

namespace ExcelUtilities
{
    ///


    /// Summary description for ExcelReader.
    ///

    public class ExcelReader : IDisposable
    {
        public ExcelReader(string strWorkBookPath)
        {
            _app = new Excel.ApplicationClass();
            _book = null;
            _sheet = null;

            //speed up performance
            _app.Visible = false;
            _app.ScreenUpdating = false;
            _app.DisplayAlerts = false;

      if (!System.IO.Path.IsPathRooted(strWorkBookPath))
      {
        //root off current directory
        string strRoot = Assembly.GetExecutingAssembly().Location;
        strRoot = System.IO.Path.GetDirectoryName(strRoot);
        strWorkBookPath = strRoot + @"\" + strWorkBookPath;
      }

      if (!System.IO.File.Exists(strWorkBookPath))
        throw new System.ArgumentException("
The file path for the Excel book did not exist: '" + strWorkBookPath + "'.");

            _book = _app.Workbooks.Open(strWorkBookPath,
                Missing.Value, Missing.Value, Missing.Value,
                Missing.Value, Missing.Value, Missing.Value, Missing.Value,
                Missing.Value, Missing.Value, Missing.Value, Missing.Value,
                Missing.Value, Missing.Value, Missing.Value);
        }

        private Excel.ApplicationClass _app;
        private Excel.Workbook _book;   
        private Excel.Worksheet _sheet;
        private Excel.Range _range;

        private int _intCurrentSheet = 1;

        public int CurrentSheet
        {
            get
            {
                return _intCurrentSheet;
            }
            set
            {
                if (_intCurrentSheet < 1)
                    throw new ArgumentOutOfRangeException("
Sheet must be a positive number.");
                _intCurrentSheet = value;
            }
        }

        ///

        ///        Where cell is of the form "
A1"
        ///

        ///
        ///
        public object GetCell(Cell c)
        {
            return GetRange(c,c)[0,0];
        }

    public string GetCellText(Cell c)
    {
      _sheet = (Excel.Worksheet)_book.Worksheets[this.CurrentSheet];

      //get values
      _range = _sheet.get_Range(c.A1Reference,c.A1Reference);

      object o =  _range.Text;
     
      if (o == null)
        return String.Empty;
      else
        return o.ToString().Trim();
    }

        public object[,] GetRange(Cell cUpperLeft, Cell cLowerRight)
        {
            _sheet = (Excel.Worksheet)_book.Worksheets[this.CurrentSheet];

            //get values
            _range = _sheet.get_Range(cUpperLeft.A1Reference,cLowerRight.A1Reference);

            object[,] aaobj = null;
            if (_range.Count == 1)
            {
                aaobj = new object[1,1];
                aaobj[0,0] = _range.Value2;
            }
            else
                aaobj = (object[,])_range.Value2;

            return aaobj;
        }

        #region IDisposable Members

        public void Dispose()
        {
            //cleanup
            _range = null;
            _sheet = null;

            if (_book != null)
                _book.Close(false, Missing.Value, Missing.Value);
            _book = null;
            if (_app != null)
                _app.Quit();
            _app = null;
        }

        #endregion
    }
}

The constructor takes the necessary parameters to initialize the "connection" to Excel. Instance members then use this info to retrieve data.

Note that due to the heavy resources associated to read Excel, we want to implement IDisposable, such that callers can run it in a "using" context like so:

        [Test] public void GetCell_Empty()
        {
            using (ExcelReader er = new ExcelReader(_strPath))
            {
                er.CurrentSheet = 2;
                object o = er.GetCell(new Cell("X",7));
                Assert.IsNull(o);
            }
        }

We use our Cell object from the previous post to ensure that we're only requesting valid cells. If a user directly accessed the sheet, requesting cell '-1a$', it would be an error. Our Cell object can't even be created with these bad inputs, and hence relieves the ExcelReader from this validation burden. It's also quicker for the program to catch the bad input when trying to instantiate an in-memory Cell class than requesting an erroneous value from the Excel workbook.

One big question: How to test this? You could create a real Excel Workbook, include it as an embedded resource, and then have your tests hit that workbook. This would let you test all the boundary conditions, like:

  • Making sure that our methods distinguish between value (like the formula '=1 + 1') and text (its result, like '2')
  • The GetCell methods handle ranges of input
  • The ExcelReader handles both absolute and relative paths.

This series has provided us several tips to use .Net to enhance Excel functionality. We can call a .Net console app from Excel, passing in whatever command-line values we need, and then have that .Net console app read the Excel book to do complex functionality, such as creating an Html Report, sending data to the database, transforming the data to an Xml file, sending the data over an Xml Web Service, or anything else the .Net Framework lets us do.