//Monday 27 October 2008

Using the ASP.NET CompareValidator control to validate date input

Stumbled across this tip the other day:

http://dotnettipoftheday.org/tips/validate-format-of-integer-double-date-currency.aspx

It turns out that you can validate date inputs with a CompareValidator by setting the Operator property to DataTypeCheck and ValidationDataType to Date (or Integer, Double, Currency, etc.).

This was a new one on me, I had always assumed that the CompareValidator was just used for comparing the value of a control with another control (or a constant value) which is what the name (and MSDN) would suggest. I don't know why Microsoft didn't create a separate control called a DataTypeValidator with this functionality rather than hide it within the CompareValidator.

//Wednesday 3 September 2008

Comparing Linq with SubSonic

After seeing Subsonic in action in the dashCommerce project (Open Source ASP.NET eCommerce application) I have used it in a couple of projects and I like it. It's easy to get up and running, has an intuitive object model and a handy Scaffold component for getting a data admin interface put together quickly. It stands up well compared to other .NET DALs I have used in the past e.g. NEO which I found less intuitive to configure and update.
Recently I've started looking at Linq to see how it compares to Subsonic (and other .NET DALs). From what I've seen so far it looks good though I find it less intuitive than SubSonic. The query syntax is good and is easy to understand for anyone familiar with SQL but I think the concepts of how and when the data is loaded is less obvious with Linq when compared with Subsonic.
SubSonic feels like an object model I would have written myself from scratch. This might just be a case of me getting used to Linq though. To start getting to grips with Linq I thought I would generate a data context for the dashCommerce project and try writing some queries that correspond to existing Subsonic methods.
Examples
Here are some examples of how some calls to the dashCommerce database look in Subsonic with a corresponding Linq version.
Fetch all products
Subsonic Call
ProductController pc = new ProductController();
ProductCollection pcol = pc.FetchAll();
Linq Equivalent
DashContext rc = new DashContext(connStr);
var q = from p in rc.Products               
        select p;
List<Product> productList = q.ToList();
Fetch specific product by id
Subsonic Call
ProductController pc = new ProductController();
ProductCollection pcol = pc.FetchByID(5);
Product p = pcol[0];
Linq Equivalent
DashContext rc = new DashContext(connStr);
var q = from p in rc.Products        
        where p.ProductId == 5
        select p;
List<Product> productList = q.ToList();
Product p = productList[0];
Search for products by name
Subsonic Call
ProductController pc = new ProductController();
Query q = new Query(Product.Schema.TableName);
q.AddWhere("Name", Comparison.Like, "%sensor%");
ProductCollection pcol = pc.FetchByQuery(q);
Linq Equivalent
DashContext rc = new DashContext(connStr);
var q = from p in rc.Products        
        where p.Name.Contains("sensor")
        select p;
List<Product> productList = q.ToList();
Performance comparison
Inspired by this forum post: http://dashcommerce.org/forums/p/656/2194.aspx I thought I'd try comparing the performance of Linq with that of Subsonic. This isn't a particularly scientific test but I put each of the calls described above in a loop with 1000 iterations to see if there were any major differences in performance.
Fetch all products
Linq:     00:00:04.1
Subsonic: 00:00:07.2
Fetch specific product by id
Linq:     00:00:00.09
Subsonic: 00:00:01.1
Search for products by name (LIKE query)
Linq:     00:00:04.2
Subsonic: 00:00:01.6
In the first two queries Linq outperformed Subsonic but in the case of the LIKE query it was slower. I am guessing this is something to do with the way Linq translates the query into SQL as the final SQL that is executed is the same.
I decided to try the final test again using a Linq Compiled Query as follows:
    public static class Queries
    {
        public static Func<RetrofitContext, string, IQueryable<Product>> SearchProductsByName
            = CompiledQuery.Compile((RetrofitContext rc, string name) => from p in rc.Products where p.Name.Contains(name) select p);     
    }

    RetrofitContext rc = new RetrofitContext(connStr);
    for (int i = 1; i < 1001; i++)
    {
       // Now use a CompiledQuery
       List<Product> productList = Queries.SearchProductsByName(rc, "sensor").ToList();
    }
This time Linq outperformed Subsonic (slightly) with a time of 00:00:01.3.
For reference the products table in this test contained 39 records, there were 3 records that matched the LIKE query (i.e. have the word sensor in the name). I was using my local SQL Server 2005 Express Edition (SP2).
Conclusion
In (my not particularly stringent) performance testing Linq seems to stack up well against Subsonic, my current DAL of choice. I will continue to investigate the features of Linq and post any hints, tips or gripes here. I'm also going to look at ASP.NET Dynamic Data to see whether this will allow me to replicate the simple data admin interface provided by the Subsonic Scaffold control.

//Monday 23 June 2008

CSS Image Replacement Techniques

Web designers like to use images for text because they can use non-standard fonts and have them nicely anti-aliased. There are a number of techniques out there that allow you to make this kind of image text accessible and search-engine friendly.

One technique I have seen used is a negative text-indent (see http://phark.typepad.com/phark/2003/08/accessible_imag.html).

For example, if you wanted to use an image for your h1 tag you could write something like this:


<h1 id="hello-world">Hello World</h1>

and then in your CSS you set a background-image for the hello-world element and set text-indent to a very large negative number so that it's rendered off the screen.

#hello-world
{
width: 100px;
height: 25px;
background: url(helloworld.gif) no-repeat;
text-indent: -9999px;
}

Most users will see the image but without the style sheet the h1 renders as normal. So search engines and screen readers should read your page in the expected way.
Note: I have read in articles that this technique does indeed work with the JAWS screen reader and Google seems to index the text OK so I guess it doesn't view it as a "black-hat" technique.

There is a flaw with this technique and that is that most browsers allow you to disable images. If you were to build a page where most of the text was replaced using the text-indent technique and someone did browse it with images turned off they would see an almost entirely blank page. Some people might consider this a moot point because after all who in the age of high speed internet access really disables image downloads? However, in my opinion there does seem to be something intuitively wrong with this technique. It just doesn't feel right to me having a load of text mysteriously floating somewhere off screen.

So, what's the alternative? Well, it seems that there are no perfect solutions to this problem. There are a number of different solutions that I've seen suggested. For example, if your image has a flat background and you can fit it in you can make the text small and hide it in the image. Then have a contrasting background color underneath so that it is visible with images turned off (http://mondaybynoon.com/2006/10/23/my-latest-take-on-image-replacement/). I've also seen an article (http://www.drunkenfist.com/304/2007/03/19/an-alernative-to-negative-text-indent-for-image-replacement-css/) that suggests using opacity in CSS to make the text transparent though this will only work in modern browsers and requires an additional element in your markup.

Another alternative that requires an additional element purely for presentational purposes is as follows:

<h1 id="hello-world">Hello World<span></span></h1>
#hello-world
{
position: relative;
width: 100px;
height: 25px;
overflow: hidden;
}
#hello-world span
{
display: block;
position: absolute;
left: 0;
top: 0;
width: 100px;
height: 25px;
background: url(helloworld.gif) no-repeat;
}

The original explanation (I think) of this technique can be found here (http://levinalex.net/files/20030809/alternatefir.html). Again this doesn't feel right. Mainly because your markup is less semantic and your code become a lot more bloated when compared with the text-indent method. However this technique does work nicely if you do happen to have images switched off. Note: If you use a transparent gif then you'll be able to see the text because it's sitting underneath the image.

So which method should you use? I don't think there is a definitively right or a wrong answer because all techniques I have seen have some kind of drawback. Which method is right for you will depend on your target audience.

I've created a test page that demonstrates the techniques I have discussed in this article:

Examples of CSS Image replacement techniques

//Wednesday 18 June 2008

Get the FCKEditor value from javascript

As a postscript to my previous post about customizing the Subsonic Scaffold I also had cause whilst building an admin interface to pull out the XHTML value from the editor in a Javascript function. This took a little bit of searching for so I thought I would post it here:

FCKeditorAPI.GetInstance("ctl00_phAdmin_Scaffold1_PageContent").GetXHTML(true);

Where ctl00_phAdmin_Scaffold1_PageContent is the ID of your FCKEditor control.

I found the answer here:

http://www.fckeditor.net/forums/viewtopic.php?f=6&t=9536&start=0&st=0&sk=t&sd=a

I guess I should have gone straight to the Javascript API docs but I tend to just Google questions like this. Since my first search was the title of this post hopefully this will help people to find the answer quicker than I did.

Customizing the Subsonic scaffold control

The Subsonic data access layer comes with a control called a Scaffold (the Scaffold concept comes from Ruby on Rails apparently) which allows you to quickly build a simple administration interface for your database tables.

The basic syntax is as follows:

<cc1:Scaffold ID="Scaffold1" runat="server" TableName="Products"></cc1:Scaffold>

Where SubsonicProvider is the name of the SubSonicService Provider from your web.config and TableName is the table you want to edit. The control handles rendering a gridview and a form for editing the fields in your table. By default the control will display all fields from the table in both the grid view and detail view. If you have a big table this quite quickly becomes unwieldy, particularly in the case of the grid view. The control provides two properties HiddenGridColumns and HiddenEditorColumns which allow you to specify which columns to hide. Place a comma-delimited list of the fields you want to hide in each property.

If you are providing an administration interface that allows your users to maintain content on their website then it is quite likely you will want to be able to present them with a XHTML editor for the appopriate fields. To achieve this I customized the original Scaffold control to include another property RichTextColumns. Again this is a comma-separated list that contains the fields in the table that you would like to present with a rich text editor.

My open source XHTML editor of choice is FCKEditor which is easy to configure and install and seems to create decent XHTML.

In the fields declaration I've added the following beneath the declaration of the other internal string lists:


private readonly List _richTextColumnList = new List();
In the BindEditor method between:




if(ctrlType == typeof(TextBox))
{
...
}
and




else if(ctrlType == typeof(CheckBox))
{
...
}
I've added the following:





else if (ctrlType == typeof(FredCK.FCKeditorV2.FCKeditor))
{
FredCK.FCKeditorV2.FCKeditor editor = (FredCK.FCKeditorV2.FCKeditor)control2;
editor.Value = reader[column.ColumnName].ToString();
}




Then in the GetEditorControl method in the section that deals with string fields I've added the part that creates the XHTML editor:



switch (col.DataType)
{
case DbType.Guid:
case DbType.AnsiString:
case DbType.String:
case DbType.StringFixedLength:
case DbType.Xml:
case DbType.Object:
case DbType.AnsiStringFixedLength:
if (Utility.IsMatch(colName, ReservedColumnName.CREATED_BY) Utility.IsMatch(colName, ReservedColumnName.MODIFIED_BY))
{
cOut = new Label();
}
else
{
if (_richTextColumnList.Contains(colName))
{
FredCK.FCKeditorV2.FCKeditor editor = new FredCK.FCKeditorV2.FCKeditor();
editor.ToolbarStartExpanded = false;
editor.ForcePasteAsPlainText = true;
editor.Height = 400;
editor.UseBROnCarriageReturn = true;
cOut = editor;
}
else
{

TextBox t = new TextBox();
if (Utility.GetEffectiveMaxLength(col) > 250)
{
t.TextMode = TextBoxMode.MultiLine;
t.Columns = 60;
t.Rows = 4;
}
else
{
t.Width = Unit.Pixel(250);
if (colName.EndsWith("guid"))
{
t.Text = Guid.NewGuid().ToString();
t.Enabled = false;
}
}
cOut = t;
}
}
break;

Then finally I've added the RichTextColumns property which works in a similar way to the other HiddenEditorColumns and HiddenGridColumns properties:



[DefaultValue(""), Description("A comma delimited list of column names which are displayed in the Editor using an XHTML editor."), Bindable(true), Category("Data")]
public string RichTextColumns
{
set
{
this._richTextColumnList.Clear();
foreach (string str in Utility.Split(value))
{
this._richTextColumnList.Add(str.ToLower());
}
}
}




And that's it! This has enabled us to quickly produce simple database administration interfaces for tables that store XHTML content.

//Friday 21 March 2008

Validating a CheckBoxList with an ASP.NET validation control

If you try to use the standard ASP.NET validation controls to validate a CheckBoxList control you may receive an error message that looks like this:

Control 'CheckBoxList1' referenced by the ControlToValidate property of 'RequiredFieldValidator1' cannot be validated

Unfortunately out of the box the RequiredFieldValidator will not work on the CheckBoxList control. In a recent project I had a requirement to validate a number of checkbox lists. I needed to validate that at least one item was checked and in some cases make sure that no more than a certain number of options were selected. I created a new validator control specifically for validating CheckBoxLists to handle this.

Here is a demo of the control in action:


http://www.sourcemotion.com/CheckBoxListTest.aspx

Here is the source code for the validator control:


using System;
using System.Collections;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Validators
{
    public class CheckBoxListValidator : CustomValidator
    {
        private const string CheckBoxListValidatorScriptKey = "CheckBoxListValidator_GlobalScript";

        private bool _isRequired;

        private int _maximumChecked;

        private CheckBoxList _listctrl;

        public bool IsRequired
        {
            get
            {
                return this._isRequired;
            }
            set
            {
                this._isRequired = value;
            }
        }

        public int MaximumChecked
        {
            get
            {
                return this._maximumChecked;
            }
            set
            {
                this._maximumChecked = value;
            }
        }

        public CheckBoxListValidator()
        {
            this._isRequired = true;
            this._maximumChecked = -1;
        }

        protected override bool ControlPropertiesValid()
        {
            bool flag;
            Control control = this.FindControl(base.ControlToValidate);
            if (control == null)
            {
                flag = false;
            }
            else if (!(control is CheckBoxList))
            {
                flag = false;
            }
            else
            {
                this._listctrl = (CheckBoxList)control;
                if (base.EnableClientScript)
                {
                    this.Page.ClientScript.RegisterClientScriptBlock(base.GetType(), this.ClientID, this.GetScript(), true);
                    base.ClientValidationFunction = "validateCheckBoxList";
                }
                flag = true;
            }
            return flag;
        }

        protected override bool EvaluateIsValid()
        {
            bool flag;
            int num = 0;
            foreach (ListItem item in this._listctrl.Items)
            {
                if (item.Selected)
                {
                    num++;
                }
            }
            if ((num != 0 ? true : !this._isRequired))
            {
                flag = ((this._maximumChecked == -1 ? true : num <= this._maximumChecked) ? true : false);
            }
            else
            {
                flag = false;
            }
            return flag;
        }

        protected string GetScript()
        {
            string str = string.Format("var {0}_maxChecked = {1};\r\nvar {0}_isRequired = {2};\r\n", this._listctrl.ClientID, this._maximumChecked, this._isRequired.ToString().ToLower());
            return str;
        }

        protected string GetStartupScript()
        {
            return string.Format("fixCheckboxValidationEvent('{0}');\r\n", this._listctrl.ClientID);
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if ((!base.EnableClientScript ? false : !this.Page.ClientScript.IsClientScriptBlockRegistered(this.Page.GetType(), "CheckBoxListValidator_GlobalScript")))
            {
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.AppendLine("function fixCheckboxValidationEvent(id)");
                stringBuilder.AppendLine("{");
                stringBuilder.AppendLine("    if (document.getElementById(id))");
                stringBuilder.AppendLine("    {");
                stringBuilder.AppendLine("        var checkboxList = document.getElementById(id);");
                stringBuilder.AppendLine("        if (checkboxList.getElementsByTagName)");
                stringBuilder.AppendLine("        {");
                stringBuilder.AppendLine("             var inputs = checkboxList.getElementsByTagName(\"input\");");
                stringBuilder.AppendLine("             for (j=0;j<inputs.length;j++)");
                stringBuilder.AppendLine("             {");
                stringBuilder.AppendLine("                 if (inputs[j].type.toLowerCase()==\"checkbox\")");
                stringBuilder.AppendLine("                 {");
                stringBuilder.AppendLine("                     inputs[j].onclick = inputs[j].onchange;");
                stringBuilder.AppendLine("                     inputs[j].onchange = null;");
                stringBuilder.AppendLine("                 }");
                stringBuilder.AppendLine("             }");
                stringBuilder.AppendLine("        }");
                stringBuilder.AppendLine("    }");
                stringBuilder.AppendLine("}");
                stringBuilder.AppendLine("");
                stringBuilder.AppendLine("function validateCheckBoxList(val, args)");
                stringBuilder.AppendLine("{");
                stringBuilder.AppendLine("    var selectedCount = 0;");
                stringBuilder.AppendLine("    if (document.getElementById(val.controltovalidate))");
                stringBuilder.AppendLine("    {");
                stringBuilder.AppendLine("        var checkboxList = document.getElementById(val.controltovalidate);");
                stringBuilder.AppendLine("        if (checkboxList.getElementsByTagName)");
                stringBuilder.AppendLine("        {");
                stringBuilder.AppendLine("            var inputs = checkboxList.getElementsByTagName(\"input\");");
                stringBuilder.AppendLine("            for (j=0;j<inputs.length;j++)");
                stringBuilder.AppendLine("            {");
                stringBuilder.AppendLine("                if (inputs[j].type.toLowerCase()==\"checkbox\")");
                stringBuilder.AppendLine("                {");
                stringBuilder.AppendLine("                    if (inputs[j].checked)");
                stringBuilder.AppendLine("                    {");
                stringBuilder.AppendLine("                        selectedCount++;");
                stringBuilder.AppendLine("                    }");
                stringBuilder.AppendLine("                }");
                stringBuilder.AppendLine("            }");
                stringBuilder.AppendLine("        }");
                stringBuilder.AppendLine("        var maxCount = eval(val.controltovalidate + \"_maxChecked\");");
                stringBuilder.AppendLine("        var isRequired = eval(val.controltovalidate + \"_isRequired\");");
                stringBuilder.AppendLine("        if (selectedCount == 0 && isRequired)");
                stringBuilder.AppendLine("        {");
                stringBuilder.AppendLine("            args.IsValid = false;");
                stringBuilder.AppendLine("        } ");
                stringBuilder.AppendLine("        else if ((maxCount > -1) && (selectedCount > maxCount)) ");
                stringBuilder.AppendLine("        {");
                stringBuilder.AppendLine("            args.IsValid = false;");
                stringBuilder.AppendLine("        } ");
                stringBuilder.AppendLine("        else ");
                stringBuilder.AppendLine("        {");
                stringBuilder.AppendLine("            args.IsValid = true;");
                stringBuilder.AppendLine("        }");
                stringBuilder.AppendLine("    }");
                stringBuilder.AppendLine("}");
                this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), "CheckBoxListValidator_GlobalScript", stringBuilder.ToString(), true);
            }
            if ((!base.EnableClientScript ? false : !this.Page.ClientScript.IsStartupScriptRegistered(base.GetType(), this.ClientID)))
            {
                this.Page.ClientScript.RegisterStartupScript(base.GetType(), this.ClientID, this.GetStartupScript(), true);
            }
        }
    }
}

Notes:

  • In the CheckBoxListValidator class I override the ControlPropertiesValid method. This is the method that normally throws an exception if you try to assign a CheckboxList control to the ControlToValidate property of a RequiredFieldValidator control. This method now checks to ensure that the ControlToValidate property is pointing at a CheckboxList control.
  • The Javascript function fixCheckboxValidationEvent gets called for each CheckBoxList control and swaps the standard client validation function from the onchange event to the onclick event. By default validation is called on change but for the checkbox you actually need it on click.

//Friday 1 February 2008

Toolset (Part 4) - NUnit

NUnit is a unit-testing framework for .NET. You can download it here:

http://www.nunit.org/index.php?p=download

In this article I'm going to give a quick guide to how I set up my NUnit test projects. I'm not going to go into the reasons why test-driven development is a good idea, for that why not take a look at this:

http://www.codinghorror.com/blog/archives/000640.html (it has the added benefit of including a great picture of Mr T)

Creating your test project
Open the solution for your .NET application and add a new project of type Console Application. Call it XXXTests where XXX is the name of the application you are creating the tests for. Add references to the nunit assemblies nunit.core, nunit.core.interfaces and nunit.framework they will be located in the bin folder of your nunit installation (e.g. C:\Program Files\NUnit 2.4.3\bin\). Now add references to the web application you are testing and any shared assemblies that are required.

If your web application requires any configuration settings in order to run properly you will need to set these up in app.config, also in order to test via nunit.gui you will need to replicate this file and name it XXX.config where XXX is the name of your console application. So if for example your test project was called MyWebAppTests you will need to add a file to your project called MyWebAppTests.config that replicates app.config. For more information see (http://nunit.com/blogs/?p=9).

You should then create a test class for each class in your application. It should be named XXXTests where XXX is the name of the class. So to test a class called Data you would add a test class to your console application called DataTests.cs.

Build your project and then open up NUnit 2.4.3...NUnit Gui from your Start menu. Select File...New Project... navigate to the project directory for your test project and call the project file XXX.nunit where XXX is the name of your test project e.g. MyWebAppTests.nunit. Now go to Project...Add Assembly... and select the assembly for your test project e.g. MyWebAppTests.exe. All your tests should be listed with grey traffic lights select File...Save.

Now in Visual Studio click on your test project and select the Show All Files option. Your Nunit project file should be shown. Right click and select Include in Project. Right click again and select Open With... if it isn't already set it to be the Nunit Gui and set that to be the default (if it isn't in the list you might need to add it by browsing to C:\Program Files\NUnit 2.4.3\bin\nunit.exe. You will now be able to double click on the project file to launch the NUnit Gui.

Attributes
TestFixture
Each of the test classes will have this attribute at the top above the class declaration. This indicates to nunit that this class is to be included when the tests are run.

TestFixtureSetUp
Place this attribute above a method declaration to indicate that this method should be called before nunit starts running the tests in the class. This is useful for initializing objects/variables that you want to use throughout the tests e.g. reading a file, opening a database connection, etc.

TestFixtureTearDown
Place this attribute above a method declaration to indicate this method should be called after nunit has finished running the tests in the class. This is useful for finalizing any objects you created in the set up method e.g. closing a database connection.

Test
Place this attribute above a method declaration to indicate that this is a test. Each method that is marked with this attribute will appear in nunit-gui as a traffic light which will turn green or red depending on whether the test runs successfully.

ExpectedException
If you have tests which should always result in a particular exception you can use this attribute to detail what that exception is and what message you are expecting.

What to test
You should create at least one test for each method and property in each class. You should try and cover the complete functionality of your object so you should test success and failure. Try to think about all the possible ways in which the method or property could fail.

Example
Here is a simple example of a class and an associated test class.

Class to test:

using System;
using System.Collections.Generic;
using System.Text;

namespace MyClassLibrary
{
public class MyClass
{
#region Fields
private int _a;
private int _b;
private string _text;
#endregion

#region Constructors
public MyClass()
{
}
#endregion

#region Properties
public int A
{
get
{
return _a;
}
set
{
if (value <> 100)
{
throw new MyClassException("Value of A cannot be greater than 100");
}
_a = value;
}
}

public int B
{
get
{
return _b;
}
set
{
if (value > 0)
{
throw new MyClassException("Value of B cannot be more than zero");
}
if (value < -100) { throw new MyClassException("Value of B cannot be less than -100"); } _b = value; } } public string Text { get { return _text; } set { if (value != null && value.Length > 600)
{
throw new MyClassException("Length of text cannot be greater than 600");
}
_text = value;
}
}
#endregion

#region Methods
public int GetAPlusB()
{
return _a + _b;
}
#endregion
}
}


Test class:


using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using NUnit.Core;
using NUnit.Framework;
using MyClassLibrary;

namespace MyClassLibraryTests
{
[TestFixture]
public class MyClassTests
{
private StreamReader _validFile;
private StreamReader _tooLargeFile;

[TestFixtureSetUp()]
public void MyClassTests_SetUp()
{
// This is not best practice because ideally you wouldn't keep references to
// files open to avoid locking them however this is just to demonstrate
// the usage of the setup and teardown attributes
_validFile = new StreamReader(String.Format("{0}\\ValidFile.txt", Environment.CurrentDirectory));
_tooLargeFile = new StreamReader(String.Format("{0}\\TooLargeFile.txt", Environment.CurrentDirectory));
}

[TestFixtureTearDown()]
public void MyClassTests_TearDown()
{
_validFile.Close();
_tooLargeFile.Close();
}

[Test, ExpectedException(typeof(MyClassException), ExpectedMessage = "Value of A cannot be less than zero")]
public void A_Set_TooSmall()
{
MyClass obj = new MyClass();
obj.A = -10;
}

[Test, ExpectedException(typeof(MyClassException), ExpectedMessage = "Value of A cannot be greater than 100")]
public void A_Set_TooLarge()
{
MyClass obj = new MyClass();
obj.A = 101;
}

[Test, ExpectedException(typeof(MyClassException), ExpectedMessage = "Value of B cannot be less than -100")]
public void B_Set_TooSmall()
{
MyClass obj = new MyClass();
obj.B = -101;
}

[Test, ExpectedException(typeof(MyClassException), ExpectedMessage = "Value of B cannot be more than zero")]
public void B_Set_TooLarge()
{
MyClass obj = new MyClass();
obj.B = 1;
}

[Test]
public void GetAPlusB_CheckCalculation()
{
MyClass obj = new MyClass();
obj.A = 9;
obj.B = -3;
int result = obj.GetAPlusB();
Assert.AreEqual(6, result, String.Format("Expected result of GetAPlusB is 6 but actual value was {0}", result));
}

[Test]
public void Text_Set_Valid()
{
string validText = _validFile.ReadToEnd();
MyClass obj = new MyClass();
obj.Text = validText;
}

[Test, ExpectedException(typeof(MyClassException), ExpectedMessage = "Length of text cannot be greater than 600")]
public void Text_Set_TooLarge()
{
string tooLargeText = _tooLargeFile.ReadToEnd();
MyClass obj = new MyClass();
obj.Text = tooLargeText;
}
}
}
Debugging Your Tests
To debug your NUnit tests you can call your tests manually in the Main method of your console application and then run the application in debug mode as you would any other application. You will need to set your test application to be the startup project in your solution.


using System;
using System.Collections.Generic;
using System.Text;

namespace MyClassLibraryTests
{
class Program
{
static void Main (string[] args)
{
MyClassTestsDebug();
}

public static void MyClassTestsDebug()
{
// This allows me to debug a particular test method
MyClassTests myClassTests = new MyClassTests();
myClassTests.MyClassTests_SetUp();
try
{
myClassTests.Text_Set_TooLarge();
}
finally
{
myClassTests.MyClassTests_TearDown();
}
}

}
}
Alternative Method
If you would prefer to test through the Nunit Gui then right click on your test project in Visual Studio, select Properties and go to the Debug tab.

Change Start Action to be Start external program and set it to be NUnit Gui: C:\Program Files\NUnit 2.4.3\bin\nunit.exe.Set Command line arguments to be your Nunit test project e.g. MyWebAppTests.nunit.Set working directory to be the directory where the nunit project file is located (which should be the root of folder for your test project).Right click on the test project and select Set as Startup Project (if you haven't already).

Now when you click run in Visual Studio it will launch NUnit Gui when you run the tests in Nunit it will stop at any breakpoints you have set up in Visual Studio.

//Tuesday 29 January 2008

Toolset (Part 3) - Miscellaneous

Reflector

Reflector is a class browser, explorer, analyzer and documentation viewer for .NET.

http://www.aisto.com/roeder/dotnet/

It is particularly useful if you are having problems with a third party .NET assembly that you do not have the source for as it includes a disassembler. If you open up an assembly (File...Open) and then drill down to a class or method you can right click and select Disassemble and it will display the source code for you. There is also a dropdown on the toolbar that lets you select language so you can disassemble it in C#, Visual Basic, MC++, etc. should you so wish.

IISAdmin.NET
This is a useful little tool that let's you create multiple root websites in IIS under Windows XP which is often handy during development.

http://www.codeplex.com/iisadmin

WebLog Expert Lite

Useful free log analyzer for examining IIS log files. Gives information about Visitors, Referrers, Browsers, Errors, etc.

Xenu's Link Sleuth
Xenu's link sleuth is a website spider tool that can be useful for detecting broken links in a website or for checking what pages in a website are actually used. You might inherit a website with lots of pages but actually find that not every page is still linked up.

http://home.snafu.de/tilman/xenulink.html

SyntaxHighlighter