Archive for the ‘Programming’ Category
April 29th, 2009
Just making sure this is correct. By this I mean PreCode (requires Windows Live Writer 2009) with SyntaxHighligher setup. PreCode is also a stand alone program.
public bool Validate(IValidationDictionary modelState, string prefix)
{
// xVal example code
var dataAnnotationErrors = from prop in TypeDescriptor.GetProperties(_entity).Cast<PropertyDescriptor>()
from attribute in prop.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(prop.GetValue(_entity))
select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), _entity);
var brokenRules = GetBrokenRules();
if (!String.IsNullOrEmpty(prefix))
prefix += ".";
if (dataAnnotationErrors.Any())
dataAnnotationErrors.ForEach(ei => modelState.AddError(prefix + ei.PropertyName, ei.ErrorMessage));
if (brokenRules.Any())
brokenRules.ForEach(rule => modelState.AddError(prefix + rule.Property, rule.Message));
return modelState.IsValid;
}
There is a “Fix Indentation” button in PreCode. I am in love.
Now just to setup the clipboard SWF thing.
I should seriously consider a theme with a wider content area.
April 15th, 2009
Since you can run classic web forms along side MVC its useful to know how to update your *.csproj project file so Visual Studio shows you the MVC items you can add to your project when you add a new item. To do this, after installing MVC (and .NET 3.5 SP1 if you haven't yet):
- Unload your project through Solution Explorer (right-click, unload)
- Edit your *.csproj (yes you can do this in Visual Studio, right click the unloaded project node in Solution Explorer, Edit *.proj)
- In the <ProjectTypeGuids /> node add the GUID {603c0e0b-db56-11dc-be95-000d561079b0}; (the semi-colon is important if you prepend it to the exiting list, which is what I did)
- Add <MvcBuildViews>false</MvcBuildViews> as a sibling of the <ProjectTypeGuids /> node
- Reload the project
April 15th, 2009
So Java is good for something other than an abstract white coffee cup on an orange background in my system tray after all!
I am working with a friend on an attempt at best practices MVC development using the whole nine yards (DI, TDD, CI, git, SVN). I had sort of dreaded the part where I had to setup CruiseControl.NET because, well, I hate editing XML files and I hate configuring CC.NET. Then I stumbled across Chris Allport's blog post on Hudson for .NET Projects. Hudson is a continuous integration server written in Java and its trivially easy to install, setup, and get running. On my workstation it took less than 15 minutes to have a working build. My Windows Home Server (Windows Server 2003) it was a bit more taxing as I had no build environment so I'll detail the steps shortly.
Some of the advantages I see from Hudson, other than very easy to setup and configure:
- not a lot of hand editing XML files (you could I suppose)
- plugins - loads and loads - install from the web UI, no manual steps necessary
- has built in "install as Windows service" option
- if you do this, do it first for optimal user experience - if you set your hudson directory to something like "c:\hudson" and install as a Windows Service your previous settings will appear to be lost (but they aren't, they are just under the ~\.hudson folder Hudson uses by default)
- easy to manage security or for the masochistic LDAP integrated security
- very clean, responsive UI
- build reports, trends, status
- RSS feeds for build statuses per project plus email notification
- integrated textual help on just about everything
Its rare to find a software package that just feels enjoyable to use. Hudson is one of those software packages. It absolutely has displaced CC.NET for CI for me.
So on my bare Windows Server 2003 I had to install the following things (download links may not work due to session identifiers but you should be able to find everything after a cursory search):
- Java Runtime Environment 1.6 (1.5+ is required) [download]
- .NET Framework 3.5 with SP1 [download] (required a reboot)
- Hudson (latest stable) [download - can go out of date, look for the latest on the Hudson releases page]
- I placed the hudson.war file in c:\hudson and gave the local system (Network Service) account privileges to this folder
- Ruby (for Rake, optional if you use NAnt or something else) [download - this link can easily go out of date, you'll want the latest version of Ruby as always]
- after installation, Command Prompt and cd c:\ruby then gem install rake
- Windows SDK for Windows Server 2008 and .NET Framework 3.5 [download]
- Supported platforms: Windows Server 2003, amongst many others - don't let the name fool you!
- There is an ISO version and web installer - I went for the web installer and paired down the installed components to just the .NET SDK stuff
- (Optional) ASP.NET MVC if you are compiling an MVC application [download]
- (Optional) Visual Studio for C# Express [home page]
I also had to copy my workstation's MS Build targets (found, on Vista 64 by default at: C:\Program Files (x86)\MSBuild\Microsoft and ended up on my Windows 2003 Server 32-bit at Program Files\MSBuild\Microsoft [download]
After you're all setup, you can start Hudson by running:
java -jar c:\hudson\hudson.war*
then you can browse to http://localhost:8080 and begin your configuration - but if you're going to install Hudson as a Windows Service make sure to turn that on first so you don't waste time double configuring! Installing as a service was very easy but I did have to go to Computer Management > Services and manually restart Hudson due to a Java NullPointerException caused when the web UI tried to launch the service manually. No big deal.
I installed the Rake plugin and the NUnit plugin. Once doing this I restarted the service (there is a button after plugin installation to do this, and restarting the service from the service hosted HTTP server works fine).
After fiddling around with some settings (specifically email - I created a gmail account and used smtp.gmail.com over SSL for my build server) I moved onto configuring a job. Some settings I made for my build were:
- Advanced Project Options > Use custom workspace - I created c:\hudson\workspaces\specialorder and choose that as my custom workspace. Be foreward: this ended up causing my repository to be placed under c:\hudson\workspaces\specialorder\specialorder since I also set the Source Code Management > Local module directory (optional) value to "specialorder"
- For building I used rake because, well, its ridiculously easy. I used the example Rakefile.rb found on Chris Allport's blog "Getting Started with Rake on .NET Projects" as my basis, the resulting Rakefile.rb was placed in my source control repository's trunk\ directory and checked in.
task :default => :build
task :build => [:clean, :compile, :test]
task :clean do
FileUtils.rm_rf("build")
# this doesn't do anything for me yet - have to conf. projects to ".."
end
task :compile do
params = '/t:Rebuild /nologo /v:m /p:Configuration="Debug" src\SiameseHead.SpecialOrder.sln'
msbuid = 'C:\\WINDOWS\\Microsoft.NET\\Framework\\v3.5\\MSBuild.exe'
sh "#{msbuid} #{params}"
end
task :test do
# FileUtils.cd 'build\\Debug'
# exec "..\\..\\tools\\nunit\\nunit-console.exe TestStuffInDotNet.dll"
end
You can see I haven't tried setting up automatic unit testing yet, but I will shortly.
- For Build > Advanced I specified the full path to the Rakefile.rb (C:\hudson\workspaces\specialorder\specialorder\trunk\Rakefile.rb) and the full path for the Rake working directory (C:\hudson\workspaces\specialorder\specialorder\trunk\); I was getting "invalid path" errors from Rake otherwise
Once you've saved your job you should be able to build it using the "Build Now" link and work your way through any errors (just click on failed builds then look for the "Console Output" link on the left side of the page).
March 30th, 2009
The Lightweight Test Automation Framework is a browser automation framework like WatiN and Selenium RC that allows you to create automated integration tests. Its a beta release from MS and can be found on Codeplex at:
http://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=22739
I first read about it from Steve Sanderson's blog where he describes how to use it with an ASP.NET MVC project.
I am supporting a legacy web forms application and have a typical web application setup so I'll explain what I did to get up and running in a matter of minutes with LTAF.
- Download LTAF; you can use the "... for ASP.NET Samples" link and pull the *.dll out of the Bin folder and the contents of the Test folder, or you could build from source using the "... for ASP.NET Source"
- Create a new Class Library project in your solution and name it appropriately - Company.Tests.Integration.Site - for example
- For this project's properties alter the output assembly's name to begin with App_Code - this will slightly speed up web test load times and can help solve problems you may experience if your web application references tons of assemblies, i.e. App_CodeCompany.Tests.Integration.Site
- Add a reference to Microsoft.Web.Testing.Light.dll to your Company.Tests.Integration.Site project
- Copy and add to your web project (Company.Site) the Test folder; you can name the folder anything you want and you don't strictly have to add it to your project but I did for convenience for Start > Debug
- Remember to alter your build process to remove the Test folder for production releases
- Alter the web.config file for your Company.Site to allow anonymous access to the Test folder:
<location path="Test">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
- Again, remember to alter your build process to eliminate this node from your production web.config
- Add a reference to your integration test project, Company.Tests.Integration.Site, to your web application, Company.Site
- Let's test the login page (here I assume that "Login.aspx" is the name of your login page and its in the root of the application and you've used an <asp:Login /> control)...
- Create a new class LoginTests in your Company.Tests.Integration.Site project and eliminate the file's contents - it is very important that all tests exist outside of a namespace
- Paste this code into the body of your LoginTests.cs file:
using Microsoft.Web.Testing.Light;
[WebTestClass]
public class LoginTests
{
[WebTestMethod]
public void SignInAndSignOut()
{
var page = new HtmlPage("Login.aspx");
page.Elements.Find("UserName").SetText("someuser");
page.Elements.Find("Password").SetText("yourPW");
page.Elements.Find("LoginButton").Click(WaitFor.Postback);
// you'll need to determine what a good landmark for your
// application is upon successful login
var logoutLink = page.Elements.Find("LogoutHyperlink");
Assert.AreEqual("[Logout]", logoutLink.GetInnerText());
logoutLink.Click(WaitFor.Postback);
Assert.IsNotNull(page.Elements.Find("LoginButton"));
}
}
- Set your Company.Site as the startup project if its not already, and Tests/Default.aspx as the startup page; run from Visual Studio
Magic! This is my first use of a browser automation framework for integration testing and while it has its pitfalls, it does work and looks very promising.
Notes
- Looks like all your integration tests must exist in the same assembly
- Source code for LTAF is available so making this a configuration option is a possibility
- All tests must exist outside of namespaces
- Prefixing your integration tests assembly with App_Code can help work around assembly loading problems as the test runner searches for WebTestClass attributes
- Remember to have your production build process eliminate the Tests folder and associated integration test assembly
January 8th, 2009
So I had written a SmtpClientWrapper for System.Net.SmtpClient because the aforementioned class implements no interface making it impossible to perform dependency injection using it (as anyone who has to unit test any sort of email functionality can attest to). Then I had the idea that during production we may want to mirror email messages sent to a debugging email address so I also made a MirrorSmtpClientDecorator. Herein lies the problem - System.Net.MailMessage does not implement ICloneable and the MirrorSmtpClientDecorator.Send(MailMessage) either needs a copy or its going adversely affect MailMessage with a side affect (by altering the MailMessage.To MailAddressCollection for a second message sent to the debugging email addresses). I explored numerous ways to copy MailMessage and eventually I resorted to some brute force extension methods (to ObjectModel.Collection<T>; ShallowCopy and Replace, suck I know) but during my research I discovered some older MemoryStream serialization I refactored and felt its worth sharing.
The problem with this approach is that the class you want to deep copy requires that it be marked with the SerializableAttribute. I've noticed that a portion of my time is spent writing hackarounds for classes in the .NET BCL and MailMessage is no exception - yup no ICloneable and no SerializableAttribute either.
public class Serializer
{
public static void Serialize(Stream target, object source)
{
var formatter = new BinaryFormatter();
formatter.Serialize(target, source);
}
public static T Deserialize<T>(Stream source) where T : class
{
var formatter = new BinaryFormatter();
var obj = (T)formatter.Deserialize(source);
return obj;
}
}
public class MemorySerializer<T> : IDisposable where T : class
{
#region Member Variables
private readonly MemoryStream _ms;
#endregion
#region Constructors
public MemorySerializer(MemoryStream ms)
{
_ms = ms;
}
public MemorySerializer()
{
_ms = new MemoryStream();
}
#endregion
#region Methods
public MemorySerializer<T> Serialize(T obj)
{
Serializer.Serialize(_ms, obj);
_ms.Position = 0;
return this;
}
public T Deserialize()
{
return Serializer.Deserialize<T>(_ms);
}
#endregion
#region Implementation of IDisposable
public void Dispose()
{
if (_ms == null)
return;
try
{
_ms.Close();
}
finally
{
_ms.Dispose();
}
}
#endregion
}
public static class ObjectUtility
{
/// <summary>
/// Creates a deep copy of an object for any object which
/// supports binary serialization.
/// </summary>
/// <typeparam name="T">Type to serialize/deserialize.</typeparam>
/// <param name="source">Object to deep copy.</param>
/// <returns>New instance of the object with all properties and
/// fields that support serialization copied.</returns>
/// <remarks>This method isn't as fast as a provided Clone method
/// but when ICloneable is not implemented on a BCL class its a
/// pretty close substitute.</remarks>
public static T DeepCopy<T>(this T source) where T : class
{
T ret;
using (var ms = new MemorySerializer<T>())
{
ret = ms.Serialize(source).Deserialize();
}
return ret;
}
}