Castle Windsor/DelegatingHandler/IPrincipal Dependency Injection (DI)/Inversion of Control (IoC) in ASP.NET Web API
Asked Answered
M

1

4

I decided to clean this post up and I posted a sample project at ge.tt/3EwoZEd/v/0?c

Spent around 30 hours on this already and still can't figure it out... help would be really appreciated!

I have an ASP.NET Web API solution that uses this code: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/ to implement "Basic HTTP authentication in ASP.NET Web API using Message Handlers". I'm new to IoC/DI and I'm trying to get this to work with Castle Windsor.

I've been trying a lot of different things but I get 1 of the following errors depending on what I did wrong:

  • "Looks like you forgot to register the http module Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule"
  • "Object reference not set to an instance of an object." for the PrincipalProvider in BasicAuthMessageHandler
  • "No component for supporting the service *.DummyPrincipalProvider was found"

Below is my code:


Global.asax.cs:

private static IWindsorContainer _container;

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    var config = (CustomErrorsSection)ConfigurationManager.GetSection("system.web/customErrors");

    IncludeErrorDetailPolicy errorDetailPolicy;

    switch (config.Mode)
    {
        case CustomErrorsMode.RemoteOnly:
            errorDetailPolicy
                = IncludeErrorDetailPolicy.LocalOnly;
            break;
        case CustomErrorsMode.On:
            errorDetailPolicy
                = IncludeErrorDetailPolicy.Never;
            break;
        case CustomErrorsMode.Off:
            errorDetailPolicy
                = IncludeErrorDetailPolicy.Always;
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }

    GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = errorDetailPolicy;

    ConfigureWindsor(GlobalConfiguration.Configuration);

    GlobalConfiguration.Configuration.MessageHandlers.Add(new BasicAuthMessageHandler()
    {
        PrincipalProvider = _container.Resolve<IProvidePrincipal>()
    });
}

public static void ConfigureWindsor(HttpConfiguration configuration)
{
    // Create / Initialize the container  
    _container = new WindsorContainer();

    // Find our IWindsorInstallers from this Assembly and optionally from our DI assembly which is in abother project.  
    _container.Install(FromAssembly.This());
    _container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel, true));

    //Documentation http://docs.castleproject.org/Windsor.Typed-Factory-Facility.ashx  
    // Set the WebAPI DependencyResolver to our new WindsorDependencyResolver  
    var dependencyResolver = new WindsorDependencyResolver(_container);
    configuration.DependencyResolver = dependencyResolver;
}

Windsor Installer:

public class PrincipalsInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly().BasedOn<DelegatingHandler>());

        container.Register(
            Component.For<IProvidePrincipal>().ImplementedBy<DummyPrincipalProvider>()
        );
    }
}

Modified DummyPrincipalProvider (from the original I got from the URL above):

public class DummyPrincipalProvider : IProvidePrincipal
{
    private IUserRepository _userRepo;

    public DummyPrincipalProvider(IUserRepository userRepo)
    {
        this._userRepo = userRepo;
    }

    public IPrincipal CreatePrincipal(string username, string password)
    {
        try
        {
            if (!this._userRepo.ValidateUser(username, password))
            {
                return null;
            }
            else
            {
                var identity = new GenericIdentity(username);
                IPrincipal principal = new GenericPrincipal(identity, new[] { "User" });

                if (!identity.IsAuthenticated)
                {
                    throw new ApplicationException("Unauthorized");
                }

                return principal;
            }
        }
        catch
        {
            return null;
        }
    }
}

WindsorDependencyResolver.cs:

internal sealed class WindsorDependencyResolver : IDependencyResolver
{
    private readonly IWindsorContainer _container;

    public WindsorDependencyResolver(IWindsorContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }

        _container = container;
    }

    public object GetService(Type t)
    {
        return _container.Kernel.HasComponent(t) ? _container.Resolve(t) : null;
    }

    public IEnumerable<object> GetServices(Type t)
    {
        return _container.ResolveAll(t).Cast<object>().ToArray();
    }

    public IDependencyScope BeginScope()
    {
        return new WindsorDependencyScope(_container);
    }

    public void Dispose()
    {

    }
}

WindsorDependencyScope.cs:

internal sealed class WindsorDependencyScope : IDependencyScope
{
    private readonly IWindsorContainer _container;
    private readonly IDisposable _scope;

    public WindsorDependencyScope(IWindsorContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }
        _container = container;
        _scope = container.BeginScope();
    }

    public object GetService(Type t)
    {
        return _container.Kernel.HasComponent(t) ? _container.Resolve(t) : null;
    }

    public IEnumerable<object> GetServices(Type t)
    {
        return _container.ResolveAll(t).Cast<object>().ToArray();
    }

    public void Dispose()
    {
        _scope.Dispose();
    }
}
Mccormac answered 1/4, 2013 at 1:13 Comment(0)
U
7

I assume IProvidePrincipal is your own implementation. Best way, the only one IMHO, to use an IoC container is the Composition Root. The entry point/composition root for web api has been well explained by ploeh's blog. DelegatingHandler are not part of the "request resolving", so you may choose to resolve it within global asax Application_start where the container lives as private variable.

GlobalConfiguration.Configuration.MessageHandlers.Add(container.Resolve<BasicAuthMessageHandler>());

If you properly registered your handler and all its dependencies in the container, nothing else has to be done: handler instance you extracted from the container and added among MessageHandlers will have an IPrincipalProvider and (I)UserRepository. Keep in mind BasicAuthMessageHandler will act a singleton, so if you need a new instance of (I)UserRepository on each method call... you may consider TypedFactory to create your (I)UserRepository as late dependencies

Of course, any component starting from you top graph component have to be register in the container.

That's the easy way... in case you need somenthing more sophisticate, you may end up creating your "composition root" for DelegatingHandlers as well.

BTW: never, ever, doing somenthing like UserRepository userRepo = new UserRepository(); or PrincipalProvider = new DummyPrincipalProvider()

none of the "Behaviour instance" should be created explicitly: container take care of providing right instance at the right time...

As per Jon Edit: now DummyPrincipalProvider looks fine: just keep in mind since DummyPrincipalProvider is created among the message handler(act as singleton due to global registration), you are reusing always same instance.

Instead of your plumbing

var dependencyResolver = new WindsorDependencyResolver(_container);
configuration.DependencyResolver = dependencyResolver;

I rather use ploeh implementation(see above).

Your registration

container.Register(
    Component.For<IProvidePrincipal>().ImplementedBy<DummyPrincipalProvider>()
    .UsingFactoryMethod(kernel => kernel.Resolve<DummyPrincipalProvider>())
);

should be replaced with

container.Register(
    Component.For<IProvidePrincipal>().ImplementedBy<DummyPrincipalProvider>()
);

that's wrong... container has to resolve it, not you explicitly

GlobalConfiguration.Configuration.MessageHandlers.Add(new BasicAuthMessageHandler());

stick with my configuration as above: BasicAuthMessageHandler resolved via container.

Let me know if it works.

PS: You registered the TypedFactory facility in the container, but you are not using it... just to let you know. You registered DelegatingHandler(s) as Transient, but keep in mind they gonna be "singleton" by design: adding it to the MessageHandlers collection imply they gonna be reused on each request.

As per Jon Edit 2:

I added a sample on github. You should be able to build it and run it using NuGet Package Restore

Your issue about PerWebRequestdepends on the depencies of UserRepository on the NHibernate factory session creating session "PerWebRequest": you cannot resolve IPrincipalProvider->IUserRepository->ISession in Application_Start due to HttpContext. If you really need a IUserRepositry working w/ IPrincipalProvider dependency has to be to a IUserRepositoryFactory(TypedFactory) instead. I tried to fix your sample using the typed factory and it works, but than I had an issue w/ NHibernate configuration and since I'm not an expert of that, I didn't go any further.

If you need help w/ the factory thing... LMK and I'll update my git sample using a factory within the DummyPrincipalProvider

Ut answered 2/4, 2013 at 7:59 Comment(14)
Thank you so much for your response! I was afraid nobody would answer. I edited my post above to give some insight of what I've been trying if you have more time...Mccormac
I disagree, but windsor resolution wise... try this: GlobalConfiguration.Configuration.MessageHandlers.Add(new BasicAuthMessageHandler() { PrincipalProvider = _container.Resolve<IProvidePrincipal>() }); since you registered so in the container....Ut
Firstly, I just wanted to thank you again Christiano. I really appreciate all your help. As for the windsor resolution, I've tried resolving using IProvidePrincipal and I get the error "Looks like you forgot to register the http module Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule". It also states that I should add "Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule" to the web.config modules and I did (in both system.web/httpModules and webServer/modules) but still no luck.Mccormac
I've been checking online and people are saying that you can't resolve in Application_Start()? I added my Application_Start() code to my original post so you can see it. In the end, I really just want to be able to check authentication and header values in an ASP.NET Web API (v4). I've been searching online and it's odd I can't seem to find a good example of doing this. Either way though, I think the way it was setup before (and working without IoC/DI) should work with IoC/DI.Mccormac
Do you have a git account? If so add your project and I'll try to fork and fix it as soon as I can. I don't like the approach of using IDepencyresolver. I'm for IHttpControllerActivator as per ploeh post, but I'm not saying that's the reason of your malfunctioning. At the first sight I don't see anything wrong... BTW I never had issue resolving, if needed, in the Application_start...Ut
About "PerWebRequestLifestyleModule", yes you have to register it in the web.config(registration may not required w/ 3.2) as long as you use LifeStyle.PerWebRequest but I don't see any of that Lifestyle in your sampleUt
I don't have a git account but I uploaded to 2 free services (in case you have issues with one) FileDropper and GE.TT (no registrations required): filedropper.com/mysample1 or ge.tt/9yYtC8d/v/0 In the root folder I included the SQL script to create the database as well. The API website should be set as the start project. Thank you!Mccormac
As for using IHttpControllerActivator, you'll see under the "Plumbing" folder 2 hidden files "WindsorApiControllerFactory.cs" and "WindsorControllerFactory". I was using that before but I wanted the controllers to work AND have the API controllers work and using IDependencyResolver seemed to do the trick. You'll also notice I have a hidden file under Controllers for "HomeController.cs" that is a controller and than I have "VenueController.cs" that is an API controller.Mccormac
I also thought I'd throw this out there in case, but it's a REST API and I use this client to test: code.google.com/p/rest-clientMccormac
Another thing I'm noticing is that if I remove the [Authorize] tag from the Venue Controller, remove the "GlobalConfiguration.Configuration.MessageHandlers.Add(new BasicAuthMessageHandler()" line from the Global.asax.cs and call localhost:56126/venue?venueid=1, I get the error "The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json;"... why am I having so many issues!Mccormac
Looks like the second link is working anymore so I re-uploaded again. ge.tt/3EwoZEd/v/0?cMccormac
Resolving seems to be working now but I keep getting "ExecuteReader requires an open and available Connection. The connection's current state is closed.". Is this because of the lifestyles now being singleton? Would I be able to see how you got MySample1 working? I can figure out the NHibernate issues later, I'm still just trying to get this DI/IoC working.Mccormac
Just wanted to say thank you again! I got it working FINALLY (sort of - it still does double calls or gives me db errors but thats nhibernate related)!Mccormac
you are welcome and good luck w/ windsor... "best of breed, mature Inversion of Control container available for .NET and Silverlight"Ut

© 2022 - 2024 — McMap. All rights reserved.