【问题标题】:Castle Windsor/DelegatingHandler/IPrincipal Dependency Injection (DI)/Inversion of Control (IoC) in ASP.NET Web APIASP.NET Web API 中的 Castle Windsor/DelegatingHandler/IPrincipal 依赖注入 (DI)/控制反转 (IoC)
【发布时间】:2013-04-07 05:18:27
【问题描述】:

我决定清理这篇文章,并在ge.tt/3EwoZEd/v/0?c发布了一个示例项目

已经花了大约 30 个小时来解决这个问题,但仍然无法弄清楚...非常感谢您的帮助!

我有一个 ASP.NET Web API 解决方案,它使用以下代码:http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/ 来实现“使用消息处理程序在 ASP.NET Web API 中进行基本 HTTP 身份验证”。我是 IoC/DI 的新手,我正在尝试让它与 Castle Windsor 一起使用。

我一直在尝试很多不同的事情,但根据我做错了什么,我得到了以下错误之一:

  • “您好像忘记注册 http 模块 Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule”
  • “对象引用未设置为对象的实例。”用于 BasicAuthMessageHandler 中的 PrincipalProvider
  • “未找到支持服务 *.DummyPrincipalProvider 的组件”

下面是我的代码:


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 安装程序:

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

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

修改后的 DummyPrincipalProvider(来自我从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();
    }
}

【问题讨论】:

    标签: c# asp.net dependency-injection inversion-of-control castle-windsor


    【解决方案1】:

    我假设 IProvidePrincipal 是您自己的实现。 恕我直言,使用 IoC 容器的唯一方法是 Composition Rootploeh's blog 已经很好地解释了 web api 的入口点/组合根。 DelegatingHandler 不是“请求解析”的一部分,因此您可以选择在全局 asax Application_start 中解析它,其中容器作为私有变量存在。

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

    如果您在容器中正确注册了您的处理程序及其所有依赖项,则无需执行任何其他操作:您从容器中提取并添加到 MessageHandlers 中的处理程序实例将具有 IPrincipalProvider 和 (I)UserRepository。请记住,BasicAuthMessageHandler 将充当单例,因此如果您在每个方法调用上都需要一个 (I)UserRepository 的新实例...您可以考虑使用TypedFactory 将您的 (I)UserRepository 创建为后期依赖项

    当然,从你的顶部图组件开始的任何组件都必须是register in the container

    这是最简单的方法...如果您需要更复杂的东西,您可能最终也会为 DelegatingHandlers 创建“组合根”。

    顺便说一句:永远,永远,做类似的事情 UserRepository userRepo = new UserRepository(); 或 PrincipalProvider = new DummyPrincipalProvider()

    不应显式创建任何“行为实例”:容器负责在正确的时间提供正确的实例...

    根据 Jon 编辑: 现在 DummyPrincipalProvider 看起来不错:请记住,由于 DummyPrincipalProvider 是在消息处理程序中创建的(由于全局注册而充当单例),因此您始终重用相同的实例。

    代替你的管道

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

    我宁愿使用 ploeh 实现(见上文)。

    您的注册

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

    应该替换为

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

    这是错误的......容器必须解决它,而不是你明确

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

    坚持我上面的配置:BasicAuthMessageHandler 通过容器解析。

    让我知道它是否有效。

    PS:您在容器中注册了 TypedFactory 设施,但您没有使用它……只是为了让您知道。 您将 DelegatingHandler(s) 注册为 Transient,但请记住,它们在设计上将是“单例”:将其添加到 MessageHandlers 集合意味着它们将在每个请求中被重用。

    根据 Jon Edit 2:

    我添加了sample on github。您应该能够使用 NuGet Package Restore 构建和运行它

    您关于 PerWebRequest 的问题取决于 NHibernate 工厂会话创建会话“PerWebRequest”上 UserRepository 的依赖关系:由于 HttpContext,您无法解析 Application_Start 中的 IPrincipalProvider->IUserRepository->ISession。如果你真的需要一个 IUserRepositry 工作 w/ IPrincipalProvider 依赖必须是一个 IUserRepositoryFactory(TypedFactory) 代替。 我尝试使用类型化工厂修复您的示例并且它可以工作,但是我遇到了 NHibernate 配置问题,并且由于我不是这方面的专家,所以我没有再进一步。

    如果您需要工厂方面的帮助...LMK 和我将使用 DummyPrincipalProvider 中的工厂更新我的 git 示例

    【讨论】:

    • 非常感谢您的回复!我怕没人回答。如果您有更多时间,我编辑了上面的帖子,以便了解我一直在尝试的内容...
    • 我不同意,但温莎决议明智...试试这个: GlobalConfiguration.Configuration.MessageHandlers.Add(new BasicAuthMessageHandler() { PrincipalProvider = _container.Resolve() });因为你在容器中注册了所以......
    • 首先,我想再次感谢克里斯蒂亚诺。我真的很感谢你的帮助。至于温莎解决方案,我尝试使用 IProvidePrincipal 解决,但出现错误“看起来您忘记注册 http 模块 Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule”。它还指出我应该将“Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule”添加到 web.config 模块中,我做了(在 system.web/httpModules 和 webServer/modules 中)但仍然没有运气。
    • 我一直在网上查,有人说不能在Application_Start()中解决?我将 Application_Start() 代码添加到我的原始帖子中,以便您查看。最后,我真的只想能够检查 ASP.NET Web API (v4) 中的身份验证和标头值。我一直在网上搜索,奇怪的是我似乎找不到这样做的好例子。不管怎样,我认为之前的设置方式(并且在没有 IoC/DI 的情况下工作)应该适用于 IoC/DI。
    • 你有 git 账号吗?如果是这样,请添加您的项目,我会尽快分叉并修复它。我不喜欢使用 IDepencyresolver 的方法。根据 ploeh 帖子,我支持 IHttpControllerActivator ,但我并不是说这是您出现故障的原因。乍一看,我没有发现任何问题...顺便说一句,如果需要,我从未在 Application_start 中解决过问题...
    猜你喜欢
    • 2010-09-13
    • 1970-01-01
    • 1970-01-01
    • 2015-07-30
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    相关资源
    最近更新 更多