Wednesday, February 27, 2008

Shame On Me

Someone decided to publicly criticize my example code in my previous post by saying that it was “bad.”  While I don’t agree with the nature of the criticism (my blog *does* have comments, you know) I totally agree with the message.  It was bad code.  I would never place that in a production application.

The intent of the post was to demonstrate some ajax techniques, not to talk about how to write testable, decoupled controllers.  I suppose that example code should come with a bit of a disclaimer that says “hey, we are doing this here for simplicity’s sake, but obviously you’d abstract this out.”  Ideally I would have commented out the entire flickr section, because it’s totally not important in the context of the example.

In light of this topic, here is how a perfectly testable PhotosController might be written:

public class PhotosController : Controller
{        
    private readonly IPhotoRepository _photoRepository;

    public PhotosController(IPhotoRepository photoRepository)
    {
        _photoRepository = photoRepository;
    }

    [ControllerAction]
    public void Search()
    {            
        RenderView("Search", new Photo[] { });
    }        

    [ControllerAction]
    public void Find(string query, bool? ajax)
    { 
        
        var photos = _photoRepository.SearchPhotos(query);
          
        ViewData["query"] = query;
        ViewData["photos"] = photos;
        
        if(ajax == true)
        {
            RenderView("_images", photos);
        }
        else
        {
            RenderView("results");    
        }            
    }
}

Here you can clearly see our dependency on IPhotoRepository.  This guy can be anything we want, from a Flickr implementation, google images implementation, a file system directory, or even (gasp) a TEST implementation for writing unit tests!

Here is the IPhotoRepository, that we are using in the example:

public interface IPhotoRepository
{
    Photo[] SearchPhotos(string query);

asdf

}

This returns a Photo object (that I defined), which means whatever implementation you choose, you need to map to this object. This is a good thing because it removed my dependency on "FlickrPhoto" in the view itself.

Our FlickrPhotoRepository now looks like this:

public class FlickrPhotoRepository : IPhotoRepository
{
    private readonly IConfigSource _ConfigSource;

    public FlickrPhotoRepository(IConfigSource configSource)
    {
        _ConfigSource = configSource;
    }

    public Photo[] SearchPhotos(string query)
    {
        var flickr = new Flickr(_ConfigSource.GetSetting("flickr.api.key"));
        var results = flickr.PhotosSearch(query, TagMode.AnyTag, query, 40, 1);

        var photos = new List<Photo>();
        foreach (var item in results.PhotoCollection)
        {
            photos.Add(new Photo(item.SquareThumbnailUrl, item.Title, item.PhotoId));
        }

        return photos.ToArray();
    }
}

Wait a minute! This class now has it's own dependency on IConfigSource? Craziness! In our example code from before, I had accessed the Configuration singleton directly, which is punishable by hand-removal in Albonia. Thank goodness I don't live there. Now this class is decoupled from the actual configuration implementation and it could really be coming from a text file or database (whatever you want).

Wiring all this up is easy.  You could do it via XML, but I chose to do it directly in code:

private void InitializeWindsor()
{
    _container = new WindsorContainer();
    _container.AddComponent("config-source", typeof(IConfigSource), typeof(AppDomainConfigSource));        
    foreach(Type type in Assembly.GetExecutingAssembly().GetTypes())
    {             
        if(type.IsSubclassOf(typeof(Controller)))
        {
            _container.AddComponent(type.Name, type);
        }
    }
}

Also note that I'm using WindsorControllerFactory from MVCContrib, so now my controller can be created (and each of it's dependencies ( and their dependencies (and their Dependencies)))... I think you get the idea.

And there you have it.  A perfectly decoupled and testable modification for my previous example code.  As you were….

Wednesday, February 27, 2008 1:58:08 PM (Central Standard Time, UTC-06:00)
Ben,

You might consider checking out the batch registration facility in Castle, this will allow you to do the same type of registration without individual component registration in XML and supports inclusion/exclusion rules.
Wednesday, February 27, 2008 3:09:07 PM (Central Standard Time, UTC-06:00)
Will do, thanks Nick!

I was actually thinking of picking up Binsor, the component registration there seems pretty painless and powerful.
Ben Scheirman
Wednesday, February 27, 2008 9:56:03 PM (Central Standard Time, UTC-06:00)
Ben, I think most everyone understood the nature of your post. And it's reasonable to shrug off the situation since it's become a pattern.
Wednesday, February 27, 2008 10:08:57 PM (Central Standard Time, UTC-06:00)
You beat me to this post bro!! Thanks for this post nonetheless.
Comments are closed.
Savings - Car Insurance - Mortgage - Secured Loans