【问题标题】:ASP Web Api - IoC - Resolve HttpRequestMessageASP Web Api - IoC - 解析 HttpRequestMessage
【发布时间】:2012-10-10 06:34:57
【问题描述】:

我正在尝试使用 ASP.NET WebAPI 设置 Castle Windsor。

我也在使用 Hyprlinkr 包 (https://github.com/ploeh/Hyprlinkr),因此需要将 HttpRequestMessage 实例注入到我的控制器的依赖项之一中。

我正在关注 Mark Seemann 的这篇文章 - http://blog.ploeh.dk/2012/04/19/WiringHttpControllerContextWithCastleWindsor.aspx,但我发现虽然 API 运行,但当我调用它时,请求只是挂起。没有错误信息。就好像它处于一个无限循环中。它挂在我的自定义 ControllerActivator 中对 Resolve 的调用

我认为我的某些 Castle 注册有误。如果我删除了上面文章中提到的那些,那么我可以成功调用 API(尽管没有我需要解决的依赖关系)

有什么想法吗?

代码如下

//Global.asax
public class WebApiApplication : HttpApplication
{
    private readonly IWindsorContainer container;

    public WebApiApplication()
    {
        container = 
            new WindsorContainer(
                new DefaultKernel(
                    new InlineDependenciesPropagatingDependencyResolver(), 
                    new DefaultProxyFactory()), 
                new DefaultComponentInstaller());

        container.Install(new DependencyInstaller());
    }

    protected void Application_Start()
    {        
        GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new WindsorCompositionRoot(this.container));
    }

// installer
public class DependencyInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.AddFacility<TypedFactoryFacility>();

        container.Register(
            Component.For<ValuesController>()
                .Named("ValuesController")
                .LifeStyle.PerWebRequest,

            Component.For<IResourceLinker>()
                .ImplementedBy<RouteLinker>()
                .LifeStyle.PerWebRequest,

            Component.For<IResourceModelBuilder>()
                .ImplementedBy<ResourceModelBuilder>()
                .LifeStyle.PerWebRequest,

                Component.For<HttpRequestMessage>()
                .Named("HttpRequestMessage")
                .LifeStyle.PerWebRequest
            );
    }
}

//Activator

public class WindsorCompositionRoot : IHttpControllerActivator
{
    private readonly IWindsorContainer container;

    public WindsorCompositionRoot(IWindsorContainer container)
    {
        this.container = container;
    }

    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        var controller = (IHttpController)this.container.Resolve(controllerType, new { request = request });

        request.RegisterForDispose(
            new Release(
                () => this.container.Release(controller)));

        return controller;
    }

// DependencyResolver   
public class InlineDependenciesPropagatingDependencyResolver : DefaultDependencyResolver
{
    protected override CreationContext RebuildContextForParameter(CreationContext current, Type parameterType)
    {
        if (parameterType.ContainsGenericParameters)
        {
            return current;
        }

        return new CreationContext(parameterType, current, true);
    }
}

编辑**************** 附加信息****************

所以我设置了一个场景,控制器只将 HttpRequestMessage 作为 ctor 参数并发现:

这行得通:

//controller
public class ValuesController : ApiController
    {
        private readonly HttpRequestMessage _httpReq;

        public ValuesController(HttpRequestMessage httpReq)
        {
            _httpReq = httpReq;
        }
//IHttpControllerActivator
public IHttpController Create(
            HttpRequestMessage httpRequest,
            HttpControllerDescriptor controllerDescriptor,
            Type controllerType)
        {

            var controller = (IHttpController)this.container.Resolve(
                controllerType, new { httpReq = httpRequest });

            return controller;

但是,这不是。

//controller
public class ValuesController : ApiController
    {
        private readonly HttpRequestMessage _httpReq;

        public ValuesController(HttpRequestMessage request)
        {
            _httpReq = request;
        }

//IHttpControllerActivator
public IHttpController Create(
            HttpRequestMessage request,
            HttpControllerDescriptor controllerDescriptor,
            Type controllerType)
        {

            var controller = (IHttpController)this.container.Resolve(
                controllerType, new { request = request });

            return controller;

即当 anon 对象具有称为“请求”的属性并且控制器 ctor arg 称为“请求”时。它以某种方式使控制器认为它的请求属性为空。这是导致我看到的错误的原因:

无法重用“ApiController”实例。 'ApiController' 必须是 每个传入消息构造。检查您的自定义 'IHttpControllerActivator' 并确保它不会制造 同一个实例。

在 System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext 控制器上下文,CancellationToken 取消令牌)在 System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal(HttpRequestMessage 请求,CancellationToken 取消令牌)在 System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsync(HttpRequestMessage 请求,CancellationToken 取消令牌)

阅读本文 How can I enrich object composition in StructureMap without invoking setter injection?

它解释了一个类似的场景。

当然,hyprlinkr 的 HttpRequestMessage 的 ctor arg 称为“request”,所以我确实需要使用该属性名称指定 anon 对象。

有什么想法吗?

【问题讨论】:

  • 上面链接的帖子描述了如何使用 Castle Windsor 和 Web API 的预览。由于预览版和 RTM 版本之间存在重大更改,因此此处描述的方法不再有效。请参阅blog.ploeh.dk/2012/10/03/…,了解如何在 Web API RTM 中使 DI 与 Castle Windsor 一起使用。
  • 谢谢 - 是的 - 我已经遵循了。我只是不知道如何为 hyprlinkr 需要的 HttpRequestMessage 进行注册。你能指出我正确的方向吗?
  • 再次感谢 - 我现在收到一条错误消息!所以有些进展。 “不能重用 'ApiController' 实例。必须为每个传入消息构建 'ApiController'。检查您的自定义 'IHttpControllerActivator' 并确保它不会制造相同的实例。”。我已经用我现在使用的代码更新了上面的问题。你能看出有什么明显的错误吗?听起来好像认为控制器注册为Singleton,但肯定是PerWebRequest。有什么想法吗?
  • 您不能将 PerWebRequest 与 Web API 一起使用。 PerWebRequest 依赖于环境上下文HttpContext.Current,这在 Web API 中为空。尝试使用Transient,或编写您自己的定制Castel Windsor 生活方式——我的书解释了如何做到这一点:affiliate.manning.com/idevaffiliate.php?id=1150_236

标签: c# asp.net-web-api inversion-of-control castle-windsor hyprlinkr


【解决方案1】:

这是一个适合我的合成根:

public class WindsorCompositionRoot : IHttpControllerActivator
{
    private readonly IWindsorContainer container;

    public WindsorCompositionRoot(IWindsorContainer container)
    {
        this.container = container;
    }

    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        var controller = (IHttpController)this.container.Resolve(
            controllerType,
            new
            {
                request = request
            });

        request.RegisterForDispose(
            new Release(
                () => this.container.Release(controller)));
        return controller;
    }

    private class Release : IDisposable
    {
        private readonly Action release;

        public Release(Action release)
        {
            this.release = release;
        }

        public void Dispose()
        {
            this.release();
        }
    }
}

这是我创建容器的方法:

this.container =
    new WindsorContainer(
        new DefaultKernel(
            new InlineDependenciesPropagatingDependencyResolver(),
            new DefaultProxyFactory()),
        new DefaultComponentInstaller())
        .Install(new MyWindsorInstaller());

这里是 InlineDependenciesPropagatingDependencyResolver:

public class InlineDependenciesPropagatingDependencyResolver : 
    DefaultDependencyResolver
{
    protected override CreationContext RebuildContextForParameter(
        CreationContext current,
        Type parameterType)
    {
        if (parameterType.ContainsGenericParameters)
        {
            return current;
        }

        return new CreationContext(parameterType, current, true);
    }
}

最后,这是我注册 RouteLinker 的方法:

container.Register(Component
    .For<RouteLinker, IResourceLinker>()
    .LifestyleTransient());

需要注意的一点是,ApiController 基类有一个名为 Request 的 HttpRequestMessage 类型的公共属性。正如my book 的第 10.4.3 节所述,Windsor 将尝试为每个可写属性分配一个值,如果它具有匹配的组件 - 并且该匹配不区分大小写。

当您将名为 request 的 HttpRequestMessage 传递给 Resolve 方法时,这正是发生的情况,因此您需要告诉 Castle Windsor 它应该放弃对 ApiControllers 的属性注入。以下是我在基于约定的注册中的做法:

container.Register(Classes
    .FromThisAssembly()
    .BasedOn<IHttpController>()
    .ConfigureFor<ApiController>(c => c.Properties(pi => false))
    .LifestyleTransient());

【讨论】:

  • 谢谢 - 您需要更换 IHttpeControllerActivator 吗? GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new WindsorCompositionRoot(container));如果我不这样做,我会收到一个错误,即我的控制器没有默认构造函数 - 所以它显然不是由城堡构造的。如果我确实添加了上面的行,那么我会得到与以前相同的错误。即不能重用“ApiController”实例。你能告诉我你的控制器注册是什么样的吗?我有这个: container.Register(Component.For().LifestyleTransient());
  • 请参考blog.ploeh.dk/2012/10/03/…了解基本信息。您不需要(也不能)完全注册 HttpRequestMessage,因为每次调用 IHttpControllerActivator.Create 都会有一个新实例。 ValuesController 的注册看起来不错...从目前可用的信息来看,我无法判断出了什么问题。
  • 是的,这很奇怪。也许我有不同版本的 web api 给你?有没有办法告诉?另外——您能否确认您的版本是否替换了默认的 IHttpControllerActivator,即 config.Services.Replace(typeof(IHttpControllerActivator), new WindsorCompositionRoot(container));
  • 我已经卸载并重新安装了 MVC4 包,以确保我使用的是最新版本。并尝试使用上面的确切代码在一个全新的项目中,但仍然得到相同的错误。我不知所措地解释出了什么问题。很郁闷....
  • 您遇到的终身问题与 Hyprlinkr 无关。尝试将问题隔离在 Castle Windsor 和单个 ApiController 上。如果您无法找出问题所在,请尝试在新问题中发布您的代码。
【解决方案2】:

为什么不使用 ASP.NET Web API 中已经内置的机制 - 依赖解析器

http://www.asp.net/web-api/overview/extensibility/using-the-web-api-dependency-resolver

在 WebApiContrib 项目中有 CastleWindsor 解析器实现,但正如我在 Dependency 解析器中看到的那样

https://github.com/WebApiContrib/WebApiContrib.IoC.CastleWindsor

正如马克在评论中所说 - 实现 IHttpControllerActivator 的方法之一

http://blog.ploeh.dk/2012/10/03/DependencyInjectionInASPNETWebAPIWithCastleWindsor.aspx

【讨论】:

  • 我更感兴趣的是找出如何解决 HttpRequestMessage。无论哪种方式...
  • Hyprlinkr 库对它有一个我需要满足的构造函数依赖
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-08
  • 1970-01-01
  • 2014-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-08
相关资源
最近更新 更多