RobIII
RobIII

Reputation: 8821

Use DI in ConfigureServices?

I have implemented a custom InputFormatter (MyInputFormatter):

public class MyInputFormatter : SystemTextJsonInputFormatter
{
    private readonly IMyDepenency _mydependency;

    public CustomInputFormatter(
        JsonOptions options, 
        ILogger<SystemTextJsonInputFormatter> logger, 
        IMyDependency myDependency
     ) : base(options, logger)
    {
        _mydependency = myDependency ?? throw new ArgumentNullException(nameof(myDependency));
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
    {
        //...
    }
}

Now, according to the documentation I need to use it as follows:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.InputFormatters.Insert(0, new MyInputFormatter(...));
    });
}

However, as you can see my CustomInputFormatter has some constructor arguments required and needs some services and it's not clear to me how to use DI to resolve these services. I have read through a lot of answers/blogs/pages like this one but either the inputformatter doesn't have any constructor arguments (and so no need for DI, just new up a new instance inline) or the following is suggested:

public void ConfigureServices(IServiceCollection services)
{
    var sp = services.BuildServiceProvider();
    services.AddControllers(options =>
    {
        options.InputFormatters.Insert(0, new MyInputFormatter(
            sp.GetService<...>(),
            sp.GetService<...>(),
            sp.GetService<IMyDependency>(),
        ));
    });
}

But we're not supposed to call BuildServiceProvider from ConfigureServices.

How would I go about this?

Upvotes: 2

Views: 1426

Answers (1)

pinkfloydx33
pinkfloydx33

Reputation: 12739

You can make use of the Options infrastructure and create an IConfigureOptions<MvcOptions> . This new service can take the necessary dependeices. It will be instantiated and "executed" the first time something (the MVC infrastructure) requests an IOptions<MvcOptions>

public class ConfigureMvcOptionsFormatters : IConfigureOptions<MvcOptions> 
{ 
   private readonly ILoggerFactory _factory;
   private readonly JsonOptions _jsonOpts;
   private readonly IMyDependency _depend;
   public ConfigureMvcOptionsFormatters(IOptions<JsonOptions> options, ILoggerFactory loggerFactory, IMyDependency myDependency)
   {
      _factory = loggerFactory;
      _jsonOpts = options.Value;
      _depend = myDependency;
   } 

   public void Configure(MvcOptions options)
   { 
      var logger = _factory.CreateLogger<SystemTextJsonInputFormatter>();
      var formatter =  new MyInputFormatter(_jsonOpts, logger, _depend);
      options.InputFormatters.Insert(0, formatter);
   }
} 

You then register your class to have its IConfigureOptions implementation run by calling the ConfigureOptions<T>() extension method on the IServiceCollection:

public void ConfigureServices(IServiceCollection services)
{
  services.AddControllers();
  services.ConfigureOptions<ConfigureMvcOptionsFormatters>();
}

Alternatively you can use an Options builder along with Configure and it's callback, specifying your dependencies as the generic arguments.

public void ConfigureServices(IServiceCollection services)
{
  services.AddControllers();
  services.AddOptions<MvcOptions>()
          .Configure<IOptions<JsonOptions>, ILoggerFactory, IMyDependency>(
              (o, j, l, d) => o.InputFormatters.Insert(0, new MyInputFormatter(j.Value, l.CreateLogger<SystemTextJsonInputFormatter>(), d)
          );
}

Note: I've been using ILoggerFactory because I don't believe the infrastructure will inject an ILogger<ClassA> into ClassB. However, I admit I've never tried it and I'm not near a computer to verify. If it is in fact allowed, you can specify the type you need directly instead.

Upvotes: 5

Related Questions