Reputation: 8821
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
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