.NET Core ASP.NET Core C# Dependency Injection

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.

 GETPOST
Mediatr15.729615.5852
Controller15.392315.5079
Request15.490115.5516
ASP.NET Core Test Results
Test results

Author

Sean Leitzinger

Comments (2)

  1. Request Injection in Asp.Net Core - How to Code .NET
    December 19, 2017

    […] on December 17, 2017 submitted by /u/DotNetCultist [link] [comments] Leave a […]

  2. Ahmed Galal
    April 27, 2021

    Your code is not working in NET CORE v5.0,
    var provider = services.BuildServiceProvider();
    The previous generated Provider are not recommended in .NET CORE v5 ,

    ASP0000 Calling ‘BuildServiceProvider’ from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to ‘Configure’.

    Please Review this link https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#ASP0000

    If you can upgrade your RequestInjectorHandler to work with maybe like
    services.AddOptions().Configure((options, ContextAccessor) =>
    {
    options.JsonSerializerOptions.Converters.Add(new RequestInjectorSerializer(ContextAccessor));
    });

    I can’t get it to work please Advice
    My best regards, Your Articles was very enlightening .

Leave a comment

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