3

在我的 Web API 项目中,我有一个需要当前请求的依赖项。

代码如下:

public interface IResourceLinker {
  Uri Link(string routeName, object routeValues);
}

public class ResourceLinker : IResourceLinker {
  private readonly HttpRequestMessage _request;

  public ResourceLinker(HttpRequestMessage request) {
    _request = request;
  }

  public Uri Link(string routeName, object routeValues) {
    return new Uri(_request.GetUrlHelper()
        .Link(routeName, routeValues));
  }
}

public class TestController : ApiController {
  private IResourceLinker _resourceLinker;

  public TestController(IResourceLinker resourceLinker) {
    _resourceLinker = resourceLinker
  }

  public Test Get() {
    var url = _resourceLinker.Link("routeName", routeValues);

    // etc.
  }
}

使用 Simple Injector,是否可以在运行时将当前请求注入容器?

我尝试了以下方法:

public class InjectRequestHandler : DelegatingHandler
{
  protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
  {
    InjectRequest(request);
    return base.SendAsync(request, cancellationToken);
  }

  public static void InjectCurrentRequestIntoContainer(
    HttpRequestMessage request)
  {
    var resolver = (SimpleInjectorDependencyResolver)
      request.GetDependencyScope();
    resolver.Container.Register(() => request);
  }
}

但收到以下错误

在第一次调用 GetInstance、GetAllInstances 和 Verify 后无法更改容器。

有没有办法在运行时将当前请求注入容器?

4

1 回答 1

5

容器在注册阶段之后阻止任何注册。这将容器的使用分为两个阶段:注册和解析。Simple Injector 不是唯一这样做的容器。例如,Autofac 更明确地做到了这一点,它允许用户使用ContainerBuilder构建容器的Build方法作为配置阶段的最后一步进行注册。

Simple Injector 不允许这样做,主要是因为在应用程序生命周期的后期进行注册很容易导致各种有问题的行为,例如竞争条件。这也使得 DI 配置更难理解,因为注册分散在整个应用程序中,而您应该在应用 DI 时尝试集中注册。作为副作用,这种设计允许容器在多线程场景中具有线性性能特征,因为容器的快乐路径是无锁的。

Simple Injector 允许使用事件进行即时注册ResolveUnregisteredType,但这不是您的情况。

您遇到的问题是您想将仅在运行时才知道的对象注入对象图中。这可能不是最好的做法,因为通常您应该通过方法参数传递运行时依赖项,而编译时/配置时依赖项应该通过构造函数传递。

但是,如果您确定将HttpRequestMessage作为构造函数参数传递是正确的做法,那么您需要做的是在请求的生命周期内缓存此消息并进行允许返回该缓存实例的注册。

这就是它的样子:

// using SimpleInjector.Advanced; // for IsVerifying()

container.Register<HttpRequestMessage>(() =>
{
    var context = HttpContext.Current;

    if (context == null && container.IsVerifying())
        return new HttpRequestMessage();

    object message = context.Items["__message"];
    return (HttpRequestMessage)message;
});

此注册将检索HttpRequestMessage缓存在HttpContext.Items字典中的一个。有一个额外的检查来允许这个注册在验证期间工作(调用时container.Verify()),因为在那个时间点没有HttpContext.Current. 但是,如果您对验证容器不感兴趣(顺便说一句,您通常确实应该这样做),您可以将其最小化为:

container.Register<HttpRequestMessage>(() =>
    (HttpRequestMessage)HttpContext.Current.Items["__message"]);

有了这个注册,你唯一需要做的就是在调用之前HttpRequestMessage缓存一个实例来获取控制器: container.GetInstance

public static void InjectCurrentRequestIntoContainer(
    HttpRequestMessage request)
{
    HttpContext.Current.Items["__message"] = request;
}

更新

Web API 集成包包含GetCurrentHttpRequestMessage允许您检索HttpRequestMessage当前请求的扩展方法。Web API 集成 Wiki 页面描述了如何使用此扩展方法。

于 2013-07-15T09:15:37.557 回答