Thursday, 16 December 2010

[ASP.NET] Pass your own arguments to the ClientValidationFunction in a CustomValidator

When using a CustomValidator in asp.net you can specify a ClientValidationFunction to run on your client side. This function need to have 2 parameters (sender, args). The sender is the customvalidator control and the args is where you can specify the result of your validation(args.IsValid) and also access to the value of the control attached to you custom validator(args.Value). This is very useful for more complex validation on client side.

http://alejandrobog.wordpress.com/2009/09/27/pass-your-own-arguments-to-the-clientvalidationfunction-in-a-customvalidator/

[.NET] Determining File Mime Types (Correctly!)

If you have used an ASP.NET upload control, you may have been reliant on getting the ContentType property and making security decisions based on the value from that. However, this isn't truly accurate.
What .NET does here is take the uploaded file and look at the file extension. It will then feed the extension into a mime library and generate the relevant content type.

I.e.
Upload a .doc file - ContentType will read: "application/msword"

For this to fail, we could rename an executable file (.exe) to a .doc and upload it. The ContentType will still be application/msword as it's based off the extension.

To overcome this problem, there are two methods we can use. Both of them involve peeking into the file and fetching some data relevant to it's type. So there is a natural trade-off with performance vs. security.

The first example below uses InterOp, and gives us an accurate mime type for the file. The second example uses feature codes, the only down side to using this is where you wish to accept doc and ppt but not xls. This isn't possible as these all share the same feature code (see below).



Two Examples to make decisions based on file/mime type
[System.Runtime.InteropServices.DllImport("urlmon.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode, ExactSpelling = true, SetLastError = false)]
static extern int FindMimeFromData(IntPtr pBC,
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string pwzUrl,
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPArray, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I1, SizeParamIndex = 3)] 
byte[] pBuffer, int cbSize,
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string pwzMimeProposed, int dwMimeFlags, out IntPtr ppwzMimeOut, int dwReserved);
 
/// <summary>
/// Ensures that file exists and retrieves the mime type
/// </summary>
/// <param name="fileName">Full path to the file</param>
/// <returns>Returns the Mime Type</returns>
public string GetMimeFromFile(string fileName)
{
    IntPtr mimeout;
    if (!System.IO.File.Exists(fileName))
    throw new System.IO.FileNotFoundException(fileName + " not found");
 
    int MaxContent = (int)new System.IO.FileInfo(fileName).Length;
    if (MaxContent > 4096) MaxContent = 4096;
    System.IO.FileStream fs = System.IO.File.OpenRead(fileName);
 
    byte[] buf = new byte[MaxContent];
    fs.Read(buf, 0, MaxContent);
    fs.Close();
    int result = FindMimeFromData(IntPtr.Zero, fileName, buf, MaxContent, null, 0, out mimeout, 0);
 
    if (result != 0)
    throw System.Runtime.InteropServices.Marshal.GetExceptionForHR(result);
 
    string mime = System.Runtime.InteropServices.Marshal.PtrToStringUni(mimeout);
    System.Runtime.InteropServices.Marshal.FreeCoTaskMem(mimeout);
 
    return mime;
}
 




/// <summary>
/// Reads the file to determine the feature code.
///     /* Extension name specification [Feature codes]
///     *7173        gif  
///     *255216      jpg 
///     *13780       png 
///     *6677        bmp 
///     *239187      txt,aspx,asp,sql 
///     *208207      xls,doc,ppt 
///     *6063        xml 
///     *6033        htm,html 
///     *4742        js 
///     *8075        xlsx,docx,pptx,mmap,zip,msi
///     *8297        rar
///     *01          accdb,mdb 
///     *7790        exe,dll
///     *5666        psd  
///     *255254      rdp  
///     *10056       bt  
///     *64101       bat
///     *3780        pdf
///     */
/// </summary>
/// <param name="hifile">Posted file following the upload process</param>
/// <returns>Returns weather the file type is allowed</returns>
public bool IsAllowedExtension(HttpPostedFile hifile)
{
    bool ret = false;
 
    System.IO.BinaryReader r = new System.IO.BinaryReader(hifile.InputStream);
    string fileclass = string.Empty;
    byte buffer;
 
    try
    {
    buffer = r.ReadByte();
    fileclass = buffer.ToString();
    buffer = r.ReadByte();
    fileclass += buffer.ToString();
    }
    catch
    {
    return false;
    }
    r.Close();
 
    String[] fileType = {"208207", "8075", "3780"}; // Place allowed file types here
 
    for (int i = 0; i < fileType.Length; i++)
    {
    if (fileclass == fileType[i])
    {
        ret = true;
        break;
    }
    }
 
    return ret;
}

Friday, 26 November 2010

AJAX - ASP.NET : Call WebMethod defined in User Control

If you have welcomed the joys of AJAX into your ASP.NET application, you may or may not have come across this problem.

With AJAX calls, I tend to store the WebMethod within the ASPX pages rather than services; so that I can keep my logic relating to the page separate from everything else. There is nothing worse than having a global service with tens or hundreds of methods in that get called every time. (well maybe there is, but you see my point!)

However, where user controls are concerned, we have a slight problem. A limitation of the framework prevents us from calling a WebMethod defined in a User Control (as of .NET 3.5). Some people have suggested adding a proxy method to the page which hosts the user control to call a method within the user control. This is wrong on many levels as it ties the page to the control and defeats the purpose or re-usability with ease.

Solution
Instead of a page method, add a service method instead. Either an ASMX or an SVC, it doesn't really matter. These are useful to hosting WebMethods that can be reused by many pages within the application OR user controls (which are re-usable by design anyway).

Thursday, 25 November 2010

asp:menu - Fix submenu appearing as a white box [IE8+]

If you have an asp:menu control as part of your ASP.NET application, you will notice that the sub menu appears as a white box and your content isn't visible. This is quite crucial if this is the only access point to your web pages!

Solutions
1. Add z-index: 100; within a CSS class bound to the DynamicMenuItemStyle property on the menu control. This is the easiest fix. What IE8+ browsers are doing here is in theory correct, it more of a shortfall of the control. [Best Solution]

2. Use CSS friendly adapters. Requires a total re-design, why not take a look anyway.

3. Making your browser emulate IE7 by using the following meta tag in the appropriate pages.
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />


Note: The 3rd solution is not the best idea as it goes against the principles of moving forward with the technology!

SQL Server Management Studio - Edit Query Results

This is a useful way to edit results of queries in SQL Server Management Studio 2005 and 2008.

SQL Server 2005
1. Right click table
2. Open Table
3. Right click anywhere in the results > Pane > SQL
4. SQL query windows pops up, enter query here.

SQL Server 2008
1. Right click table
2. Edit Top 200 Rows
3. Right click anywhere in the results > Pane > SQL
4. SQL query windows pops up, enter query here.

XSS: Cross style scripting attacks in ASP.NET [Examples]

Recently I have been looking at vulnerabilities in ASP.NET applications where XSS attacks are concerned. In recent years, integration with ASP.NET and AJAX has become more apparent as it offers many benefits; including partial page refreshes. However, this opens our applications up to potential attacks, one of which is the main area of focus for this topic: XSS (Cross-style scripting).

Must Read: The XSS Wiki

When creating pages in ASP.NET, you may have stumbled upon the ValidateRequest property of the Page directive before. If not, it's a useful .NET feature that analyses page submissions for potentially unsafe mark up. You can test this yourself by entering <script>alert('Xss Vector!')</script> into a asp.net textbox control and hitting submit on the form. You will receive an exception informing you that the submission is potentially unsafe. Try setting this to false (default is true if not specified) and you'll see that this is no longer picked up by the framework on submit.

So why not leave this set to true for all pages, what's the problem?
1. User's may want to enter markup into a text area control for example.
2. AJAX calls to WebMethod's do not follow this process.
3. Sessions/Cookies/application variables can be hijacked that also do not follow this process.

The three reasons above present a clear reason of why we should add protection to our ASP.NET applications against XSS attacks. Especially as ASP.NET AJAX is becoming more and more common. We have three options for protection against these kinds of attacks.

1. Write your own utility to strip out potentially unsafe markup upon each potentially dangerous call.
2. Include the Microsoft Anti-Cross Site Scripting Library into your solution. This is the same as step 1, but without the "Write your own" bit. It's around 700kb (V1.5).
3. Do not make any of this calls where this becomes an issue [not realistic]


For steps 1 and 2... We need to analyse our code or potential code (I.e. in the design process!) where these issues arise. You refer to the 3 potentially unsafe areas above as a starting point as these area areas where XSS attacks can occur. This Microsoft page gives a good description on how to do this.

Once this areas are known, we can makes all inbound calls safe by stripping out or parsing incoming data using a bespoke tool or Microsoft's Anti-Cross Site Scripting Library.

Here is a typical example where we make an AJAX call to the server and we use Microsoft's library to parse the incoming data. The example also includes a vulnerable option so the two approaches can be compared.

The example can be downloaded here [Requires .NET 3.5]


Useful links:
XSS Cheat Sheet
Test your XSS Attack skills here!

Tuesday, 23 November 2010

ASP.NET/AJAX - Client-side validation with server validation controls

With web applications getting more and more complex, it becomes necessary to make them more per-formant. It is quite easy to create an ASP.NET web application and within little or no time, most of the logic exists on the server. This is all good and well for a simple application, but as complexity increases, we need to start handing process to the client where client-side operations need to exist.

One good example is validation. For example, there is no reason why we should have required field validation on the server (I.e. checking if a field exists in the code behind after submitting a form to the server). We can easily use a RequiredFieldValidator in this case, and this will be processed on the client-side.

Ok, now for more complex examples, and this is the main vocal point of this article... What if we are using a custom validator and we need to check if, for example; an email exists dynamically without submitting the whole form? The common thing developers do here is override the OnServerValidate property of a CustomValidator, then this will take care of things when the form is submitted. I am now going to discuss how we can avoid this....

The old method

- Requires whole page postback
- Less per-formant


The new method

- Partial page postback
- Asynchronous, can be performed on-the-fly (dynamically)


With the old method, you can see that we must submit the form to the server in order to use our server side validation method. Server side validation is useful if you wish to use a database for example (check if email exists), something that your Javascript cannot do alone.

With the new method, we override the ClientValidationFunction of the CustomValidator and set it to a javascript function. This javascript function then wraps up some data using JSON; this data refers to what we are validating (I.e. email address), then passes it to a WebMethod on the server. The server method will behave exactly like the previous OnServerValidate method previously, and pass back the results of our validation process. The CustomValidator will then use this to trigger an error if necessary.

I have attached the project files used in this example (written in ASP.NET 3.5). If you open the Javascript file, you can see that a page method is being referenced. You can also reference a service (.asmx) by uncommenting the line below in the script, and commenting out the page method reference. Both work exactly the same but present two alternate mechanisms.


Download the project files here



If the Web Service in different Namespace you can refer it before the class name this Main formula may help you :

NameSpaceName.ClassName.WebMethdName(Parameters , Success callback function, Error callback function);

Parameters: you can pass one or many parameters.

Success callback function :handles returned data from the service .

Error callback function :Any errors that occur when the Web Service is called will trigger in this function. Using Error Callback function is optional.


Note: In this example, I am using the $.ajax command of JQuery to call the WebMethod. So in the url section, we need to specify the method as a URL relative to where it's being called from.

I.e

+ javascript
--- callwebservice.js < the script calling the service
+ Services
--- service.asmx < the service being called


Then your url will be "../Services/service.asmx/NameOfTheWebMethod"

Tuesday, 9 November 2010

Multiple Versions of IE - With IE7, IE8 and IE9!!!

Here is a really good tool for emulating versions IE from 5.5 to 9. For those of you that have used a similar tool in the past (Multiple IE's) you'll notice that this has been discontinued and there are a few difficulties getting it to work with Windows Vista and Windows 7.

Here is a better tool, IETester
http://www.my-debugbar.com/wiki/IETester/HomePage

Wednesday, 3 November 2010

CSS: Corner Radius

http://www.the-art-of-web.com/css/border-radius/

Really good article!

Thursday, 21 October 2010

C#: Capture exception and all inner exceptions

With complex architectures and many layers in the code throwing exceptions, it can prove difficult to find out exactly what went wrong.

Generally, the GUI may produce an error for example "Payment could not be processed." when making a payment on-line. From this error, we don't know which layer caused this error, but we know that along the way, something occurred.

This code snippet takes an exception and loops through all inner exceptions and produces a string for the whole exception. This can be stored in a logfile and the problem can be easily identified.


/// <summary>
/// Gets a full exception message string from a given exception.
/// This will loop through all inner exceptions.
/// </summary>
/// <param name="ex">Incoming exception</param>
/// <returns>Full exception message</returns>
internal string GetFullExceptionMessage(Exception ex)
{
    string result = string.Empty;
 
    while (ex != null)
    {
    result += ex.ToString() + Environment.NewLine;
    ex = ex.InnerException;
    }
 
    return result;
}

Wednesday, 13 October 2010

Downgrading Adobe Flash Player

So I had an issue today where all of my SWF video tutorials did not work correctly after a certain version of the Flash Player. I originally used ViewletCam software to do this back in 2004; and the videos seemed to half at random intervals and then crash.

I needed a way to at least view these files locally, so that I could convert them to something more suitable. I had FLV in mind, and I could use these to load into many free web-embedded flash players.

This is what I did.

1. Uninstall the current version of flash. Adobe provides uninstallers for all versions. Current page to do this
2. test that the uninstall worked. This should either: not load or prompt you to install flash player (do not install here) Link here
3. Obtain an older copy of flash player. Adobe has them all Here or take a visit to OldVersions.com
4. Install the old flash player.
5. Repeat step 2... this should give you your new flash version.

Tuesday, 12 October 2010

CSS: Hacks in Google Chrome and Safari only...

Here's a nice snippet to use in CSS when it seems like all other browsers are displaying your content correctly besides browsers based on Apple WebKit (Chrome, Safari etc.)

An example of setting css properties in WebKit browsers
/* This works only in Safari and Google Chrome */
@media screen and (-webkit-min-device-pixel-ratio:0) {
    .classNameHere {
        margin: 0 0 1px 0;
        padding: 0px 3px 0px 3px;
    }
    #exampleBorderStyleHere {
        border:solid 1px #000000;
    }
}

Friday, 8 October 2010

ASP.NET/JQUERY - Maintain Scroll Position of DIV (example uses a treeview)

Today I had the task of maintaining the scroll position of an asp:TreeView control. Keeping it short and sweet; here's what I came up with.

I noticed that when rendered in the browser, the TreeView is rendered as a div... and from back in the days of remembering window positions after post backs, I knew this wouldn't be much different.

The solution I was working on used JQuery already and I found it useful to pull out references to server controls using the 'endwith' syntax. You can also get client id's, it doesn't matter to an extent.


Code behind
protected void Page_Load(object sender, EventArgs e)
{
    this.AddEvents();
}
 
private void AddEvents()
{
    ScriptManager.RegisterStartupScript(this, this.GetType(), "Load", "PageLoad();", true);
    this.tvOrganisation.Attributes.Add("onscroll", "saveScrollPosition(this)");
}


In the above code i'm attaching an onscroll event to the Treeview. I'm also adding a form load function in javascript so that I can initialise anything in here.


Page Markup
<script type="text/javascript" src="LOCATION OF JQUERY HERE/jquery-x.x.x.js"> </script>
<script type="text/javascript">
    function saveScrollPosition(div) {
        $("[id$='hdnTVScrollYPos']").val(div.scrollTop);
        $("[id$='hdnTVScrollXPos']").val(div.scrollLeft);
    }
 
    function restoreScrollPosition() {
        $("[id$='tvTREEVIEWNAME']").scrollTop($("[id$='hdnTVScrollYPos']").val());
        $("[id$='tvTREEVIEWNAME']").scrollLeft($("[id$='hdnTVScrollXPos']").val());
    }
 
 
    function PageLoad() {;
        restoreScrollPosition();
    }
</script>
 
 
<asp:HiddenField ID="hdnTVScrollXPos" runat="server" Value="0" />
<asp:HiddenField ID="hdnTVScrollYPos" runat="server" Value="0" />


Here i'm using two hidden fields to hold the values of X and Y. This will maintain throughout postback and uses a cookie-less approach towards maintaining these details. The JavaScript speaks for itself.

Monday, 4 October 2010

CSS: Cross Browser Gradients

As a Web Developer, my usual ethic is to immediately create an ingenious architecture and spill out reusable code within the framework I've just created; while my design aspects are crudely left to the side until I need a GUI to make the thing work!
In practise, it doesn't really matter which order these are done, just as long as the requirements are in place and there is a general idea.

However, when i'm at home (without the glorious support of a design team to make my images and write my CSS for me), things do get done, but designing pages with a div layout; bearing cross browser functionality in mind and actually making it look good takes a lot of skill. So here's one for the skillset....


Cross Browser Gradients with CSS
.gradientTitle {
    background-color: #CECEF6; /* Opera and browsers that do not support gradients  */
    background: -webkit-gradient(linear, left top, right bottom, from(#CECEF6), to(#FFFFFF)); /* Webkit browsers: Chrome, Safari etc. */
    background: -moz-linear-gradient(left,  #CECEF6,  #FFFFFF); /* Mozilla Firefox */
    filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr='#CECEF6', endColorstr='#FFFFFF'); /* IE 5.5+ */
    padding: 2px 2px 2px 8px;
    color: #1C1C1C;
    width: 60%;
}



I have set an initial background for browsers that do not support gradients. If a browser is capable of picking up one of these three, the background-color is overridden.


Preview

Here's a Sample Heading

Wednesday, 29 September 2010

Javascript: Namespaces and O-O examples...

I'm not sure how common practise this is, but where I currently work we have a standard whereby we placed common JavaScript functionality in separate files and what could be called classes. I'm sure most developers out there do group common functions in files and import them when needed. This is fine, but it doesn't promote certain O-O practises like information hiding and the feel of objects, methods and properties. This is what this post aims to achieve.

I have attached a sample project file which declares a JavaScript name space in a separate file, declares a Messages 'class' in another and a sample HTML page which invokes various methods and properties against it. Sample can be found at the end of this post.


Namespace declaration - Must be imported first
/******************************
** BANTY Namespace definition
******************************/
var BANTY = BANTY || {};
BANTY = (function() {
    return {
        helloWorld : function() {
            return "Helllo World!";
        }
    }
}());



Class definition
/******************************
** Define Messages class
*******************************/
if (!BANTY.Messages) BANTY.Messages = {};
BANTY.Messages = (function() {
 
    // private declarations here
    var pi_Message;
 
    var pi_ChangeMessage = function (newMsg) {
        pi_Message = newMsg;
    };
    
    
    // public declarations here
    return {
        publicProperty : "This is a public property",
        
        init : function (Args) {
            pi_Message = "Message initialised.";
        },
        
        showMessage : function () {
            return "The value of the message is: " + pi_Message;
        },
        
        changeMessage : function (msg) {
            pi_ChangeMessage(msg);
        },
    };
}());



Example HTML Page
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Javascript O-O Example</title>
    <script src="Banty.js" type="text/javascript"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript"></script>
</head>
<body style="font-family: verdana; font-size: 8pt">
    <div id="tagged"></div>
 
    <script src="BANTY.Messages.js" type="text/javascript"></script>
    <script type="text/javascript">
    $('#tagged').append('<br /><div><font color="0000FF"><b>Information hiding principles...</b></font></div>');
    $('#tagged').append('<div>Can I see BANTY?:<b> '+ (BANTY ? 'Yes' : 'No') +'</b></div>');
    $('#tagged').append('<div>Can I see BANTY.helloWorld?:<b> '+ (BANTY ? 'Yes' : 'No') +'</b></div>');
    $('#tagged').append('<div>Can I see BANTY.Messages?:<b> '+ (BANTY.Messages ? 'Yes' : 'No') +'</b></div>');
    $('#tagged').append('<div>Can I see BANTY.Messages.publicProperty?:<b> '+ (BANTY.Messages.publicProperty ? 'Yes' : 'No') +'</b></div>');
    $('#tagged').append('<div>Can I see BANTY.Messages.showMessage?:<b> '+ (BANTY.Messages.showMessage ? 'Yes' : 'No') +'</b></div>');
    $('#tagged').append('<div>Can I see BANTY.Messages.pi_Message?:<b> '+ (BANTY.Messages.pi_Message ? 'Yes' : 'No') +'</b></div>');
    $('#tagged').append('<div>Can I see BANTY.Messages.pi_ChangeMessage?:<b> '+ (BANTY.Messages.pi_ChangeMessage ? 'Yes' : 'No') +'</b></div>');
 
 
    $('#tagged').append('<br /><div><font color="0000FF"><b>Show and Change Messages...</b></font></div>');
    $('#tagged').append('<div><b> BANTY.helloWorld:</b> '+ BANTY.helloWorld() +'</div>');
    $('#tagged').append('<div><b> BANTY.Messages.report():</b> '+ BANTY.Messages.showMessage() +'</div>');
    $('#tagged').append('<div><b> BANTY.Messages.init():</b> '+ BANTY.Messages.init() +'</div>');
    $('#tagged').append('<div><b> BANTY.Messages.report():</b> '+ BANTY.Messages.showMessage() +'</div>');
    $('#tagged').append('<div><b> BANTY.Messages.changeMessage(newmsg):</b> '+ BANTY.Messages.changeMessage("newmsg") +'</div>');
    $('#tagged').append('<div><b> BANTY.Messages.report():</b> '+ BANTY.Messages.showMessage() +'</div>');
    </script>
</body>
</html>



Example Output

Information hiding principles...
Can I see BANTY?: Yes
Can I see BANTY.helloWorld?: Yes
Can I see BANTY.Messages?: Yes
Can I see BANTY.Messages.publicProperty?: Yes
Can I see BANTY.Messages.showMessage?: Yes
Can I see BANTY.Messages.pi_Message?: No
Can I see BANTY.Messages.pi_ChangeMessage?: No

Show and Change Messages...
BANTY.helloWorld: Helllo World!
BANTY.Messages.report(): The value of the message is: undefined
BANTY.Messages.init(): undefined
BANTY.Messages.report(): The value of the message is: Message initialised.
BANTY.Messages.changeMessage(newmsg): undefined
BANTY.Messages.report(): The value of the message is: newmsg




Download Sample Files Here

Thanks go out to my company for putting this standard in place.

ASP.NET - Get IPv4 Address, even if the user is using a proxy or an intranet user

Today I was working on some PayPal integration for an internal web application when part of the API suggested I needed an IP Address for the user to enhance security.

Realising this was an intranet application which could be used externally, I had to cater for all aspects. Getting an IP Address for an internal users involves looking up the host addresses using the System.Net.Dns namespace, rather than a simple peek in a server variable. As well as this, I needed to make sure I could identify real IP Addresses for users behind a proxy. If that wasn;t enough, the PayPal API doesn't support IPv6, so I had to search the host list for a IPv4 address and use that.... PayPal requires an IP or it will fail, so if everything else fails, it will return the loopback address (ack!). here's what I came up with...

UPDATE - Feb 2013
I have provided a couple of updates to this code in light of the comments. Thanks for the input!

Code Snippet
  1.  
  2. // Invoker
  3. string IPAddress = IPHelper.GetIPAddress(Request.ServerVariables["HTTP_VIA"],
  4.                                                 Request.ServerVariables["HTTP_X_FORWARDED_FOR"],
  5.                                                 Request.ServerVariables["REMOTE_ADDR"]);
  6.  
  7.  
  8.  
  9. public class IPHelper
  10. {
  11.     /// <summary>
  12.     /// Gets the user's IP Address
  13.     /// </summary>
  14.     /// <param name="httpVia">HTTP_VIA Server variable</param>
  15.     /// <param name="httpXForwardedFor">HTTP_X_FORWARDED Server variable</param>
  16.     /// <param name="RemoteAddr">REMOTE_ADDR Server variable</param>
  17.     /// <returns>user's IP Address</returns>
  18.     public static string GetIPAddress(string HttpVia, string HttpXForwardedFor, string RemoteAddr)
  19.     {
  20.         // Use a default address if all else fails.
  21.         string result = "127.0.0.1";
  22.  
  23.         // Web user - if using proxy
  24.         string tempIP = string.Empty;
  25.         if (HttpVia != null)
  26.             tempIP = HttpXForwardedFor;
  27.         else // Web user - not using proxy or can't get the Client IP
  28.             tempIP = RemoteAddr;
  29.  
  30.         // If we can't get a V4 IP from the above, try host address list for internal users.
  31.         if (!IsIPV4(tempIP) || tempIP == "127.0.0.1 ")
  32.         {
  33.             try
  34.             {
  35.                 string hostName = System.Net.Dns.GetHostName();
  36.                 foreach (System.Net.IPAddress ip in System.Net.Dns.GetHostAddresses(hostName))
  37.                 {
  38.                     if (IsIPV4(ip))
  39.                     {
  40.                         result = ip.ToString();
  41.                         break;
  42.                     }
  43.                 }
  44.             }
  45.             catch { }
  46.         }
  47.         else
  48.         {
  49.             result = tempIP;
  50.         }
  51.  
  52.         return result;
  53.     }
  54.  
  55.     /// <summary>
  56.     /// Determines weather an IP Address is V4
  57.     /// </summary>
  58.     /// <param name="input">input string</param>
  59.     /// <returns>Is IPV4 True or False</returns>
  60.     private static bool IsIPV4(string input)
  61.     {
  62.         bool result = false;
  63.         System.Net.IPAddress address = null;
  64.  
  65.         if (System.Net.IPAddress.TryParse(input, out address))
  66.             result = IsIPV4(address);
  67.  
  68.         return result;
  69.     }
  70.  
  71.     /// <summary>
  72.     /// Determines weather an IP Address is V4
  73.     /// </summary>
  74.     /// <param name="address">input IP address</param>
  75.     /// <returns>Is IPV4 True or False</returns>
  76.     private static bool IsIPV4(System.Net.IPAddress address)
  77.     {
  78.         bool result = false;
  79.  
  80.         switch (address.AddressFamily)
  81.         {
  82.             case System.Net.Sockets.AddressFamily.InterNetwork:   // we have IPv4
  83.                 result = true;
  84.                 break;
  85.             case System.Net.Sockets.AddressFamily.InterNetworkV6: // we have IPv6
  86.                 break;
  87.             default:
  88.                 break;
  89.         }
  90.  
  91.         return result;
  92.     }
  93. }
End of Code Snippet

Thursday, 23 September 2010

asp:Menu controls not working in Chrome/Safari

I faced a small issue today with asp:Menu controls not working properly in Chrome or Safari the first time the page is loaded. However, if I refresh the page, the contents seem to load fine.
The underlying problem is the way Chrome manages it's adaptors and we need to be able to clear these out and refresh the page if this situation occurs.

I have therefore written an OnInit event for any ASP.NET page or user control in which the menu is placed. Simply just add it in the code behind...


Fix Menu rendering in Chrome and Safari
protected override void OnInit(EventArgs e)
{
    // For Chrome and Safari
    if (Request.UserAgent.IndexOf("AppleWebKit") > 0)
    {
        if (Request.Browser.Adapters.Count > 0)
        {
            Request.Browser.Adapters.Clear();
            Response.Redirect(Page.Request.Url.AbsoluteUri);
        }
    }
}



- Using the "AppleWebKit" we can identify these browsers
- By checking more than one adapter exists, we avoid re-directing every time; this only needs to be done on the first hit.
- We Re-Direct back to itself the first time the page is hit.

Wednesday, 22 September 2010

C#: Cultures

Cultures are things you probably won't come across for general projects unless you are dealing with support for multiple viewing countries. This mainly includes: Dates, times and currencies.

For example, When parsing (using Parse/TryParse) a DateTime object, it assumes the DateTime is in US format... so when we come to use this, we'll notice our days and months are the wrong way round!


Parsing a string in a DateTime object with a UK Culture
using System.Globalization;   // For access to culture objects
 
 
private DateTime ParseDate(string IncomingDate)
{
    CultureInfo ukCulture = new CultureInfo("en-GB");
    DateTime parsedDate = new DateTime();
 
    DateTime.TryParse(IncomingDate, ukCulture.DateTimeFormat, DateTimeStyles.None, out parsedDate);
 
    return parsedDate;
}



An implicit example
DateTime dateObj = DateTime.Parse(inputString, new CultureInfo("en-GB"));



We can also apply culture globally across a whole application by setting culture settings in the Web.config file. In the following snippet, we have Culture and UICulture. The former refers to how DateTime objects and currencies are parsed etc. Using a en-GB culture will ensure these all use UK currencies and formatting. The latter determines which resource files are used for the UI.


Setting the culture globally in the Web.config
<globalization uiCulture="en-GB" culture="en-GB" />

Monday, 20 September 2010

Sending HTML Emails and Emailing Tricks with C#

Sending emails is relatively easy with C#, even-so, its not much more difficult to send HTML based emails either. The formatting, and how different email clients interpret the HTML and CSS in the emails, differs slightly. The aims of this post is to present a few typical scenarios of different types of emails which are sent.

Sending a Simple email using an email server - non-HTML
using System.Net.Mail;
 
 
MailMessage mail = new MailMessage("from@email.com", "to@email.com", "Email Subject", "Email Message");
mail.IsBodyHtml = false;
this.SendMail(mail);
 
 
/// <summary>
/// Sends the mail using a mail message.
/// </summary>
/// <param name="Mail">The mail message.</param>
private void SendMail(MailMessage Mail)
{
     SmtpClient smtpClient = new SmtpClient();
     smtpClient.Host = "Email Server Address";
     smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
 
     try
     {
          smtpClient.Send(Mail);
     }
     catch (Exception ex)
     {
          throw new EmailException(ErrorCodeType.SendFailed, ex);
     }
}


This is enough to send a simple email to a target destination using an existing email server. To enable HTML is the body, we simply enable the IsBodyHtml property of the MailMessage object by setting it to true.


Simple HTML Email using the function in the previous example

MailMessage mail = new MailMessage("from@email.com", "to@email.com", "Email Subject", "<b>HELLO!!</b><br><br>Test Message...");

mail.IsBodyHtml = true;

this.SendMail(mail);



With OutLook for example, some representations of styling alter slightly. For example, I spent some time trying to understand why the line-height was interpreted differently in outlook. For whatever reason, If I tested the HTML template in IE, FF or Chrome; everything would be the same apart from the line-height. This seems to be a common problem. The solution I came up with is to tell outlook to render it in a certain way and that other browsers should ignore it.

HTML Email with line spacing and styles applied to the body of the email
string emailBody = "<div style=\"font-family: Verdana, Arial, sans-serif; font-size: 9pt; mso-line-height-rule:exactly; line-height: 20px\">";
emailBody += "<b>HELLO!!</b><br><br>Test Message...</div>";
 
MailMessage mail = new MailMessage("from@email.com", "to@email.com", "Email Subject", emailBody);
 
mail.IsBodyHtml = true;
 
this.SendMail(mail);


By setting "mso-line-height-rule:exactly" before the line-height property, we are tell mso (Microsoft Office) to render this differently to other browsers. You'll notice that I set the line-height to 20px... for a 9pt font, this is quite huge normally, but in Outlook; it's not.


One more tip, which may be quite trivial to some... If you want to using alias' in your emails (I.e. Appear that the email is being sent from an Alias) then there is no special code. Simply format the 'From' address in the following Format... "AliasName" (Include the quotes aswell)

In a config file, this might look like... [Remember we need to escape special characters in the XML config file!]
<appSettings>
    <add key="Email.FromAddress" value="&quot;WebAdmin&quot; &lt;online@tmgcrb.co.uk&gt;"/>
</appSettings>

Thursday, 16 September 2010

Forcibly clear a printer queue

Sometimes the printer queue just won't clear, no matter how many times you right click and try to delete the job. Here's how to actually clear it out properly!

1. Start > Run > Services.msc

2. Find "Printer Spooler" and stop the service

3. Go to "C:\Windows\System32\spool\PRINTERS" and you will see a list of files. These represent the jobs in the printer queue. Remove these.

4. Restart the "Printer Spooler" service and your queue should be cleared.

Monday, 13 September 2010

View Gradients/Colours properly in your Virtual Machine (VM)

• In the Windows XP operating system (remote system), click on Start menu, then Run.
• Type GPEdit.msc to open Group Policy Editor.
• Navigate to Local Computer Policy -> Computer Configuration -> Administrative Templates -> Windows Components -> Terminal Services.
• In the right pane, double-click on the Limit Maximum Color Depth setting.
• In the Properties dialog, select radio button of Enabled, and then set Color Depth value to 24 bit or Client Compatible

Friday, 10 September 2010

Code Snippit - Determining whether brackets are balanced

We usually have coding challenges at work quite often. This one was quite useful. The aims were to do something that regular expressions struggle doing; that is, determine whether brackets are balanced.

Example of a legal expression: “([as](<{}>))”.
Example of an illegal expression: “({<)>}”.

Here's what I came up with...



private bool IsValid(string str)
{
if (!string.IsNullOrEmpty(str))
{
List<char> LeftParenthesis = new List<char>() { '(', '{', '[', '<' };
List<char> RightParenthesis = new List<char>() { ')', '}', ']', '>' };
List<char> items = new List<char>(str.Length);

// Loop through each character
for (int i = 0; i < str.Length; i++)
{
char c = str[i];

if (LeftParenthesis.Contains(c))
{
items.Add(c); // Add each opening bracket
}
else if (RightParenthesis.Contains(c))
{
int rpIndex = RightParenthesis.IndexOf(c);

// Invalid if there are no open brackets and we find a closing one
int lastPairIndex = items.LastIndexOf(LeftParenthesis[rpIndex]);

if (lastPairIndex >= 0)
{
if (items[items.Count - 1] != items[lastPairIndex]) // Last item must be matching bracket
return false;
else
items.RemoveAt(lastPairIndex); // Remove last pair
}
else
return false;
}
}

// Look for any other brackets left over
if (items.Count > 0)
return false;
}

return true;
}

Monday, 23 August 2010

Wednesday, 30 June 2010

LINQ To SQL ERROR: An attempt was made to remove a relationship between a [Table1] and a [Table2].

Full Error
An attempt was made to remove a relationship between a [Table1] and a [Table2]. However, one of the relationship's foreign keys ([Table1].[Foreign Key]) cannot be set to null.


This error usually occurs when trying to called SubmitChanges() on the data context.
For example, I have received this error when trying to commit an object and I have not property build up its properties (one of which may be another table) and when trying to commit this parent object, its related table will be null; this throws an error.

So what you could do is...

1. Ensure the property is present (that is, if you want to commit it in one transaction).
2. Tell the framework to delete child items on submitting... Open the DBML file in Xml Editor (Right Click > Open With...) and find the association and tag "
DeleteOnNull="true" " to the end.
- Alternately, you can specify this in the code...
DataContextInstance.TableName.DeleteAllOnSubmit(items);
3. Remove the table reference as a property all together. Open the DBML file, click on the association and view the properties. Set 'Child Property' to 'False'.

Hope this helps!

Wednesday, 16 June 2010

iPhone SDK in Windows

Here's a good guide on getting the iPhone SDK working from a windows isntallation.

http://iphone-sdk-in-windows.co.uk/

Friday, 11 June 2010

CruiseControl.NET: 10 Useful Plugins [Written in C#.NET]

For the past two years I have developed various Continuous Integration systems and along the way, like most things, you collect a lot of information. Recently, I have managed to compile most of this into this solution and kept it up to date.

For the build process of our Continuous Integration, we can give CruiseControl.NET as much power as we desire. Most developers prefer to keep logic in Nant/MSBuild scripts, while some find themselves using CCNET plugins to manage a lot of the work.

In this solution attached, I have included all of the plugins I have developed and have used within some of my processes (I say some because the other use build scripts to perform similar actions). I have updated the solution to work with CCNET v1.5 [1.5.6804.1 to be specific].

If you don't have this particular version, you can either upgrade to it, or easier, copy the required files into the 'References' folder and recompile the plugins. The files required are:

- ThoughtWorks.CruiseControl.Core.dll
- NetReflector.dll
- Wix.dll

These can all be found in the 'server' directory of your CCNET setup. Remember, with plugins, the file names are specific (NOTE: all must start with ccnet and end within plugin.dll). This has already been done.

Included in the ZIP file are the following plugins:

- Banty.AssemblyInfoUpdater - Updates assemblyinfo files with a CCNET build version number.
- Banty.CustomVersioner - Enables a custom versioner (labeller) with wildcard support.
- Banty.MSILister - Lists and produced a packing file to display the contents of an MSI file as XML.
- Banty.TestHarness - Used to test the plugins locally
- Banty.VersionMSIFile - Injects a CCNET version into an MSI file
- Banty.VersionWixFile - Injects a CCNET version into a Wix Setup project
- Banty.VSSCommentCollector - Collects VSS comments for the build.
- CCNet.Sequential.PlugIn+Unittests (External project updated to latest version)
- MsBuildToCCNet (External project by rodymeister)


Working with ASP.NET, JQUERY and JSON [EXAMPLES]

I have put together some examples of working with ASP.NET, JQUERY and JSON. The examples use three techniques:

- Page Methods
- Web Services
- WCF Services

and neither of them use UpdatePanels or ScriptManagers to display dynamic content, worth a look!

The file is for .NET Framework 3.5 and written in C#.

JQuery: Calling ASP.NET Page Methods

Here is a great page explaining how to call page methods directly using JQuery.

Friday, 4 June 2010

Error connecting to undo manager of source file "designer file here"

This error happens a lot with visual studio, up to and including VS2008. The reason being that VS keep track of the form designers in debug mode by creating 'designer' files for each ASP.NET or each Windows Form etc...

This usually occurs, for whatever reason, when the designer file cannot be updated, usually being its corrupted. To resolve this error, simply do the following...

ASP.NET
1) Right-click the designer file and select delete
2) Right-click the aspx file and select Convert to Web Application

Other Project Types
Exclude the file from the Project, recompile, then re-include, the recompiles.

Wednesday, 2 June 2010

.NET: LoaderLock was detected

Error Message
Attempting managed execution inside OS Loader lock. Do not attempt to run managed code inside a DllMain or image initialization function since doing so can cause the application to hang.


The problem can be solved by switching off the MDA:

Debug -> Exceptions -> Managed Debug Assistants

and unchecking the LoaderLock item.

This simply tells visual studio that you do not wish to be alerted by this exception everytime you debug the application.

Restore SQL Server Database from .BAK backup file

You would think that restoring a backup would be fairly straight forward, but usual, this isn't always the case. We need to create an MDF (database) and LDF (log file) from the .BAK file, and plug this into SQL Server. urgh.

First things first... Run the below command to find out the logical names of the MDF and LDF files. I recommand running this under the master database or you will receive a 'database in use' error message.
RESTORE FILELISTONLY
FROM DISK = 'C:\dbbackup.bak'



Now we have these names, we can restore to them using the following command. We can specify a UNC path to the BAK file incase we want to update over a network, but the MDF and LDF file paths cannot be UNC.
RESTORE DATABASE [TESTDB]
FROM DISK = 'C:\dbbackup.bak'
WITH REPLACE,
MOVE 'TESTDB' TO 'C:\Program Files (x86)\Microsoft SQL Server\MSSQL.1\MSSQL\Data\TESTDB.MDF',
MOVE 'TESTDB_log' TO 'C:\Program Files (x86)\Microsoft SQL Server\MSSQL.1\MSSQL\Data\TESTDB_log.LDF'


The database name will be the logical name of the MDF file... In my case, I could not get this to work until I had created a database with the same name before hand.
Ensure the directories above exist otherwise it will not work. I chose to place them with my other SQL Server table definitions.

Monday, 26 April 2010

Unit Testing: Accessing AppSettings within the context of the test

I recently had an issue with my tests failing, I realised it was because when using my particular gtest suite, the debug files are not stored within bin\Debug, but rather somewhere else; and the configuration files are not being copied!

This was quite dramatic as my DB connection strings are stored as settings, and needed these to produce tests against the data. The simpliest way to solve this problem is to add a post-build event to the test project. This psot build event will copy the configuration file to the target destination. The testing suite will then pick up these settings as and when requested.

1. Right click and properties on the test project
2. Click Build Events Tab.
3. Add the following post build event...
copy /Y "$(ProjectDir)app.config" "$(TargetPath).config"



This will ensure your config file is copied to its destination, where your test suite (I.e. NUnit can pick it up).

Friday, 23 April 2010

JQuery: Select elements containing an attribute

Here's a simply JQuery snippit that will select all elements using the selector which contain a specified attribute...

This will select all table rows (TR's) of a table with an ID of 'tblTest' that have an attribute 'uniqueid'.
$("table#tblTest tr[uniqueid]")

Javascript: Add/Remove classes from elements

Here's a simple way of achiving this without any bloating libraries (I.e. JQuery etc.)
There is also a 'HasClass' method, which becomes quite useful.

function hasClass(ele, cls) {
    return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
}
function addClass(ele, cls) {
    if (!this.hasClass(ele, cls)) ele.className += " " + cls;
}
function removeClass(ele, cls) {
    if (hasClass(ele, cls)) {
        var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
        ele.className = ele.className.replace(reg, ' ');
    }
}

Wednesday, 21 April 2010

ASP.NET: Using FindControl Recursively.

FindControl can be a bit of a pain, especially as it doesn't contain recursive support; that being, you need to know the direct parent of an object before you can start your search.

I have attached a method which is useful, especially when creating generic methods for groups of objects where the parent is unknown.

        /// <summary>
        /// Finds a Control recursively. Note finds the first match and exists
        /// </summary>
        /// <param name="RootControl">Root control to start looking in.</param>
        /// <param name="ControlToFind">The control to find within it recursively.</param>
        /// <returns>The control if it exists.</returns>
        public Control FindControlRecursive(Control RootControl, string ControlToFind)
        {
            if (RootControl.ID == ControlToFind)
                return RootControl;
 
            foreach (Control Ctl in RootControl.Controls)
            {
                Control FoundCtl = FindControlRecursive(Ctl, ControlToFind);
                if (FoundCtl != null)
                    return FoundCtl;
            }
 
            return null;
        }

Wednesday, 14 April 2010

ASP.NET GridView: Adding Sorting and Paging with Business Object Binding

So recently, I was given the challenge of binding a complex business object to a GridView... I didn't think it would be tricky, but I did find a few niggles along the way. I thought I would share them so anybody else attempting to do this can pick up some lessons learned. The following is my implementation of this task...

This example displays a list of courses. Each course has an ID, a name and a production status. The production status' will be rendered as images within the gridview. Below is the designer structure of the grid..

        <asp:GridView ID="grdBantyGridView"
                    CssClass="gridView"
                    rowstyle-borderstyle="none"
                    GridLines="None" BorderStyle="None"
                    RowStyle-CssClass="row"
                    runat="server"
                    ShowFooter="True"
                    AutoGenerateColumns="False"
                    ShowHeader="True"
                    AlternatingRowStyle-CssClass="alternate"
                    AllowSorting="True"
                    UseAccessibleHeader="True"
                    onrowdatabound="grdBantyGridView_RowDataBound" 
                    onsorting="grdBantyGridView_Sorting"
                    OnPageIndexChanging="grdBantyGridView_PageIndexChanging" 
                    AllowPaging="True"
                    PageSize="10"
                    PagerSettings-Mode="NumericFirstLast"
                    PagerSettings-PageButtonCount="10"
                    DataKeyNames="ID" EnableViewState="False"
                    >
            <HeaderStyle CssClass="gridViewHeader" />
            <RowStyle BorderStyle="None" CssClass="row" />
            <Columns>
                <asp:BoundField DataField="ID" Visible="False" />
                <asp:TemplateField AccessibleHeaderText="Name" HeaderText="Name" 
                    SortExpression="Name">
                    <ItemTemplate>
                        <asp:Label ID="lblName" runat="server" 
                            Text='<%# Eval("Name") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField AccessibleHeaderText="Description" HeaderText="Description" 
                    SortExpression="Description">
                    <ItemTemplate>
                        <asp:Label ID="lblDescription" runat="server" 
                            Text='<%# Eval("Description") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField AccessibleHeaderText="Status" HeaderText="Status" 
                    SortExpression="Status">
                    <ItemTemplate>
                        <asp:Image ID="imgStatus" runat="server" 
                            ImageUrl='<%# GetCourseStatusUrl((Banty.Library.DTO.CourseStatus)Eval("Status")) %>' />
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
            <FooterStyle CssClass="gridViewFooter" />
            <EmptyDataTemplate>
            No data available.
            </EmptyDataTemplate>
        <PagerStyle height="5px" verticalalign="Bottom" horizontalalign="Center" />
            <AlternatingRowStyle CssClass="alternate" />
    </asp:GridView>



Things to note:
AutoGenerateColumns - This gives us more flexibility over what columns appear when we bind out business objects.
AllowSorting - Allows the sorting, but because we have disabled AutoGenerateColumns, we need to override the 'onsorting' event.
AllowPaging - Allow the paging. We must override the 'OnPageIndexChanging' event to make this work.
DataKeyNames - Set this so that we can retrieve each row during the 'onrowdatabound' event. This will store a datakey against each row. I am using this to enable javascript highlights of rows in this example.

For each column in the Gridview definition, be sure to add a SortExpression value. This helps us in the code-behind to identify while column is requesting the sort.

The above points are key to creating this pagable and sortable grid, and must be taken into consideration... Lets now have a look at the code behind and how we fit this all together...


        private List<Banty.Library.DTO.Course> courses = null;
 
        #region >>> Properties
 
        public List<Banty.Library.DTO.Course> Courses { get { return this.courses; } set { this.courses = value; } }
 
        #endregion
 
        protected void Page_Load(object sender, EventArgs e)
        {
        }
 
        /// <summary>
        /// Initialises the user control.
        /// </summary>
        public void InitControl()
        {
            if (this.Courses != null)
            {
                this.SetDataSource(true);
 
                pnlNoData.Visible = false;
                pnlGridView.Visible = true;
            }
            else
            {
                pnlNoData.Visible = true;
                pnlGridView.Visible = false;
            }
        }
 
        /// <summary>
        /// Sets the datasource to the gridview.
        /// </summary>
        private List<Banty.Library.DTO.Course> SetDataSource(bool dataBind)
        {
            // Bind apps to the grid view.
            this.grdBantyGridView.DataSource = this.Courses;
 
            // Bind if requested
            if (dataBind)
                this.grdBantyGridView.DataBind();
 
            return this.Courses;
        }
 
        /// <summary>
        /// Performs actions as the row data is bound.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void grdBantyGridView_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                int uniqueID = 0;
                if (Int32.TryParse(this.grdBantyGridView.DataKeys[e.Row.RowIndex].Value.ToString(), out uniqueID))
                {
                    // Add applicationID to row
                    e.Row.Attributes["uniqueid"] = uniqueID.ToString();
                }
 
                // So that we can distinguish rows
                e.Row.Attributes.Add("OnMouseUp", "grdBantyGridView_OnMouseUp(" + e.Row.ClientID + ");");
            }
        }
 
        /// <summary>
        /// Provides an image URL for value.
        /// </summary>
        /// <param name="input">data value</param>
        /// <returns>A URL to the value icon</returns>
        protected string GetCourseStatusUrl(CourseStatus input)
        {
            string result = string.Empty;
 
            switch (input.ID)
            {
                case 1:
                    result = "../images/icon-no.gif";
                    break;
                case 2:
                    result = "../images/icon-yes.gif";
                    break;
                case 3:
                    result = "../images/icon-verified.gif";
                    break;
            }
 
            return result;
        }
 
        /// <summary>
        /// Handles the sorting of the gridview columns
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void grdBantyGridView_Sorting(object sender, GridViewSortEventArgs e)
        {
            // Determine weather column is valid
            string[] validFields = new string[] { "Name", "Description", "Status"};
 
            // if we have a valid field to sort....
            if (validFields.Contains(e.SortExpression))
            {
                // Get Sort direction
                string sortDir = this.GetSortDirection(e.SortExpression);
                List<Banty.Library.DTO.Course> courses = this.SetDataSource(false);
 
                // Filter on sort expression...
                switch (e.SortExpression)
                {
                    case "Name":
                        if (sortDir.Equals("ASC"))
                            courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return c1.Name.CompareTo(c2.Name); });
                        else
                            courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return -c1.Name.CompareTo(c2.Name); });
                        break;
 
                    case "Description":
                        if (sortDir.Equals("ASC"))
                            courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return c1.Description.CompareTo(c2.Description); });
                        else
                            courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return -c1.Description.CompareTo(c2.Description); });
                        break;
 
                    case "Status":
                        if (sortDir.Equals("ASC"))
                            courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return c1.Status.Status.CompareTo(c2.Status.Status); });
                        else
                            courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return -c1.Status.Status.CompareTo(c2.Status.Status); });
                        break;
                }
 
                // Rebind the gridview
                this.grdBantyGridView.DataBind();
            }
        }
 
        /// <summary>
        /// Handle page changing on the grid.
        /// </summary>
        /// <param name="sender">This parameter is not used.</param>
        /// <param name="e">This parameter is not used.</param>
        protected void grdBantyGridView_PageIndexChanging(object sender, GridViewPageEventArgs e)
        {
            // Update data source
            this.SetDataSource(false);
 
            this.grdBantyGridView.PageIndex = e.NewPageIndex;
            this.grdBantyGridView.DataBind();
        }
 
        // This is sort the column, by default, asc.
        // Unless, the column has been sorted already...
        // then it will sort the column in the opposite direction.
        private string GetSortDirection(string column)
        {
            // By default, set the sort direction to ascending.
            string sortDirection = "ASC";
 
            // Retrieve the last column that was sorted.
            string sortExpression = ViewState["SortExpression"] as string;
 
            if (sortExpression != null)
            {
                // Check if the same column is being sorted.
                // Otherwise, the default value can be returned.
                if (sortExpression == column)
                {
                    string lastDirection = ViewState["SortDirection"] as string;
                    if ((lastDirection != null) && (lastDirection == "ASC"))
                    {
                        sortDirection = "DESC";
                    }
                }
            }
 
            // Save new values in ViewState.
            ViewState["SortDirection"] = sortDirection;
            ViewState["SortExpression"] = column;
 
            return sortDirection;
        }
 
    }



We can also add javascript to apply styles to the selected row
//
// Handles highlighting table rows on mouseup event.
//
function grdBantyGridView_OnMouseUp(row) {
 
    // Reset grid styles
    ResetGrid();
 
    // Add selected class
    row.setAttribute('class', 'selected');
}
 
function ResetGrid()
{
    // Remove selected from all rows that have a unique attribute...
    $("table.gridView tr[uniqueid]").removeClass("selected");
 
    // Now add the alternate style to every 2nd row...
    for (var i = 1; i < $("table.gridView tr[uniqueid]").length + 1; i++)
    {
        if (i % 2 == 0)
            $("table.gridView tr[uniqueid]")[i-1].setAttribute('class', 'alternate');
    }
}