I have a problem with using specific service depend on other service in web api Program file.
I create IDatabaseOption interface and DatabaseOptions class like this (and add values in appsettings):
public interface IDatabaseOptions
{
string ConnectionString { get; set; }
DatabaseType DatabaseType { get; set; }
string DatabaseName { get; set; }
}
public class DatabaseOptions : IDatabaseOptions
{
public string ConnectionString { get; set; }
public DatabaseType DatabaseType { get; set; }
public string DatabaseName { get; set; }
}
and the registration is:
services.Configure<DatabaseOptions>(configuration.GetSection(nameof(DatabaseOptions)));
services.AddSingleton<IDatabaseOptions>(serviceProvider => serviceProvider.GetRequiredService<IOptions<DatabaseOptions>>().Value);
And next step I'd like to register concrete DbContext based on DatabaseType like this:
services.AddDbContext<ApplicationDbContext>().AddOptions<DbContextOptionsBuilder>().Configure<IDatabaseOptions>((options, databaseOptions) =>
{
switch (databaseOptions.DatabaseType)
{
case DatabaseType.MSSQL:
options.UseSqlServer(databaseOptions.ConnectionString);
break;
case DatabaseType.POSTGRESQL:
options.UseNpgsql(databaseOptions.ConnectionString);
break;
default: break;
}
});
and at this point it not work. If I use DI in for example Repository in constructor like Repository(ApplicationDbContext), the context is not set.
I don't have to use BuildServiceProvider() because it is not proper solution.
How can I use registered service to decide for other service at this point?
If you make the settings while registering the DbContext object, the problem will disappear. When I tried the following piece of code, the error you mentioned disappeared.
services.AddDbContext<ApplicationDbContext>((provider, optionsBuilder) =>
{
var options = provider.GetRequiredService<IDatabaseOptions>();
switch (options.DatabaseType)
{
case DatabaseType.MSSQL:
optionsBuilder.UseSqlServer(options.ConnectionString);
break;
case DatabaseType.POSTGRESQL:
break;
default:
throw new ArgumentOutOfRangeException();
}
});
Related
I've been following the official Microsoft documentation and I've tried to enchance my codebase like the following:
public class GRequestModel(IConfiguration config) => _config = config;
{
private readonly IConfiguration Configuration;
public string path { get; set; }
public string secret { get; set; }
public string response { get; set; }
public string remoteip { get; set; }
public GRequestModel(string res, string remip)
{
response = res;
remoteip = remip;
var secret = Configuration["GoogleRecaptchaV3:Secret"];
var path = Configuration["GoogleRecaptchaV3:ApiUrl"];
if (String.IsNullOrWhiteSpace(secret) || String.IsNullOrWhiteSpace(path))
{
//Invoke logger
throw new Exception(
"Invalid 'Secret' or 'Path' properties in appsettings.json. " +
"Parent: GoogleRecaptchaV3.");
}
}
}
But I keep getting a lot of errors if I do it like it's written in the documentation...
To clarify - my goal is to read the secret and API URL from appsettings.json and use this to set the values for Google's reCAPTCHA v3.
I need to do this because I want to migrate my web app to a stable version of .NET 6.
Firstly, the syntax you have is wrong. Dependency injection should be done through the constructor (although some IoC containers provide property injection too):
public class GRequestModel
{
public string path { get; set; }
public string secret { get; set; }
public string response { get; set; }
public string remoteip { get; set; }
public GRequestModel(IConfiguration configuration, string res, string remip)
{
response = res;
remoteip = remip;
var secret = configuration["GoogleRecaptchaV3:Secret"];
var path = configuration["GoogleRecaptchaV3:ApiUrl"];
if (String.IsNullOrWhiteSpace(secret) || String.IsNullOrWhiteSpace(path))
{
//Invoke logger
throw new Exception("Invalid 'Secret' or 'Path' properties in appsettings.json. Parent: GoogleRecaptchaV3.");
}
}
}
Now we're accepting IConfiguration in the correct place, we can address how to create this object and get IConfiguration from the container. To do this, I'm going to create a delegate in the same GRequestModel class:
public class GRequestModel
{
public delegate GRequestModel Factory(string res, string remip);
Then we can register this factory with the container:
services.AddTransient<GRequestModel.Factory>(serviceProvider =>
(string res, string remip) => new GRequestModel(serviceProvider.GetRequiredService<IConfiguration>(), res, remip));
Now you can inject GRequestModel.Factory and create a GRequestModel using it:
public class SomeOtherClass
{
public SomeOtherClass(GRequestModel.Factory grequestFactory)
{
GRequestModel grm = grequestFactory("resValue", "remipValue");
}
}
Try it online
Edit: In response to your comment about it being more complicated than the documentation, that's because your use case is more complicated than the documentation examples. Specifically, you want to accept parameters res and remip.
Consider this example:
public class MyService
{
private readonly IConfiguration _configuration;
public MyService(IConfiguration configuration)
{
_configuration = configuration;
}
public void DoSomething(string res, string remip)
{
string configValue = _configuration["myConfigKey"];
}
}
You can register this and use this like so:
services.AddTransient<MyService>();
public class MyOtherClass
{
public MyOtherClass(MyService service)
{
service.DoSomething("resValue", "remipValue");
}
}
This is much simpler because you don't also need to inject the parameters into the constructor. It really depends on what you're trying to do as to which is the best option.
I added configurations to the appSettings.json file in my .NET Core project. For the sake of simplicy I'm taking database settings as an example. So in the settings file you would have
{
"Database": {
"Host": "localhost",
"Port": 1234,
"Database": "myDb",
"Username": "username",
"Password": "pw",
"EnablePooling": true
}
}
When configuring the services in the Startup.cs file I want to make those settings accessible via dependency injection. The data model for this is
public class DatabaseSettings
{
public string Host { get; set; }
public ushort Port { get; set; }
public string Database { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool EnablePooling { get; set; }
}
and I configure it this way
private void SetupSettings(IServiceCollection services)
{
ServiceProvider serviceProvider = services.BuildServiceProvider();
IConfiguration configuration = serviceProvider.GetService<IConfiguration>();
IConfigurationSection databaseConfigurationSection = configuration.GetSection("Database");
services.Configure<DatabaseSettings>(databaseConfigurationSection);
}
Lastly I want to validate those settings. I know that I can create a validator class implementing the IValidateOptions interface.
public class DatabaseSettingsValidator : IValidateOptions<DatabaseSettings>
{
private readonly IList<string> failures;
public DatabaseSettingsValidator()
{
failures = new List<string>();
}
public ValidateOptionsResult Validate(string databaseSettingsName, DatabaseSettings databaseSettings)
{
if (databaseSettings == null)
failures.Add($"{databaseSettingsName} are required.");
if (string.IsNullOrEmpty(databaseSettings?.Host))
failures.Add($"{nameof(databaseSettings.Host)} must not be empty.");
if (string.IsNullOrEmpty(databaseSettings?.Database))
failures.Add($"{nameof(databaseSettings.Database)} must not be empty.");
if (failures.Any())
return ValidateOptionsResult.Fail(failures);
return ValidateOptionsResult.Success;
}
}
but do I have to create this class and call the Validate method on my own? Maybe there is something like this sample code?
.
services.ValidateConfiguration<IOptions<DatabaseSettings>, DatabaseSettingsValidator>();
So you pass in the configured settings and the validator to use.
but I'm struggling with two questions:
Is there a way I can collect all failures instead of returning after
one? So you would get a list of failures instead of having to fix one
by one.
Do I have to create this class and call the Validate method on my own?
Maybe there is something like this sample code?
services.ValidateConfiguration<IOptions,
DatabaseSettingsValidator>(); So you pass in the configured settings
and the validator to use.
Yes, we could collect all failures list and display them at once, and we could also create a class which contains the Validate method. Please check the following steps:
First, since the class name is "DatabaseSettings", it better sets the config section name as the same as the class name:
{
"DatabaseSettings": {
"Host": "localhost",
"Port": 1234,
"Database": "myDb",
"Username": "username",
"Password": "pw",
"EnablePooling": true
}
}
[Note] If using a different name, the value might not map to the Database Setting class, so when validate the data, they all null.
Second, using the Data Annotations method adds validation rules to the model properties.
public class DatabaseSettings
{
[Required]
public string Host { get; set; }
[Required]
public ushort Port { get; set; }
[Required]
public string Database { get; set; }
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
[Required]
public bool EnablePooling { get; set; }
}
Third, create a ServiceCollectionExtensions class which contains the ConfigureAndValidate method:
public static class ServiceCollectionExtensions
{
public static IServiceCollection ConfigureAndValidate<T>(this IServiceCollection #this,
IConfiguration config) where T : class
=> #this
.Configure<T>(config.GetSection(typeof(T).Name))
.PostConfigure<T>(settings =>
{
var configErrors = settings.ValidationErrors().ToArray();
if (configErrors.Any())
{
var aggrErrors = string.Join(",", configErrors);
var count = configErrors.Length;
var configType = typeof(T).Name;
throw new ApplicationException(
$"Found {count} configuration error(s) in {configType}: {aggrErrors}");
}
});
}
Then, register the ConfigureAndValidate service:
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureAndValidate<DatabaseSettings>(Configuration);
}
Finally, get the Exception list.
public class HomeController : Controller
{
private readonly DatabaseSettings_settings;
public HomeController(IOptions<DatabaseSettings> settings)
{
_settings = settings.Value; // <-- FAIL HERE THROW EXCEPTION
}
}
Then, test result like this (I removed the Host and Username from the appSettings.json):
More detail information, you can check this blog:Validating configuration in ASP.NET Core
ValidateOptions are mainly for complex scenario, the purpose of using ValidateOptions is that you can move the validate logic out of startup.
I think for your scenario, you can use below code as a reference
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions<MyConfigOptions>()
.Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Key2 != 0)
{
return config.Key3 > config.Key2;
}
return true;
}, "Key3 must be > than Key2."); // Failure message.
services.AddControllersWithViews();
}
For more details, please refer to this document
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1#options-validation
I am currently developing a web api in .NET Core 3. I currently have the following model for my error response object:
public class ErrorRo
{
public string Message { get; set; }
public int StatusCode { get; set; }
public string Endpoint { get; set; }
public string Parameters { get; set; }
public string IpAddress { get; set; }
}
This is a mandated response I need to implement, management has pushed this. It allows more verbose error messages for people hitting our API so that they know what went wrong.
At the moment I am currently populating this object manually in the methods themselves. Is there a way where I can overwrite the response methods. I.e. can I override the BadRequest of IActionResult to automatically populate these fields?
Thanks!
You can use result filters for this purpose. Add a filter which repalces result before sending it back
Model
public class CustomErroModel
{
public string Message { get; set; }
public int StatusCode { get; set; }
public string Endpoint { get; set; }
public string Parameters { get; set; }
public string IpAddress { get; set; }
}
Filter
public class BadRequestCustomErrorFilterAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
//todo: check for BadRequestObjectResult if anything is returned for bad request
if (context.Result is BadRequestResult)
{
var result = new CustomErroModel
{
StatusCode = 200, //you status code
Endpoint = context.HttpContext.Request.GetDisplayUrl(),
Message = "some message",
IpAddress = context.HttpContext.Connection.RemoteIpAddress.ToString(), //find better implementation in case of proxy
//this returns only parameters that controller expects but not those are not defined in model
Parameters = string.Join(", ", context.ModelState.Select(v => $"{v.Key}={v.Value.AttemptedValue}"))
};
context.Result = new OkObjectResult(result); // or any other ObjectResult
}
}
}
Then apply filter per action or globally
[BadRequestCustomErrorFilter]
public IActionResult SomeAction(SomeModel model)
or
services
.AddMvc(options =>
{
options.Filters.Add<BadRequestCustomErrorFilterAttribute>();
//...
}
Well it depends on the scenario, but one possible approach could be to use a middleware using a similar strategy like the one described in this question, so that you complete the response with extra information.
I created an REST api application which has many settings and stored in database. These settings are used during filtering and inserting data to the table.
Because I need to access settings every time I need to insert data. Instead of accessing settings from database, I created a global settings class and I put every settings in that class.
public static class GlobalSettings
{
public static string Setting_1;
public static string Setting_2;
public static string Setting_3;
public static string Setting_4;
public static void Initialize(ISettingsRepo repo)
{
try
{
var settings = new GSettings(repo);
Setting_1 = settings.SetSetting_1();
Setting_2 = settings.SetSetting_2();
Setting_3 = settings.SetSetting_3();
Setting_4 = settings.SetSetting_4();
}
catch (Exception ex)
{
throw new Exception("Error when loading settings.\r\n" + ex.Message);
}
}
}
Here ISettingsRepo is scoped service that will load the settings from database. The functions will initialize the settings to the properties.
Now to initialize GlobalSettings I used configure method in startup class like this.
using (var scope = app.ApplicationServices.CreateScope())
{
Settings.GlobalSettings.Initialize(scope.ServiceProvider
.GetRequiredService<Data_Repo.Settings.ISettingsRepo>());
}
Now I can use this in controller or anywhere in my api and get settings without accessing database. Also I can reload the GlobalSettings any time if settings are updated. But does this method correct way or has memory leak problems?
Is there any better method to do this.?
Example
My appsetting.json have structure like this.
"EmailSettings": {
"MailServer": "",
"MailPort": ,
"Email": "",
"Password": "",
"SenderName": "",
"Sender": "",
"SysAdminEmail": ""
},
I will define my class like this
public class EmailSettings
{
public string MailServer { get; set; }
public int MailPort { get; set; }
public string SenderName { get; set; }
public string Sender { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string SysAdminEmail { get; set; }
}
So we have the the config structure. The last thing we need is register inside Startup.cs
services.Configure<EmailSettings>(configuration.GetSection("EmailSettings"));
To use it inside service class
private readonly IOptions<EmailSettings> _emailSetting;
public EmailSender(IOptions<EmailSettings> emailSetting)
{
_emailSetting = emailSetting;
}
email.From.Add(new MailboxAddress(_emailSetting.Value.SenderName, _emailSetting.Value.Sender));
I'm trying to make a Config.cs class to use on my project.
Structure is supposed to consist of categories of settings. For example, Config.LogOnDetails should hold the values for MySQL login.
Here is my current structure.
public class Config
{
public string pPath;
public string configPath;
public string configFilePath;
public class LogOnDetails
{
public string MySQLDatabaseName { get; set; }
public string MySQLUser { get; set; }
public string MySQLPassword { get; set; }
public string MySQLAddress { get; set; }
}
public Config()
{
pPath = Directory.GetCurrentDirectory();
configPath = Path.Combine(pPath, #"/config");
configFilePath = Path.Combine(configPath, "/config.json");
//If it doesn't exist, create a directory for the configuration to be stored in
if (!Directory.Exists(configPath))
{
Directory.CreateDirectory("config");
}
if (!File.Exists(configFilePath))
{
string json = JsonConvert.SerializeObject(this);
File.WriteAllText(configFilePath, json);
Console.WriteLine("Created a new blank config file!");
}
}
}
Here is how I'm trying to load the config to the class.
//Initialize configuration
Config.LogOnDetails logOnDetails = new Config.LogOnDetails();
//Load config from json
config.LogOnDetails = JsonConvert.DeserializeObject<Config.LogOnDetails>(config.configFilePath);
But this doesn't seem to work and looks like I don't understand subclasses properly. How can I organize my class so it will work?
json example:
{
"pPath": null,
"configPath": null,
"configFilePath": null,
"MySQLDatabaseName": null,
"MySQLUser": null,
"MySQLPassword": null,
"MySQLAddress": null
}
First off, I'm going to start with a general point. The Config class should know nothing about how it is stored, or where it is stored. That is a completely separate "concern". See Separation of concerns
Start off with the definition of what you want to store. That seems to be your MySql info, and some other info. These should all be individual classes (To be clear, you can nest them, but there is no need to and complicates the answer a little):
public class LogOnDetails
{
public string MySQLDatabaseName { get; set; }
public string MySQLUser { get; set; }
public string MySQLPassword { get; set; }
public string MySQLAddress { get; set; }
}
You can have another one:
public class Settings
{
public string Locale { get; set; }
}
And you can compose these into a master config object
public class Config
{
public string SomeTopLevelProp {get; set; }
public LogOnDetails LogOnDetails { get; set; }
public Settings Settings { get; set; }
}
The way to serialize and deserialize this is fairly straightforward
var config = new Config()
{
SomeTopLevelProp = "ABCDEF",
LogOnDetails = new LogOnDetails()
{
MySqlDatabaseName = "Foo" ,
MySQLUser = "MyUser"
// snip the rest of the props
},
Settings = new Settings
{
Locale = "en-GB"
}
}
var json = JsonConvert.SerializeObject(config );
var mySettingDeserialized = JsonConvert.DeserializeObject<Config>(json);
I have purposely left out the writing to a file part - you seem to know how to do that - but keep it outside of the Config class. For example a separate classes, perhaps just with 2 static methods which knows how/where to store the config
public static class ConfigLoader
{
public static void StoreConfig(Config config, string location) {... }
public static Config LoadConfig(string location) {... }
}
A note on security - storing your database password as plain text in a json config file is generally a bad idea. You might consider encrypting it, and storing that and decrypting it when using the value.