Simple Injector - 构造函数注入当前Web API请求的HttpRequestMessage

时间:2013-07-15 01:12:24

标签: c# .net dependency-injection simple-injector

在我的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后,无法更改容器。

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

1 个答案:

答案 0 :(得分: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。但是如果你没有兴趣验证容器(通常是really should btw),你可以将其最小化:

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

通过此注册,您唯一需要做的就是在调用 HttpRequestMessage之前缓存container.GetInstance实例以获取控制器:

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

<强>更新

Web API Integration package包含GetCurrentHttpRequestMessage扩展方法,可让您检索当前请求的HttpRequestMessage。 Web API Integration Wiki页面describes如何使用此扩展方法。