//Monday, 27 October 2008
Using the ASP.NET CompareValidator control to validate date input
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
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();
DashContext rc = new DashContext(connStr); var q = from p in rc.Products select p; List<Product> productList = q.ToList();
Subsonic Call
ProductController pc = new ProductController(); ProductCollection pcol = pc.FetchByID(5); Product p = pcol[0];
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];
Subsonic Call
ProductController pc = new ProductController(); Query q = new Query(Product.Schema.TableName); q.AddWhere("Name", Comparison.Like, "%sensor%"); ProductCollection pcol = pc.FetchByQuery(q);
DashContext rc = new DashContext(connStr); var q = from p in rc.Products where p.Name.Contains("sensor") select p; List<Product> productList = q.ToList();
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
Linq: 00:00:00.09 Subsonic: 00:00:01.1
Linq: 00:00:04.2 Subsonic: 00:00:01.6
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(); }
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:
//Wednesday, 18 June 2008
Get the FCKEditor value from javascript
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 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 ();
if(ctrlType == typeof(TextBox))
{
...
}
else if(ctrlType == typeof(CheckBox))
{
...
}
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;
[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
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
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;
}
}
}
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();
}
}
}
}
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 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