In my Web API project, I have a dependency that requires the current request.

The code is below:

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.
  }
}

Using Simple Injector, is it possible to inject the current request into the container at runtime ?

I tried the following :

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);
  }
}

but recieved the following error

The container can't be changed after the first call to GetInstance, GetAllInstances and Verify.

Is there any way to inject the current request into the container at runtime?

有帮助吗?

解决方案

The container blocks any registrations after the registration phase. This splits the usage of the container into two phases: registration and resolve. Simple Injector is not the only container that does this. Autofac for instance, does this even more explicitly by allowing users to make registrations using the ContainerBuilder that builds the container using the Build method as last step in the configuration phase.

Simple Injector disallows this mainly because making registrations later on during the application lifetime can easily lead to all sorts of problematic behavior such as race conditions. It also makes the DI configuration much harder to understand, since registrations are scattered throughout the application, while you should try to centralize registration when applying DI. As a side effect, this design allows the container have a linear performance characteristic in a multi-threading scenario, because the container's happy path is free of locks.

Simple Injector allows just-in-time registration using the ResolveUnregisteredType event, but this is not the way to go in your case.

The problem you are having is that you want to inject an object that is only known at runtime into the object graph. This might not be the best thing to do, since in general you should pass runtime dependencies through method arguments, while compile-time/configuration-time dependencies should be passed through the constructor.

But if you are certain that passing the HttpRequestMessage as a constructor argument is the right thing to do, what you need to do is to cache this message during the lifetime of the request and make a registration that allows to return that cached instance.

This is how this would look like:

// 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;
});

This registration will retrieve a HttpRequestMessage that is cached in the HttpContext.Items dictionary. There's an extra check to allow this registration to work during verification (when calling container.Verify()), since at that point in time there is no HttpContext.Current. But if you're not interest in verifying the container (which you usually really should btw), you can minimize it to this:

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

With this registration, the only thing you have to do is cache an HttpRequestMessage instance before container.GetInstance is called to get the controller:

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

UPDATE

The Web API Integration package contains a GetCurrentHttpRequestMessage extension method that allows you retrieving the HttpRequestMessage of the current request. The Web API Integration Wiki page describes how to use this extension method.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top