views:

310

answers:

1

I'm new to Castle Windsor, so go easy!!

I am developing an MVC web app and one of my controllers has a dependency on knowing the current request Url. So in my Application_Start I initialise a WindsorContainer (container below), register my controllers and then try the following...

container.AddFacility<FactorySupportFacility>();

container.Register(Component.For<Uri>().LifeStyle.PerWebRequest.UsingFactoryMethod(() => HttpContext.Current.Request.Url));

However when I run up my web app I get an exception that my controller...

is waiting for the following dependencies:

Keys (components with specific keys) - uri which was not registered.

The controller it is trying to instantiate has the following signature:

public MyController(Uri uri)

For some reason it is not running my factory method?

However if I change the controller signature to:

public MyController(HttpContext httpContext)

and change the registration to:

container.Register(Component.For<HttpContext>().LifeStyle.PerWebRequest.UsingFactoryMethod(() => HttpContext.Current));

Then everything works a treat!!

What am I missing when trying to register a Uri type? Its seems exactly the same concept to me? I must be missing something!?

Updated:

I have done some more debugging and have registered both the Uri and the HttpContext using the factory methods shown above. I have added both types as parameters on my Controller constructor.

So to clarify I have a both Uri and HttpContext types registered and both using the FactoryMethods to return the relevant types from the current HttpContext at runtime. I also have registered my controller that has a dependency on these types.

I have then added a breakpoint after I have registration and have taken a look at the GraphNodes on the kernal as it looks like it stores all the dependencies. Here it is:

[0]: {EveryPage.Web.Controllers.BaseController} / {EveryPage.Web.Controllers.BaseController}

[1]: {EveryPage.Web.Controllers.WebpagesController} / {EveryPage.Web.Controllers.WebpagesController}

[2]: {System.Web.HttpContext} / {System.Web.HttpContext}

[3]: {Castle.MicroKernel.Registration.GenericFactory1[System.Web.HttpContext]} / {Castle.MicroKernel.Registration.GenericFactory1[System.Web.HttpContext]}

[4]: {System.Uri} / {System.Uri}

[5]: {Castle.MicroKernel.Registration.GenericFactory1[System.Uri]} / {Castle.MicroKernel.Registration.GenericFactory1[System.Uri]}

It looks as though it has registered my Controller and both the types, plus it has the Factories. Cool.

Now if I drill into the WebpagesController and take a look at its dependencies it only has 1 registered:

[0]: {System.Web.HttpContext} / {System.Web.HttpContext}

Now shouldn't this have 2 registered dependencies as it takes a HttpContext and Uri on its constructor??

Any ideas? Am I barking up the wrong tree?

+3  A: 

UPDATE3:

There's new extension point in Windsor trunk now that you can use easily for that.

UPDATE2:

Turns out that I was right from the start (well kind of). Uri is a class, but Windsor treats it as a primitive. There are still at least two quick solutions to this:

  1. Wrap the Uri in some kind of IHasUri or something and take dependency on that interface in your controller

    public class FooController
    {
        public IHasUri CurrentUri { get; set; }
    
    
    
    public void SomeAction()
    {
        var currentUri = CurrentUri.GetCurrentUri();
        // do something with the uri
    }
    
    }
  2. Tell the Windsor you don't want it to treat Uris like some primitive (but like a lady).

You need a IContributeComponentModelConstruction implementation for that:

public class UriIsAServiceNotAParameter:IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        if (model.Service != typeof(UsesUri)) // your controller type here
             return;

        foreach (var constructor in model.Constructors)
        {
            foreach (var dependency in constructor.Dependencies)
            {
                if(dependency.TargetType ==typeof(Uri))
                {
                    dependency.DependencyType = DependencyType.Service;
                }
            }
        }
    }
}

and add it to the container:

container.Kernel.ComponentModelBuilder.AddContributor(new UriIsAServiceNotAParameter());

There's also the most correct way of doing this, which means telling Windsor not to register Uris as primitives in the first place, rather than fixing this afterwards, but this would require reaching into the deepest guts of the kernel, and the result is far more code (though a straightforwad one) than the workarounds outlined above.

Krzysztof Koźmic
System.Uri isn't a struct, it's a class and it's not even sealed: http://msdn.microsoft.com/en-us/library/system.uri.aspx
Mark Seemann
Oh my. You're right.
Krzysztof Koźmic
I have performed an AddFacility on the container but not on the kernel.... is there a difference? If I add it on the kernel I still get the same error, if I add it on both I get "An item with the same key has already been added."The thing I don't understand is why it works fine for the HttpContext but not the Uri? I am currently having to pass the whole HttpContext in just to get the requested url!Is the two step solution you defined used if it was a struct? It seems like a workaround for something?Thanks for your help.
j3ffb
You either add to container or kernel. Container forwards it to kernel underneath.The solution I suggested is not a two step solution - these are two separate solutions, pick your favorite. Can you add some logging to your factory method (the delegate you pass in when registering just the URI, to see it get's properly put in the container?)
Krzysztof Koźmic
Krzysztof,Thanks for your help, I have tried solution no. 2, unfortunately it doesn't seem to set the property in time, so it can be used within the code of the constructor?? I set a debug point and it was null when I was inside the constructor. Plus I'm not sure I like the encapsulation of this solution.I really want to get down to why the original code doesn't work, as I see this as the solution.I have added some more finding to my question.
j3ffb

related questions