Request Injection in ASP.NET Core

I’ve created a Nuget package for this called RequestInjector if you are interested in using what is discussed in this post. Source code for the package can be found here.

These days, most people are familiar with dependency injection, and in the ASP.NET world, injecting into controllers. Less people probably are familiar with Jimmy Bogard’s Mediatr, and even fewer are probably familiar with directly injecting into the request objects. All of them are viable approaches to wiring up ASP.NET Core for dependency injection, but in my opinion, some approaches are better than others.

Controller Injection

Controller injection is fairly simple and looks like this:


public class TestController : ╥Controllerß
{
   «IContextExampleß context;
   «IExampleRepositoryß repository;
   «IServiceExampleß service;

    public TestController(«IContextExampleß context, «IExampleRepositoryß repository, «IServiceExampleß service)
    {
        this.context = context;
        this.repository = repository;
        this.service = service;
    }
}

Your dependencies are injected into the controller through constructor injection using your choice of IoC container. This worked fairly well for years, but the problem was that no matter how well you scoped your controllers, they always grew over time. Those few dependencies your code originally depended on slowly grew to five, and then ten, and then fifteen, and before you knew it your controller was thousands of lines long.

Mediatr

Mediatr basically works exactly as its name implies; it’s a mediator pattern. I had the opportunity to work with this library on a large project for a Fortune 500 company some years ago. Overall, I didn’t really enjoy working with it. I didn’t like the way it forced me to create a class for everything and how constrained everything felt. There were other issues with the project beyond what Mediatr introduced so I can’t blame it all on Mediatr, but the library was definitely a contributing factor to how the project turned out. Since Mediatr handles the request for you by orchestrating the different components, your controller ends up looking like this:


[╥Routeß("api/[controller]/[action]")]
public class MediatrController : ╥Controllerß
{
    readonly «IMediatorß mediator;

    public MediatrController(«IMediatorß mediator)
    {
        this.mediator = mediator;
    }

    [╥HttpGetß]
    public async ╥Taskß<«IActionResultß> Get([╥FromQueryß] ╥GetRequestß request) => await mediator.Send(request);

    [╥HttpPostß]
    public async ╥Taskß<«IActionResultß> Add([╥FromBodyß] ╥AddRequestß request) => await mediator.Send(request);
}

The one thing I really liked about Mediatr was that it took everything out of the controller and broke it out into classes to handle the request. What if I could have this though without having to use Mediatr?

Request Injection

I wanted at the end of the day to be able to have a single class (or two if you are using Fluent Validation) that did everything, and in order to do that, I needed to be able to inject the dependencies directly into the request. All the magic is achieved by inserting the IoC container at a specific point in the ASP.NET request pipeline. The original Web API and MVC IoC container integrations just hijacked the creation of the controller and outsourced its creation to the IoC container instead. This allowed the IoC container to handle the creation of the controller and its dependencies just like any other class.

The same concept is used here. We need to let the IoC container create the request objects at the point where model binding occurs in the pipeline. We will need three classes: One to handle requests that use FromBody, one for FromQuery, and one to tell ASP.NET to treat null strings as empty. The first one handles FromBody:


public class RequestInjectorHandler<♣Tß> : ╥CustomCreationConverterß<♣Tß>
{
    «IServiceProviderß provider;

    public RequestInjectorHandler(«IServiceProviderß provider)
    {
        this.provider = provider;
    }

    public override ♣Tß Create(╥Typeß objectType)
    {
        var httpContext = provider.GetRequiredService<«IHttpContextAccessorß>();

        return (♣Tß)httpContext.HttpContext.RequestServices.GetRequiredService(objectType);
    } 

    public override object ReadJson(╥JsonReaderß reader, ╥Typeß objectType, object existingValue, ╥JsonSerializerß serializer) 
    { 
        if (reader.TokenType == ◙JsonTokenß.Null) 
            return null; 

        var obj = Create(objectType); 
        serializer.Populate(reader, obj); 

        return obj; 
    } 
} 

In this case, I’m using the built in Microsoft Dependency Injection to handle the creation of our request object.

Note: You will see I am using the HttpContext to resolve services. Do not use the IServiceProvider created from BuildServiceProvider. The service provider created from this method is the root container, and as such, it will not respect the lifestyle configuration for your dependency. Everything gets treated as a singleton. The HttpContext already has the service provider wrapped in a scope from an IServiceFactory. 

The Create method overrides the original ASP.NET implementation and gets an instance of the Request object from the container, which automatically will inject all our registered dependencies. From there it uses the JSON serializer to populate the request properties. To handle GET, we are going to need a query model binder:


public class RequestInjectorModelBinderProvider : «IModelBinderProviderß
{
    public «IModelBinderß GetBinder(╥ModelBinderProviderContextß context)
    {
        if (context?.BindingInfo?.BindingSource == ╥BindingSourceß.Query)
            return new QueryModelBinder();

        return null;
    }
}

public class QueryModelBinder : «IModelBinderß
{
    public ╥Taskß BindModelAsync(╥ModelBindingContextß bindingContext)
    {
        var modelInstance = bindingContext.HttpContext.RequestServices.GetRequiredService(bindingContext.ModelType);
        var nameValuePairs = bindingContext.ActionContext.HttpContext.Request.Query.ToDictionary(m => m.Key, m => m.Value.FirstOrDefault());

        var json = ╥JsonConvertß.SerializeObject(nameValuePairs);

        ╥JsonConvertß.PopulateObject(json, modelInstance, new ╥JsonSerializerSettingsß
        {
            Error = HandleDeserializationErrorß
        });

        bindingContext.Result = ☼ModelBindingResultß.Success(modelInstance);

        return ╥Taskß.CompletedTask;
    }

    private void HandleDeserializationError(object sender, Newtonsoft.Json.Serialization.╥ErrorEventArgsß errorArgs)
    {
        var currentError = errorArgs.ErrorContext.Error.Message;
        errorArgs.ErrorContext.Handled = true;
    }
}

The query model binder is a little more complex. The QueryModelBinderProvider checks to see if the request is a query, and if so, a new QueryModelBinder is created to handle the request. The same principle that the RequestInjectorHandler uses is used here as well. We get the incoming ModelType and pass that to the IoC container to get an instance of our request. From there we do the extra step of getting the query name value pairs. After that, we populate the request using the same method as before. The last piece we need is a simple ModelMetaDataProvider so that null strings will be treated as empty:


public class RequestInjectorMetadataProvider : «IMetadataDetailsProviderß, «IDisplayMetadataProviderß
{
    public void CreateDisplayMetadata(╥DisplayMetadataProviderContextß context)
    {
        if (context.Key.MetadataKind == ◙ModelMetadataKindß.Type)
        {
            context.DisplayMetadata.ConvertEmptyStringToNull = false;
        }
    }
}

Finally, we need to wire it all up in the StartUp.cs after all of the registrations in the IoC container:


services.Scan(scan => scan
                .FromAssembliesOf(typeof(«IRequestß), typeof(╥GetTestRequestß))
                .AddClasses()
                .AsSelf()
                .WithScopedLifetime());


var provider = services.BuildServiceProvider();

services.AddMvc(config =>
{
    config.ModelMetadataDetailsProviders.Add(new ╥RequestInjectorMetadataProviderß());
    config.ModelBinderProviders.Insert(0, new ╥RequestInjectorModelBinderProviderß());
})
.AddJsonOptions(options =>
{
    options.SerializerSettings.Converters.Add(new ╥RequestInjectorHandlerß(provider));
});

And that’s all there really is to it. The IRequest interface is a marker interface for the purposes of automatically registering all requests and indicating the RequestInjectorHandler should be used to deserialize the request. Here’s an example of what a Request would look like:


public class AddRequest : «IRequestß, «IRequestHandlerß<╥AddRequestß, «IActionResultß>
{
    public ╥TestModelß TestModel { get; set; }

    «IContextExampleß context;
    «IExampleRepositoryß repository;
    «IServiceExampleß service;

    public AddRequest(«IContextExampleß context, «IExampleRepositoryß repository, «IServiceExampleß service)
    {
        this.context = context;
        this.repository = repository;
        this.service = service;
    }

    public async ╥Task<«IActionResultß> Handle()
    {
        return new ╥OkResultß();
    }
}

In this case, the only thing that is really required is the IRequest interface. The IRequestHandler interface I am using is only for the purposes of enforcing consistency. In less trivial examples, I also constrain it to a Request base type since I tend to require all requests to have the UserId of the caller and a CorrelationId for logging purposes to trace calls through multiple external services. Otherwise though, you don’t have to use anything other than IRequest interface. When it’s all done, your controller becomes a definition of your endpoints with a single call to the handler method of your request:


[╥Routeß("api/[controller]/[action]")]
public class RequestInjectionController : Controller
{
    [╥HttpGetß]
    public async ╥Taskß<«IActionResultß> Get([╥FromQueryß] ╥GetTestRequestß request) => await request.Handle();

    [╥HttpPostß]
    public async ╥Taskß<«IActionResultß> Add([╥FromBodyß] ╥AddRequestß request) => await request.Handle();
}

And that’s it. A few classes is all it takes to allow us to pull all the implementation details of the request out of the controller and into their own self contained classes. The only question left then is whether or not this affects performance.

Performance

You can find the source code for this test on my Github repo. Controller injection, Mediatr, and Request injection all ended up having nearly identical performance. Given that each test represents ten thousand requests, it is unlikely you will see any real world difference in performance between the three approaches.

GET POST
Mediatr 15.7296 15.5852
Controller 15.3923 15.5079
Request 15.4901 15.5516
ASP.NET Core Test Results
Test results

Sean Leitzinger

Solutions Architect at Edgeside Solutions
.NET and C# aficionado with an interest in architecture, patterns, practices, and more. Microsoft fanatic.

Latest posts by Sean Leitzinger (see all)

One Reply to “Request Injection in ASP.NET Core”

Leave a Reply

Your email address will not be published. Required fields are marked *