Xerratus
Happily stressed out, since 1974


 
Thursday, October 26, 2006

While writing an email to a colleague today, Outlook 2003 underlined a passage and offered a correction.  The correction it offered didn't sound right but I clicked on it anyway to see if it looked better corrected.  It didn't.  But what I noticed next is that it again underlined the same passage and offered a correction, this time back to what it was originally.  Thinking it knows best (yeah, right), I clicked on it again and guess what?  It corrected back to the original passage and offered a "suggestion".

Original passage that "needed" correcting:



Corrected passage that also "needed" correcting:



The passage corrected back to the original:



And so I found the anomaly: The correction that corrected itself with a correction that needed to be corrected back to the original correction.
Wednesday, October 25, 2006

We've all been there.  We develop a web application that uses the querystring to pass data from a master list page to a detail page.  Usually just the ID is passed (just a good programming technique) and you even check and double check the value and type of data so that only the user who is supposed to see it, sees it.  But the temptation to change the id for the user is too great and nobody ever likes seeing any type of identifier in the querystring.

The solution I've developed simply encodes the querystring name/value pairs so that it is unreadable but can be retrieved and created with relative ease. 

Note: This is solution does not encrypt/decrypt the values.  Rather it uses Base64 encoding to render the string unreadable.  It is possible for somebody with knowledge to hack this but you're a good programmer... and you've solved for this, just as you did if a user simply changed the ID; right?

So back to the solution:

First, we need to create a simple class to encode and decode Base64 values. 

public sealed class Base64
{
    
public static string Encode(string data)
    {
        
try
        {
            
byte[] encData_byte = new byte[data.Length];
            encData_byte =
Encoding.UTF8.GetBytes(data);
            
string encodedData = Convert.ToBase64String(encData_byte);
            
return encodedData;
        }
        
catch(Exception e)
        {
            
throw new Exception("Error in base64Encode " + e.Message);
        }
    }

    
public static string Decode(string data)
    {
        
try
        {
            
UTF8Encoding encoder = new UTF8Encoding();
            
Decoder utf8Decode = encoder.GetDecoder();

            
byte[] todecode_byte = Convert.FromBase64String(data);
            
int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
            
char[] decoded_char = new char[charCount];
            utf8Decode.GetChars(todecode_byte,
0, todecode_byte.Length, decoded_char, 0);
            
string result = new String(decoded_char);
            
return result;
        }
        
catch(Exception e)
        {
            
throw new Exception("Error in base64Decode " + e.Message);
        }
    }
}

Next, we create a QueryString class and give it an indexer to easily retrieve the values using an ordinal.  The getter returns null if the ordinal is empty, the querystring is not found or if the ordinal does not match up with any items found.  If Request.QueryString["q"] is found, we decode the string, split the pairs, loop thru each pair splitting the name/value out, and match on the ordinal.  If a match is found, we simply return the value of the item back.

public string this[string ordinal]
{
    
get
    {
        
if(ordinal == "")
        {
            
return null;
        }

        
if(HttpContext.Current.Request.QueryString["q"] == null)
        {
            
return null;
        }

        
// decodes to name/value pairs: id=1&something=what&test=true
        string decode;
        
try
        {
            decode =
Base64.Decode(HttpContext.Current.Request.QueryString["q"]);
        }
        
catch(Exception)
        {
            
return null;
        }

        
string[] pairs = decode.Split('&');

        
foreach(string pair in pairs)
        {
            
string[] item = pair.Split('=');

            
if(item[0].ToLower() == ordinal.ToLower())
            {
                
return item[1];
            }
        }

        
return null;
    }
}

For encoding, we supply a simple encode() method.

public string EncodePairs(string data)
{
    
if(data == "")
    {
        
return "";
    }

    
return Base64.Encode(data);
}

Now, for the UI:

To retrieve the querystring items, create an instance of QueryString (I chose to make it a disposable object so I could use the using pattern), check to see if the ordinal exists.  If it does, cast it to whatever datatype your system calls for.

// Retrieve the querystring values
using(QueryString q = new QueryString())
{
    IdLabel.Text = (q[
"id"] != null) ? q["id"] : "";
    NameLabel.Text = (q[
"name"] != null) ? q["name"] : "";
    ActiveLabel.Text = (q[
"active"] != null) ? q["active"] : "";
}

To encode pairs, build up the querystring like you normally would (leaving off the starting "?") and use the encode() method to encode it.  Then, append it to whatever page you are redirecting to as q=encodedstring. 

string pairs = String.Format("id={0}&name={1}&active={2}", IdTextBox.Text, NameTextBox.Text, ActiveRadioButtonList.SelectedValue);
string encoded;

// Set the querystring values
using(QueryString q = new QueryString())
{
    encoded = q.EncodePairs(pairs);
}

Response.Redirect(
"~/Default.aspx?q=" + encoded);

This is just a quick demonstration how one can use encoding to limit tampering when passing querystring data to another page.  There are many ways to achieve this without even using a querystring but for those instances where you either have to or want to, you now have another option. 

One last note:  This can be taken one step further if encryption is needed.  Just extend the QueryString class to encrypt/decrypt rather than encode/decode.  There are risks there as well but that's for you to decide.

Download the solution: QueryStringEncoding.zip (4.13 KB)
Friday, October 20, 2006

One of the subtle differences I've found while working with web projects in Visual Studio 2005 is the Global.asax code is placed in script tags within the actual .asax page, not a codebehind file, as it was with .NET 1.1.  This article describes how to set up the Global.asax file to utilize the Global.asax.cs codebehind file.

First off, go to files->add new and select the "Global Application Class" file.  You'll notice right off the bat that the option to put the code in a separate file is grayed out.  Ignore this and click "Add".

Next, go back to files->add new and select a "Class" file.  Type in the name "Global.asax.cs" and click "Add".  Visual Studio will prompt you to add this file to the App_Code directory, select "Yes" and continue. 

Now that the files are in place, we need to do some slight manipulation of the existing code to get this solution to compile.

To start, the Global class in the Global.asax.cs file needs to implement HttpApplication.

public class Global : System.Web.HttpApplication

Next, move the code within the script tags from the Global.asax file to the Global.asax.cs file -within the Global class.

void Application_Start(object sender, EventArgs e)
{
    
// Code that runs on application startup
}

...snip...

void Session_End(object sender, EventArgs e)
{
    
// Code that runs when a session ends.
}

Once the code has been moved, remove the script tags from Global.asax:

<script runat="server">
</script>

Add the following to the Application directive at the top of the same page:

CodeBehind="Global.asax.cs" Inherits="Global"

The entire contents of the Global.asax file should look like:

<%@ Application Language="C#" CodeBehind="Global.asax.cs" Inherits="Global" %>

The solution should compile and the Global events should fire correctly as well.

Download the solution: GlobalCodeBehindWebSiteExample.zip (2.25 KB)

Wednesday, October 18, 2006

All of the ASP.NET web projects that I've ever worked on relied on some appSetting within the web.config file.  When ever the variable was needed, some code was used to grab the variable in question than modify the type as needed or used simple string comparisons.  The problem I found early on working on a small team was that every member had their own twist on grabbing and using the data from within the web.config file.  While no method was wrong, the differences in using the same variable degraded their integrity.  

My solution was to encapsulate the appSettings in a static class that returned the variables as properties of the correct type.  This encapsulation also isolated the place where all appSettings were obtained making changes much easier while, at the same time, normalizing their usage.  It takes a few minutes longer of up front work when adding new settings but the ease of usage diminishes that ten-fold.  

Another plus is that a missing setting won't throw an error since we check for it's existence before we actually try and cast the value.  In my experience, not all developers check first, they assume it'll be there -bad idea.  

Note: The below example is utilizing .NET 2.0 but the same results can be obtained using 1.1 by simply changing ConfigurationManager.AppSettings[""] to ConfigurationSettings.AppSettings[""].

First off, here are a few differnet appSettings that have varying usages and types:

<add key="PromoExpiration" value="11/1/2006"/>
<
add key="IntroText" value="This is some random text ..."/>
<
add key="DisplayExtra" value="true"/>
<
add key="LoopCount" value="16"/>

Next I created a class called ConfigController where the getters for the above settings are obtained.  Here are two examples; the first demonstrates a bool while the second shows an int data type:

public static bool DisplayExtra
{
    
get
    {
        
if(ConfigurationManager.AppSettings["DisplayExtra"] != null)
        {
            
return bool.Parse(ConfigurationManager.AppSettings["DisplayExtra"]);
        }
        
else
        {
            
return false;
        }
    }
}

public static int LoopCount
{
    
get
    {
        
if(ConfigurationManager.AppSettings["LoopCount"] != null)
        {
            
return int.Parse(ConfigurationManager.AppSettings["LoopCount"]);
        }
        
else
        {
            
return -1;
        }
    }
}

To further ensure that no errors are thrown, a try catch block can be used when casting the data.  In my opinion it's overkill but when you work on a project where the client can and does edit the web.config file outside of your control, exception handling might be in order.

if(ConfigurationManager.AppSettings["PromoExpiration"] != null)
{
    
try
    {
        
return DateTime.Parse(ConfigurationManager.AppSettings["PromoExpiration"]);
    }
    
catch(Exception)
    {
        
return DateTime.MinValue;
    }
}
else
{
    
return DateTime.MinValue;
}

Once the controller is in place, it's usage is as simple as:

PromoPanel.Visible = (DateTime.Now < ConfigController.PromoExpiration);

IntroLabel.Text =
ConfigController.IntroText;

ExtraPanel.Visible =
ConfigController.DisplayExtra;

for(int i = 0; i < ConfigController.LoopCount; i++)
{
    
Literal l = new Literal();
    l.Text =
String.Format("Count: {0}<br />", i);

    LoopPlaceHolder.Controls.Add(l);
}

In summary, keep access to the web.config settings in one place and return the resulting values using their respective types to ensure proper usage throughout your solution.

Download the solution: ConfigControllerWebSiteExample.zip (5.3 KB)
Monday, October 16, 2006

While installing Microsoft SQL Server Management Studio Express I was left with the puzzling dilemma below:

Am I OK with what is about to happen?  I'm not sure, but I don't really have a choice now do I.