adding to the windsor mvc controller factory

This is the sixth in a series of posts illustrating building a custom Windsor registration process. I'm currently test driving an implementation of a Windsor controller factory for ASP.NET MVC and have discovered that the ASP.NET MVC framework will be calling CreateController without the "Controller" suffix in the controller name.

I therefore need a new specification to address this:

[Observations]
public class when_a_controller_is_requested_from_the_factory_without_a_controller_suffix :
    observations_for_a_sut_with_a_contract<IControllerFactory, WindsorControllerFactory>
{
    private static string controllerName;
    private static Type controllerType;
    private static WindsorContainer container;
    private static IController returnedController;

    private context c = () =>
                        {
                            controllerName = "Fake";
                            controllerType = typeof (FakeController);
                            container = new WindsorContainer();
                            container.AddComponentLifeStyle(controllerType.FullName.ToLower(),
                                controllerType, LifestyleType.Transient);
                        };

    public override IControllerFactory create_sut()
    {
        return new WindsorControllerFactory(container);
    }

    because b = () => returnedController = sut.CreateController(null, controllerName);

    [Observation]
    public void should_return_a_known_instance_of_the_controller()
    {
        returnedController.should_be_an_instance_of<FakeController>();
    }
}

When this test runs it fails with a NullReferenceException on the return line of the CreateController method where the controllerType.FullName property is used on a null reference. I note this down so that I can write a specification for that a little later on.

For now, to implement this new requirement I write the code as follows:

public class WindsorControllerFactory : IControllerFactory
{
    private IWindsorContainer container;

    public WindsorControllerFactory(IWindsorContainer container)
    {
        this.container = container;
    }

    public IController CreateController(RequestContext requestContext, string controllerName)
    {
        var controllerType = GetControllerTypeFromLoadedAssemblies(controllerName);

        return container.Resolve<IController>(controllerType.FullName.ToLower());
    }

    private Type GetControllerTypeFromLoadedAssemblies(string controllerName)
    {
        Type controllerType = null;

        controllerName = EnsureCorrectFormattingOfControllerName(controllerName);

        var assemblies = AppDomain.CurrentDomain.GetApolloAssemblies();
        foreach (var assembly in assemblies)
        {
            controllerType = assembly.GetTypes().Where(t => t.Name == controllerName)
                                                .FirstOrDefault();
            if (controllerType != null)
                return controllerType;
        }

        return null;
    }

    private string EnsureCorrectFormattingOfControllerName(string controllerName)
    {
        if (!controllerName.EndsWith("Controller"))
            controllerName += "Controller";
        return controllerName;
    }

    public void ReleaseController(IController controller)
    {
        throw new NotImplementedException();
    }
}

I now have a new method that ensures the controller suffix is present before the type is obtained from the assemblies. It is now my intention to ensure this works for lowercase requests too, so I write a specification for that also:

[Observations]
public class when_a_controller_is_requested_from_the_factory_in_lower_case_without_a_suffix :
    observations_for_a_sut_with_a_contract<IControllerFactory, WindsorControllerFactory>
{
    private static string controllerName;
    private static Type controllerType;
    private static WindsorContainer container;
    private static IController returnedController;

    private context c = () =>
                        {
                            controllerName = "fake";
                            controllerType = typeof (FakeController);
                            container = new WindsorContainer();
                            container.AddComponentLifeStyle(controllerType.FullName.ToLower(),
                                                      controllerType, LifestyleType.Transient);
                        };

    public override IControllerFactory create_sut()
    {
        return new WindsorControllerFactory(container);
    }

    private because b = () => returnedController = sut.CreateController(null, controllerName);

    [Observation]
    public void should_return_a_known_instance_of_the_controller()
    {
        returnedController.should_be_an_instance_of<FakeController>();
    }
}

This specification is almost exactly the same as the previous one, so at this stage I might normally consider refactoring the test code itself and push common parts to a base class, but for clarity here on my blog I'm illustrating each specification's code in full. This test now fails with the same NullReferenceException, so I put the implementation code in as:

private string EnsureCorrectFormattingOfControllerName(string controllerName)
{
    controllerName = EnsureCapitalisationOfFirstCharacter(controllerName);

    if (!controllerName.EndsWith("Controller"))
        controllerName += "Controller";
    return controllerName;
}

private string EnsureCapitalisationOfFirstCharacter(string controllerName)
{
    var beginningCharacter = controllerName.Substring(0, 1);
    if (beginningCharacter != beginningCharacter.ToUpper())
        controllerName = beginningCharacter.ToUpper() + controllerName.Substring(1);
    return controllerName;
}

This test passes and it is now time to revisit the cause of the NullReferenceException:

[Observations]
public class when_an_unknown_controller_is_requested_from_the_controller_factory :
    observations_for_a_sut_with_a_contract<IControllerFactory, WindsorControllerFactory>
{
    private static string controllerName;
    private static IController returnedController;

    private context c = () => controllerName = "Unknown";

    because b = () => returnedController = sut.CreateController(null, controllerName);

    [Observation]
    public void should_return_gracefully_without_a_controller()
    {
        returnedController.should_be_null();
    }
}

The test fails with the familiar NullReferenceExceptiont that I want to get rid of, so the implementation is straightforward to resolve this:

public IController CreateController(RequestContext requestContext, string controllerName)
{
    var controllerType = GetControllerTypeFromLoadedAssemblies(controllerName);

    if(controllerType==null)
        return null;

    return container.Resolve<IController>(controllerType.FullName.ToLower());
}

I now have a working implementation that meets my current specifications. I would normally move onto specifying edge case specifications (such as empty or null string passed as the controllerName variable into CreateController) but I'll stop here for the purpose of this blog post.

In my next post I'll finish off the WindsorControllerFactory by implementing the ReleaseController method.



0 comments: