Friday, December 21, 2007

WatiN testing ASP.NET - app startup time issues

I started a test project to test a web app. One thing I noticed was that due to application startup, occasionally the first couple of tests fail with TimeoutExceptions because the application takes too long to compile some of the pages.

So, I came up with the following to work around this:

using SHDocVw;
using WatiN.Core;
using WatiN.Core.Exceptions;

namespace Tests {
public class TestBase {
private static bool _siteSetupRun = false;

public TestBase() {
if (!_siteSetupRun) {
setupSite();
_siteSetupRun = true;
}
}

private static void setupSite() {
bool ok = false;
using (IE ie = new IE()) {
object nil = null;
((InternetExplorer)ie.InternetExplorer).Navigate("http://testsite/",
ref nil, ref nil, ref nil, ref nil);
while (!ok) {
try {
ie.WaitForComplete();
ok = true;
} catch (TimeoutException tex) {
if (!tex.Message.Contains("'Internet Explorer busy'")) {
throw;
}
}
}
}
}
}
}

This needs to run before any unit tests.

Sunday, December 9, 2007

Resolving extension method conflicts using a Proxy pattern.

If you happen to get this error: "The call is ambiguous between the following methods or properties: ...", here is how you would fix it:

Problem Setup


Lets say you are using the following library:

namespace PRI.Interfaces {
public interface IEntity {
string Name { get; set; }
}
}

namespace PRI {
public class DataEntity:Interfaces.IEntity {
public string Name { get; set; }
}
}

And the library authors decide to release an extension method (along with a couple of others you wish to use):

 
namespace PRI.Extensions {
    using PRI.Interfaces;
 
    public static class Entity {
        public static string CapitalizeName(this IEntity entity) {
            entity.Name = entity.Name.ToUpper();
            return entity.Name;
        }
    }
}

Meanwhile you are using a third party extension method library which already contains this method:

 
namespace Contoso.Extensions {
    using PRI.Interfaces;
    using System.Text;
 
    public static class Entity {
        public static string CapitalizeName(this IEntity entity) {
            StringBuilder sb = new StringBuilder(entity.Name.Length);
            string[] words = entity.Name.Split(new char[] { ' ' });
            foreach(string word in words) {
                sb.Append(char.ToUpper(word[0]));
                sb.Append(word.Substring(1).ToLower());
                sb.Append(" ");
            }
            entity.Name = sb.ToString().Trim();
            return entity.Name;
        }
    }
}


The Problem


This is a problem because you can no longer call the CapitalizeName method as an extension method if you happen to have both namespaces in your using constructs. As long as you are using functionality from both classes where the names of the methods are different you will not have any conflicts and there will not be any problems using your code. Unfortunately, the moment you add a call to the CapitalizeName method you will get a compile time error.


namespace Program {
    using System;
    using PRI;
    using PRI.Extensions;
    using Contoso.Extensions;
 
    class Program {
        static void Main(string[] args) {
            DataEntity dataEntity = new DataEntity() { Name = "frank smith" };
            dataEntity.CapitalizeName();
            Console.WriteLine(dataEntity.Name);
        }
    }
}

This will not compile because CapitalizeName cannot be resolved.

Enter the Proxy Pattern


Because extension methods are static methods and can be called just as any other static method gets called, you can build a wrapper class around these third party extension methods in order to resolve the conflicts that the method names impose.

namespace Program.ExtensionResolvers {
    using PRI.Interfaces;
 
    internal static class Resolver {
        public static string CapitalizeName(this IEntity entity) {
            return PRI.Extensions.Entity.CapitalizeName(entity);
        }
        public static string ConvertNameToTitleCase(this IEntity entity) {
            return Contoso.Extensions.Entity.CapitalizeName(entity);
        }
    }
}

Using the Solution


The extension method proxy class can now be used in order to resolve the naming conflict.

namespace Program {
    using System;
    using PRI;
    using ExtensionResolvers;
 
    class Program {
        static void Main(string[] args) {
            DataEntity dataEntity = new DataEntity() { Name = "frank smith" };
            dataEntity.ConvertNameToTitleCase();
            Console.WriteLine(dataEntity.Name);
        }
    }
}

Recommendation


If you are going to use more than 1 extension method library ever in the course of working on a project, I believe it is awfully important to encapsulate the extension methods you will be using into a proxy class (or several such classes) as I have shown here. It is probably best to keep this class internal as you wouldn't want to continue to pollute the function name domain to other third party libraries which are using your code.

I will argue that all calls to extension methods from third party libraries be internalized in this way. Doing so adds value (you can add xml documentation to these methods for your coworkers and the R# tools will be able to find usages and such) and it better decouples you from minor API changes in the extension libraries.

--
Thanks to Peter Ritchie on the altnetconf mailing list for most of the above code in the first section of this post.

Visual Studio 2008 first impressions

ok, ...

I will not be using VS 2008 until Resharper 4.0 EAP begins. I began starting to use 2008 this evening for my next post and I almost immediately realized just how dependent on R# I have become. As I began the first things I noticed (within the first 5 seconds) were:
  • Intellisense was different and I am not used to it.
  • I had forgotten where StringBuilder was located (R# will let you know when you reference a class that is not in a namespace you are using).
  • I really miss the error detection system.
  • Alt+Enter
  • F6
  • F2
  • Ctrl+B
  • Ctrl+T (I have binded this shortcut to Resharper.UnitTest.ContextDebug; more on that at some later time)
Other than that, I really liked my initial impressions:
  • Startup is much faster.
  • F1 doesn't hang VS for 5 minutes (I really hate this shortcut and I remove it from my system when possible because I tend to accidentally press it when going for F2 sometimes)
  • Hidden toolbox tabs show up faster when moused over.
  • Compilation seems faster.
  • VS seems to close faster.
So, in general, I really like it. Without R# I will not be using it.

Thursday, December 6, 2007

Job Hunting - questions to ask

Recently I asked a couple of questions openly to a person who posted a job offer* on the alt.net list which I think are important for discovering the attitude and environment of the potential employer.

These are more or less the questions I asked:
  • Does the company use OSS?

  • How does the company view OSS solutions?

  • Are you stuck with basic VS or do you get so use R# or the DevExpress plugins (or other productivity tools)?

  • Do your developers get to blog (or is that shunned)?

  • Are they allowed to work on OSS projects in their spare time?

  • Are they allowed to do so on company time for any projects the company (or even just the group) is using?

I believe these questions provide valuable insight into the employer.

*Note: I am not currently looking for a job, but if an offer comes up and it is significantly better than my current position I will look into it. It had better have a good pay raise, great benefits, not move me from Bozeman Montana, provide a flexible schedule, allow me to work from home when I want to and give me the flexibility to work on the projects I want to be working on. Ok, maybe that is a little exaggerated, but it had better be a very very good deal.

Tuesday, December 4, 2007

Object to DTO and back in an efficient manner

Reading Oren's post about converting an object to a DTO I got to thinking. Why do all that work of reassigning a bunch of properties if you can avoid it? The DTO is probably going to be serialized before being sent over the wire and only public properties are serialized; it doesn't make much sense to me to run a transform which would need to run at least 1 delegate (1 for every property to property transform), instantiate a new DTO, instantiate a List, instantiate an enumerator and enumerate over a list and instantiate a transformation class just to call a single method on it. It seems to me that most of this extra work can be avoided.

Here is a naive approach I would consider before running to lists of lambda expressions:

internal class DbOrder {
    public string Name;
    public int Id;
}
 
public class Order {
    private DbOrder _dbOrder;
 
    private Order(DbOrder o) {
        _dbOrder = o;
    }
 
    internal DbOrder DbOrder {
        get {
            if (_dbOrder == null) {
                _dbOrder = new DbOrder();
            }
            return _dbOrder;
        }
    }
 
    public string Name {
        get { return DbOrder.Name; }
        set { DbOrder.Name = value; }
    }
 
    public int Id {
        get { return DbOrder.Id; }
        set { DbOrder.Id = value; }
    }
 
    public static explicit operator Order(OrderDTO obj) {
        return new Order(obj.DbOrder);
    }
}
 
public class OrderDTO {
    private DbOrder _dbOrder;
 
    private OrderDTO(DbOrder o) {
        _dbOrder = o;
    }
 
    internal DbOrder DbOrder {
        get {
            if (_dbOrder == null) {
                _dbOrder = new DbOrder();
            }
            return _dbOrder;
        }
    }
 
    public string CustomerName {
        get { return DbOrder.Name; }
        set { DbOrder.Name = value; }
    }
 
    public int ID {
        get { return DbOrder.Id; }
        set { DbOrder.Id = value; }
    }
 
    public static explicit operator OrderDTO(Order obj) {
        return new OrderDTO(obj.DbOrder);
    }
}


The reason I would consider doing this this way is because both the Business class and the DTO class are nothing more than proxies to the data. Since this is the case, a Proxy pattern seems to me to be the best way to treat them.