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

99封情书 提交于 2019-12-21 05:23:16

问题


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();
    }
}

回答1:


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



来源:https://stackoverflow.com/questions/15736402/castle-windsor-delegatinghandler-iprincipal-dependency-injection-di-inversion

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!