Software Mechanics
Why do we even have that lever?

Do classes need to be DI friendly?

April 25, 2008 10:31 by Chris

I was working on a version of my MSDN magazine Wiki sample (updated code for the April 16 drop coming soon) that used Unity instead of the one-off custom controller factory that is currently in there. I've posted on this before, and my previous conclusions were that it was pretty easy. Of course, I ran into a snag.

The one-off controller factory had this code in it:

[code=C#;ln=on;Original Code]ISpaceRepository GetConfiguredRepository(HttpRequestBase request)
{
    return new FileBasedSpaceRepository(request.MapPath("~/WikiPages"));
}[/code]

Basically, the FileBasedSpaceRepository takes a path to the directory where the files are stored. Of course, in a web app we need to use the server to map from a virtual-directory relative path to a physical file system path. Nothing new or unusual here.

But then I got into replacing this code with Unity, and immediately ran into the question: how do I get the container to call that MapPath method? Unity doesn't know anything about the web. And how does it get the current request?

So, I figured I'd use a child container, shove the request in there, and then use the static factory extension to resolve my string. My controller factory looks like this now:

[code=C#;ln=on;UnityTypeBasedControllerFactory.cs]public class UnityTypeBasedControllerFactory : DefaultControllerFactory
{
    IUnityContainer container;
    public UnityTypeBasedControllerFactory(IUnityContainer container)
    {
        this.container = container;
    }
    protected override IController GetControllerInstance(Type controllerType)
    {
        IUnityContainer requestContainer = container.CreateChildContainer()
            .RegisterInstance<RequestContext>(
                this.RequestContext,
                new ExternallyControlledLifetimeManager());
        return (IController)(requestContainer.Resolve(controllerType));
    }
}[/code]

So basically, what I'm doing here is that for each request, I spin up a child container, stuff the request context into the container, and then resolve the requested controller type. Pretty straightforward.

Next up is to configure the container.

[code=C#;ln=on;GetAPIConfiguredContainer method - first attempt]public static IUnityContainer GetAPIConfiguredContainer()
{
    IUnityContainer container = new UnityContainer()
        .RegisterType<ISpaceRepository, FileBasedSpaceRepository>()
        .Configure<InjectedMembers>()
            .ConfigureInjectionFor<FileBasedSpaceRepository>(
                new InjectionConstructor(
                    new ResolvedParameter<string>("PathToRepositoryFiles")))
            .Container
        .Configure<IStaticFactoryConfiguration>()
            .RegisterFactory<string>("PathToRepositoryFiles",
            c => c.Resolve().HttpContext.Request.MapPath("~/WikiPages"))
        .Container;
    return container;
}[/code]

This is a little grotesque. Instead of just configuring the string to be injected into the constructor, I have to resolve it through the container. I registered the factory delegate (on line 13) to grab the current RequestContext, then use it to resolve the string.

Lots of things wrong here. You can't put this into the config file becuase of that factory method delegate. The actual string is buried in the delegate, so it's not obvious where you're actually getting the path from. And to top it off, due to an unfortunate design decision on my part, the static factory ends up getting the parent container passed into the delegate rather than the child. So the Resolve<RequestContext> call fails.

I found something that works better. I created a new class, MappedPathFileBasedSpaceRepository, which takes the RequestContext and the path to be mapped in the constructor. It inherits from the original FileBasedSpaceRepository, and does the MapPath call before passing the resulting file system path down to the base class. This results in a much, MUCH shorter configuration of the container:

[code=C#;ln=on;GetAPIConfiguredContainer method - better]public static IUnityContainer GetAPIConfiguredContainer()
{
    IUnityContainer container = new UnityContainer()
        .RegisterType<ISpaceRepository, MappedPathFileBasedSpaceRepository>()
        .Configure<InjectedMembers>()
            .ConfigureInjectionFor<MappedPathFileBasedSpaceRepository>(
                new InjectionConstructor(typeof(RequestContext), "~/WikiPages"))
        .Container;
    return container;
}[/code]

I'm pretty happy with where I am now, except for one thing: MappedPathFileBasedSpaceRepository wouldn't exist if I hadn't plugged in the container. Is this just a case of finding a new dependency that I hadn't realized before? Or is it that the presence of the DI container is intruding on my domain model?

I'd love to get some feedback from folks on what you've done to handle issues like this. What's your opinion here?

(I think I like the "child container shove the request context in" approach, but I'd love to get opinions on that too.)


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:
Categories: .NET | Unity
Actions: E-mail | Permalink | Comments (4) | Comment RSSRSS comment feed

Comments

Comments are closed