I'm developing a .NET 7 web application using clean architecture with the following 3 projects:
- Core (Entities, Interfaces, etc.)
- Infrastructure (contains an API to get some data from a third party source, etc.)
- Web (Controllers, ViewModels, etc)
In the Infrastructure Project I have a class that accesses an external API. I want to pass some options to this class, which currently looks like this:
public class MyApiAccessor : IMyApiAccessor
{
private readonly IMyApiOptions _myApiOptions;
public MyApiAccessor (private readonly IMyApiOptions _myApiOptions)
{
_myApiOptions = myApiOptions;
}
public void GetDataFromApi()
{
// implementation details, _myApiOptions is used in here
}
}
Web Project appSettings.json
As you can see, the settings are split in two hierarchical levels. I want it explicitly to be like this.
"MyApiSettings":
{
"FirstMainSetting": 11,
"SecondMainSetting": 12
"SubSettings":
{
"FirstSubSetting": 21,
"SecondSubSetting": 22,
"ThirdSubSetting": 23
},
"MoreSubSettings":
{
"FourthSubSetting": 24,
"FifthSubSetting": 25,
"SixthSubSetting": 26
}
}
Core Project Interfaces
public interface IMyApiOptions
{
public int FirstMainSetting { get; set; }
public int SecondMainSetting { get; set; }
// Note that here I'm using the Interfaces,
// since the Core Layer doesn't know about the according implementations
public ISubSettingsOptions SubSettings { get; set; }
public IMoreSubSettingsOptions MoreSubSettings { get; set; }
}
public interface ISubSettingsOptions
{
public int FirstSubSetting { get; set; }
public int SecondSubSetting { get; set; }
public int ThirdSubSetting { get; set; }
}
public interface IMoreSubSettingsOptions
{
public int FourthSubSetting { get; set; }
public int FifthSubSetting { get; set; }
public int SixthSubSetting { get; set; }
}
Web Project Option Classes (implement the interfaces mentioned above)
public class MyApiOptions : IMyApiOptions
{
public int FirstMainSetting { get; set; }
public int SecondMainSetting { get; set; }
public ISubSettingsOptions SubSettings { get; set; }
public IMoreSubSettingsOptions MoreSubSettings { get; set; }
}
public class SubSettingsOptions : ISubSettingsOptions
{
public int FirstSubSetting { get; set; }
public int SecondSubSetting { get; set; }
public int ThirdSubSetting { get; set; }
}
public class MoreSubSettingsOptions : IMoreSubSettingsOptions
{
public int FourthSubSetting { get; set; }
public int FifthSubSetting { get; set; }
public int SixthSubSetting { get; set; }
}
Web Project Program.cs
Here I get the config section, bind it to MyApiOptions and register the dependency with IMyApiOptions.
// [Using Statements]
var builder = WebApplication.CreateBuilder(args);
// [Add various services (e.g. MVC)]
// [Configure various dependency resolutions]
// Configure the options class
builder.Services.Configure<MyApiOptions>(builder.Configuration.GetSection("MyApiSettings"));
builder.Services.AddScoped<IMyApiOptions>(x => x.GetRequiredService<IOptionsSnapshot<MyApiOptions>>().Value);
var app = builder.Build();
// [Use static files, register routes, etc.]
app.Run();
However, once I run the application, I obtain the following exception: System.InvalidOperationException: "Cannot create instance of type 'MyProject.Core.Interfaces.ISubSettingsOptions' because it is either abstract or an interface."
Does anybody have an idea, how I can solve this issue? It looks like this is caused by both ISubSettingsOptions
and IMoreSubSettingsOptions
in the IMyApiOptions
class which can't be resolved, but since IMyApiOptions
is in the Core Layer (to be accessible also from the Infrastructure Layer which references ist), I wouldn't know how I could adapt the design.
CodePudding user response:
You can add initialization of those properties. Then MyApiAOptions
class will still fully implement the interface, but DI container will be able to create its instances.
public class MyApiOptions : IMyApiOptions
{
public int FirstMainSetting { get; set; }
public int SecondMainSetting { get; set; }
public ISubSettingsOptions SubSettings { get; set; } = new SubSettingsOptions();
public IMoreSubSettingsOptions MoreSubSettings { get; set; } = new MoreSubSettingsOptions();
}
CodePudding user response:
Configuration binder requires "concrete" types to be able to construct and bind them. You can workaround by removing setters from the interface and using explicit interface implementation:
public interface IMyApiOptions
{
// ...
public ISubSettingsOptions SubSettings { get; }
public IMoreSubSettingsOptions MoreSubSettings { get; }
}
public class MyApiOptions : IMyApiOptions
{
public int FirstMainSetting { get; set; }
public int SecondMainSetting { get; set; }
public SubSettingsOptions SubSettings { get; set; }
public MoreSubSettingsOptions MoreSubSettings { get; set; }
ISubSettingsOptions IMyApiOptions.SubSettings => SubSettings;
IMoreSubSettingsOptions IMyApiOptions.MoreSubSettings => MoreSubSettings;
}