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

Mic*_*den 3 .net c# 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.
  }
}
Run Code Online (Sandbox Code Playgroud)

使用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);
  }
}
Run Code Online (Sandbox Code Playgroud)

但收到了以下错误

首次调用GetInstance,GetAllInstances和Verify后,无法更改容器.

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

Ste*_*ven 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;
});
Run Code Online (Sandbox Code Playgroud)

此注册将检索HttpRequestMessageHttpContext.Items字典中缓存的内容.有一个额外的检查,允许这个注册在验证(通话时container.Verify()),因为在那个时间点没有HttpContext.Current.但是如果你没有兴趣验证容器(你通常应该这么做),你可以把它最小化到这个:

container.Register<HttpRequestMessage>(() =>
    (HttpRequestMessage)HttpContext.Current.Items["__message"]);
Run Code Online (Sandbox Code Playgroud)

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

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

UPDATE

网页API集成软件包包含一个GetCurrentHttpRequestMessage扩展方法,它允许您检索HttpRequestMessage当前请求的.Web API Integration Wiki页面描述了如何使用此扩展方法.