Options Pattern Class not Populating - c#

I'm creating a .NET 6 worker service, hosted as a windows service. The service uses the FluentFTP library to download files from a remote server. I'm trying to implement strongly-typed configuration using the Options Pattern but am having difficulties with the binding.
public class Program
{
public static async Task Main(string[] args)
{
var config = new ConfigurationBuilder().Build();
var logger = LogManager.Setup()
.SetupExtensions(ext => ext.RegisterConfigSettings(config))
.GetCurrentClassLogger();
IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(Options => { Options.ServiceName = "Foo FTP File Process"; })
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.Sources.Clear();
IHostEnvironment env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);
IConfigurationRoot configurationRoot = config.Build();
FTPSettingsOptions options = new();
configurationRoot.GetSection(nameof(FTPSettingsOptions))
.Bind(options);
//Working
Console.WriteLine($"FTPSettingsOptions.Enabled={options.FTPUsername}");
})
.ConfigureServices(services =>
{
services.AddOptions();
services.AddSingleton<IFTPService, FTPService>();
services.AddHostedService<Worker>();
})
.ConfigureLogging((hostContext, logging) =>
{
logging.ClearProviders();
logging.AddNLog(hostContext.Configuration, new NLogProviderOptions()
{ LoggingConfigurationSectionName = "NLog" });
logging.AddConsole();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
})
.Build();
await host.RunAsync();
}
}
appSettings.Development.json:
{
"FTPSettingsOptions": {
"FTPUri": "ftp://ftp.foo.com",
"FTPUsername": "footest",
"FTPPassword": "***********",
"FTPServerPath": "/home/footest/foo",
"CallServerPath": "C:\\Temp\\foo"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
POCO Class:
public class FTPSettingsOptions
{
public FTPSettingsOptions() { }
public const string FTPSettings = "FTPSettings";
public string FTPUri { get; set; } = String.Empty;
public string FTPUsername { get; set; } = String.Empty;
public string FTPPassword { get; set; } = String.Empty;
public string FTPServerPath { get; set; } = String.Empty;
public string CallServerPath { get; set; } = String.Empty;
}
This is where the options values are coming in as null:
public class FTPService : IFTPService
{
private readonly IOptions<FTPSettingsOptions> _options;
public FTPService(IOptions<FTPSettingsOptions> options)
{
FTPSettingsOptions _options = options.Value;
// Connect only once (Singleton scope)
Connect();
}
public void Connect()
{
// FTPUsername and FTPPassword have no values
using ( var conn = new FtpClient(_options.Value.FTPUri, _options.Value.FTPUsername,
_options.Value.FTPPassword))
{
...
conn.Connect();
}
}
}

You are missing the Configure call:
.ConfigureServices((ctx, services) =>
{
services.Configure<FTPSettingsOptions>(ctx.Configuration.GetSection(nameof(FTPSettingsOptions)));
// ...
})
Note that explicit AddOptions call is not needed because Configure performs it internally.
configurationRoot.GetSection(nameof(FTPSettingsOptions)).Bind(options); just binds the data to options instance and does not register anything in the DI (check the docs here or here).

Related

ASP.NET Core API using [ApiController], Parameter always null

I'm using ASP.NET Core to create a REST API. If I use the [APIController] class attribute, the method that uses the POST method with complex parameters always gets null value even though using/without using [FromBody] (I'm using Postman, Raw Json). But if I omit the [APIController] attribute, the parameters on the POST method work normally. I'm using ASP.NET Core 6. What is the effect without using the [APIController] attribute?
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
builder.Services.Configure<IISServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
// Add services to the container.
builder.Services.AddMvc(option =>
{
option.AllowEmptyInputInBodyModelBinding = true;
option.EnableEndpointRouting = true;
option.FormatterMappings.SetMediaTypeMappingForFormat("json", Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"));
}).AddNewtonsoftJson(opt => {
opt.SerializerSettings.DateFormatString = "dd/MM/yyyy HH:mm:ss";
}).AddJsonOptions(options => {
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
Model:
public class BillRequest
{
public string? CompCode { get; set; } = String.Empty;
public string? CustNo { get; set; } = String.Empty;
public string? ID { get; set; } = String.Empty;
public string? Type { get; set; } = String.Empty;
public string? TransDate { get; set; } = String.Empty;
public string? Remark { get; set; } = String.Empty;
public BillRequest()
{
}
}
Controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace FI.Controllers;
[ApiController]
[Route("[Controller]")]
public class VAController : ControllerBase
{
private readonly ILogger<VAController> _logger;
public VAController(ILogger<VAController> logger)
{
_logger = logger;
}
[HttpPost]
//[Authorize(AuthenticationSchemes = VAAuthSchemeConstants.VAAuthScheme)]
[Route("[Action]")]
public BillRequest Bills(BillRequest billReq)
{
try
{
if (ModelState.IsValid)
{
return new BillRequest
{
CompanyCode = "Test - success"
};
}
else
{
return new BillRequest()
{
CompanyCode = "Test - not valid model"
};
}
}
catch(Exception ex)
{
return new BillRequest()
{
CompCode = "Test - error"
};
}
}
}
Postman Payload (Postman Body (Raw, Json)):
{
"CompCode": "Test Comp"
"CustNo": "1235",
"ID": "123123123",
"Type": "01",
"TransDate": "Test Date",
"Remark": "My Remark"
}
Even though I changed the parameter using a string the parameter value is still null.

How do I resolve this error in .NET Minimal API integration with PostgreSQL?

I am trying to make APIs using ASP.Net Minimal API thingy and I am trying to use PostgreSQL as Database so for that I am following an Article and and I have followed it so far but I am unable to get desired output.
I have provided all the files below from my Project...
Program.cs
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<BookStoreDB>(options => options.UseNpgsql(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/", () =>
{
return "Hello World";
});
app.MapPost("/register", (BookStoreDB database, RegisterRequest body, HttpResponse response) =>
{
User user = new User(1);
user.FirstName = body.firstName;
user.LastName = body.lastName;
user.Email = body.email;
user.Password = body.password;
database.Users.Add(user);
database.SaveChanges();
response.StatusCode = 201;
return new { requestBody = body };
});
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Port=5432;Database=BookStore;User=postgres;Password=somepass"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
BookStoreDB.cs
using Microsoft.EntityFrameworkCore;
public class BookStoreDB : DbContext
{
public BookStoreDB(DbContextOptions<BookStoreDB> options) : base(options)
{
}
public DbSet<User> Users => Set<User>();
}
RequestObjects.cs
public class LoginRequest
{
public string email { get; set; } = default!;
public string password { get; set; } = default!;
}
public class RegisterRequest
{
public string firstName { get; set; } = default!;
public string lastName { get; set; } = default!;
public string email { get; set; } = default!;
public string password { get; set; } = default!;
}
User.cs
public record User(int id)
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
public string Email { get; set; } = default!;
public string Password { get; set; } = default!;
}
I don't have any Idea what these files are for and the convention about them. But when I run the Project using dotnet watch run it starts successfully but whenever I try to make an POST request on /register route , I get an Error shown below...
Problem is in the connection string. To specify a user you need to use "User Id" instead of "User". In your appsettings.json file try changing
"DefaultConnection": "Server=localhost;Port=5432;Database=BookStore;User=postgres;Password=somepass"
this to
"DefaultConnection": "Server=localhost;Port=5432;Database=BookStore;User Id=postgres;Password=somepass"
The "user" parameter is not able to be set because of the error in the connection string provided. Update the connection string as follows in appsettings.json file:
"ConnectionStrings": {
"DefaultConnection":"User ID=postgres;Password=somepass;Host=localhost;Port=5432;Database=BookStore;CommandTimeout=100"
}
You can ignore the CommandTimeout if not needed.

Cannot resolve scoped service 'Application.Interfaces.FacadPattern.IStatisticsFacad' from root provider in .net core5

I am doing Store application with Pro ASP.NET Core 5 MVC. The example is made in ASP.NET Core 5.0.
I get this error while trying to run the program:
Cannot resolve scoped service 'Application.Interfaces.FacadPattern.IStatisticsFacad' from root provider
In this file I use IStatisticsFacad for saving data in the database:
namespace Application.Interfaces.FacadPattern
{
public interface IStatisticsFacad
{
AddStatesticsService AddStatesticsService { get; }
AddStUserService AddStUserService { get; }
AddStatesticsDetailService AddStatesticsDetailService { get; }
}
}
and this is facad class
public class StatisticsFacad: IStatisticsFacad
{
private readonly IDataBaseContext _context;
public StatisticsFacad(IDataBaseContext context
)
{
_context = context;
}
private AddStatesticsService _AddStatesticsService;
public AddStatesticsService AddStatesticsService
{
get
{
return _AddStatesticsService = _AddStatesticsService ?? new AddStatesticsService(_context);
}
}
private AddStUserService _AddStUserService;
public AddStUserService AddStUserService
{
get
{
return _AddStUserService = _AddStUserService ?? new AddStUserService(_context);
}
}
private AddStatesticsDetailService _AddStatesticsDetailService;
public AddStatesticsDetailService AddStatesticsDetailService
{
get
{
return _AddStatesticsDetailService = _AddStatesticsDetailService ?? new AddStatesticsDetailService(_context);
}
}
}
Use in this file ad add in startup file
public class ApplicationVariable
{
private readonly RequestDelegate _next;
private static int TimeOut = 5;
private readonly IStatisticsFacad _statisticFacad;
public ApplicationVariable(RequestDelegate next, IStatisticsFacad statisticFacad)
{
_next = next;
_statisticFacad = statisticFacad;
}
public async Task Invoke(HttpContext httpContext)
{
string UserAgent = httpContext.Request.Headers["User-Agent"].ToString();
string refer = httpContext.Request.Headers["Referer"].ToString();
if (UserAgent == null)
await _next.Invoke(httpContext);
if (Check(httpContext))
{
string rndkey = (new Random().Next(1000, 1000000)).ToString() + "-" + (new Random().Next(10000, 10000000) * DateTime.Now.Millisecond).ToString();
StOnline.set(new On() { key = rndkey, time = DateTime.Now.AddMinutes(TimeOut) });
httpContext.Response.Cookies.Append("OnlineUser", rndkey, new CookieOptions
{
Expires = DateTime.Now.AddMinutes(TimeOut)
});
httpContext.Session.SetString("OnlineUser", rndkey);
var q = _statisticFacad.AddStatesticsService.Execute().IsSuccess;
DateTime.Now.Date).SingleOrDefault();
var uaParser = Parser.GetDefault();
UAParser.ClientInfo UserInfo = uaParser.Parse(UserAgent);
if (q)
{
StUserDto stUser = new StUserDto()
{
OS = UserInfo.OS.Family,
Browser = UserInfo.UA.Family,
KeyId = rndkey
};
var t = _statisticFacad.AddStUserService.Execute(stUser).IsSuccess;
var IpAdd = httpContext.Connection.RemoteIpAddress.ToString() != null ?
httpContext.Connection.RemoteIpAddress.ToString() :
httpContext.Connection.LocalIpAddress.ToString();
string Contry1 = new GetLocation().Get(IpAdd).country;
StStatDto stDetail = new StStatDto()
{
CountryName = Contry1,
Ip = IpAdd,
Refferer = httpContext.Request.Headers["Referer"].ToString(),
Url = httpContext.Request.Host + "/" + httpContext.Request.Path,
Key = rndkey,
};
var sd = _statisticFacad.AddStatesticsDetailService.Execute(stDetail).IsSuccess;
}
else
{
}
}
else
{
string key = GetKey(httpContext);
StOnline.Update(key, TimeOut);
httpContext.Response.Cookies.Delete("OnlineUser", new CookieOptions
{
Expires = DateTime.Now.AddMinutes(TimeOut)
});
httpContext.Session.Remove("OnlineUser");
httpContext.Session.SetString("OnlineUser", key);
}
await _next.Invoke(httpContext);
}
}
and startup file
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IDataBaseContext, DataBaseContext>();
services.AddSingleton<IRecaptchaExtension, RecaptchaExtension>();
services.AddHttpClient();
services.AddDbContext<DataBaseContext>
(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMemoryCache();
services.AddSession();
services.AddScoped<IStatisticsFacad, StatisticsFacad>();
services.AddControllersWithViews();
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<DataBaseContext>()
.AddDefaultTokenProviders()
.AddRoles<Role>()
.AddErrorDescriber<CustomIdentityError>()
.AddPasswordValidator<MyPasswordValidator>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddFluentValidation();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseMiddleware<ApplicationVariable>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "areas",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
How to solve this problem?

The authentication response was rejected because the state parameter was missing

i desided to login with steam in asp.net core 2.1 ,
i use AspNet.Security.OpenId.Steam nuget package for connection
,when call sigin method ,client page redirect to steam and after login with steam call back to my server,but not authenticaed request and rejected...
1-in Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env,IConfiguration configuration,ApplicationDbContext applicationDbContext,ApplicationDbContextBase applicationDbContextBase)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseHsts();
}
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseCors(option => option.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.UseStaticFiles();
app.UseAuthentication();
app.UseHttpsRedirection();
AppHttpContext.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
applicationDbContext.MigrateToLastChange();
}
2 - in service.cs
public static IServiceCollection SetupNegatechApi(this IServiceCollection services, IConfiguration configuration)
{
//TODO: add services here...
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
//Assign User & Role Model And DbContext To Identity
services.AddIdentity<ApplicationIdentityUser, ApplicationIdentityRole>().AddDefaultTokenProviders().AddEntityFrameworkStores<ApplicationDbContextBase>();
//Get Auth Key & Convert To Byte;
var AuthInfo = configuration.GetSection("Auth").Get<AppSettings>();
var SSKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AuthInfo.SecurityKey));
//Config Identity Password & JWT Config
services.Configure<IdentityOptions>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Password.RequireDigit = false;
})
.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(option =>
{
option.RequireHttpsMetadata = false;
option.SaveToken = true;
option.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = AuthInfo.Issuer,
ValidAudience = AuthInfo.Audienc,
IssuerSigningKey = SSKey,
ClockSkew = TimeSpan.Zero
};
})
.AddCookie()
.AddSteam(op =>
{
configuration.Bind(op);
op.ClaimsIssuer = AuthInfo.Issuer;
op.SaveTokens = true;
op.CallbackPath = "/api/Steam/SteamCallBack";
op.RequireHttpsMetadata = false;
});
services.Configure<IISOptions>(op => op.AutomaticAuthentication = false);
//Register Configuration For Dependncy Injection
services.AddSingleton<IConfiguration>(configuration);
services.AddSingleton<IFileProvider>(new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/$gallery")));
return services;
}
3-in Controller
[ApiController]
[ApiExplorerSettings(GroupName = "public")]
[Route("api/[controller]/[action]")]
public class SteamController : BaseController
{
[HttpPost]
public async Task<IActionResult> Signin()
{
var auth = new AuthenticationProperties { RedirectUri = "/api/Steam/SteamCallBack" };
return Challenge(auth,"Steam" );
}
[HttpGet]
public IActionResult SteamCallBack(string state,openid openid)
{
//breack point
return Redirect("http://localhost:3000/profile?id=" + "test");
}
}
public class openid
{
public string claimed_id { get; set; }
public string identity { get; set; }
public string return_to { get; set; }
public string response_nonce { get; set; }
public string assoc_handle { get; set; }
public string signed { get; set; }
public string sig { get; set; }
}
4-in html file
<form id="steam_form" action="https://localhost:44315/api/Steam/Signin" method="post">
//Submit Login form to api server
<button type="submit"> Login</button>
</form>
5- result error after call back http://s8.picofile.com/file/8365103326/Untitled.png
I don't know why, but AddSteam option is above the OpenID rules.
If you look closer, then you can see that Steams OpenId is only name and some random standards.
Check your form with change endpoint to your.address/signin and make a post form:
<form id="steamAuth" action="https://localhost:44315/signin" method="post">
<input type='hidden' name='Provider' value='Steam'>
<input type = 'hidden' name='ReturnUrl' value='your.address/returnurl'></form>
<button type="submit"> Login</button>
</form>
Im not sure, but i think that .AddSteam() option not including any settings added in services configuration.
If you check repo of this library you can see examples, and here its just AddSteam(), when other providers are described:
services.AddAuthentication(options => { /* Authentication options */ })
.AddSteam()
.AddOpenId("StackExchange", "StackExchange", options =>
{
options.Authority = new Uri("https://openid.stackexchange.com/");
options.CallbackPath = "/signin-stackexchange";
});

Get all registered IOptions [duplicate]

Following this post: https://blog.bredvid.no/validating-configuration-in-asp-net-core-e9825bd15f10
I m now available to validate the Settings when a service need it. What I would like to do is to validate directly when the server starts in program.cs.
I m not sure how to do it? Is there a way to get the list of services injected in DI then verify if the type is assignable from IOption and register it?
Here is how I add to DI the Settings:
//App settings
services.ConfigureAndValidate<AuthenticationSettings>(Configuration);
services.ConfigureAndValidate<SmtpSettings>(Configuration);
The extension code:
public static class IServiceCollectionExtensions
{
public static IServiceCollection ConfigureAndValidate<T>(
this IServiceCollection serviceCollection,
IConfiguration config,
string section = null
) where T : class
{
var configType = typeof(T).Name;
if (string.IsNullOrEmpty(section)) {
section = configType;
}
return serviceCollection
.Configure<T>(config.GetSection(section))
.PostConfigure<T>(settings =>
{
var configErrors = settings.ValidationErrors().ToArray();
if (configErrors.Any())
{
var aggrErrors = string.Join(",", configErrors);
var count = configErrors.Length;
throw new ApplicationException($"Found {count} configuration error(s) in {configType}: {aggrErrors}");
}
});
}
private static IEnumerable<string> ValidationErrors(this object obj)
{
var context = new ValidationContext(obj, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(obj, context, results, true);
foreach (var validationResult in results)
{
yield return validationResult.ErrorMessage;
}
}
}
Here is my current launcher:
public class Program
{
public static async Task Main(string[] args)
{
var webHost = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddEnvironmentVariables();
var env = hostingContext.HostingEnvironment;
config.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseStartup<Startup>()
.Build();
using (var scope = webHost.Services.CreateScope())
{
var services = scope.ServiceProvider;
/// <---- BEGIN / AN IDEA OF WHAT I WOULD LIKE TO DO ---->
/// <---- THIS CODE IS NOT WORKING ---->
var allServices = services.GetAllServices();
if (allServices != null)
{
foreach (var service in allServices )
{
if (service.ServiceType.IsAssignableFrom(IOptions))
{
services.GetRequiredService<service.ServiceType>()
}
}
}
/// <---- END ---->
}
await webHost.RunAsync();
}
}
Let me know if you have any suggestions in comment.
Thanks for your help.
EDIT 1:
Thanks Steven for your help, with your answer, it helped me to continue to find an answer, but things are still missing.
now, all my settings inherit from ISettings, like:
public class AuthenticationSettings : ISettings
{
[Required]
public string Issuer { get; set; }
[Required]
public string Audience { get; set; }
[Required]
public string SecretKey { get; set; }
[Required]
public int ExpirationDurationInDays { get; set; }
}
I update Program.cs like:
using Autofac;
using Autofac.Core;
var options = services.GetService<ILifetimeScope>()
.ComponentRegistry
.Registrations.SelectMany(e => e.Services)
.Select(s => s as TypedService)
.Where(s => s.ServiceType.IsGenericType && s.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>))
.Select(s => s.ServiceType.GetGenericArguments()[0])
.Where(s => typeof(ISettings).IsAssignableFrom(s))
.ToList();
so now I need to instantiate each option in options and get the Value. I m still working on it. let me know if you have any suggestion or the solution :)
You can get a list of configured option types by iterating the IServiceCollection instance:
var configuredOptionTypes =
from descriptor in services
let serviceType = descriptor.ServiceType
where serviceType.IsGenericType
where serviceType.GetGenericTypeDefinition() == typeof(IConfigureNamedOptions<>)
let optionType = serviceType.GetGenericArguments()[0]
select optionType;
Following suggestions from Steven, here is my solution:
My settings validator service
public SettingsValidator(
IServiceProvider services,
ILifetimeScope scope
)
{
var types = scope.ComponentRegistry.Registrations
.SelectMany(e => e.Services)
.Select(s => s as TypedService)
.Where(s => s.ServiceType.IsAssignableToGenericType(typeof(IConfigureOptions<>)))
.Select(s => s.ServiceType.GetGenericArguments()[0])
.Where(s => typeof(ISettings).IsAssignableFrom(s))
.ToList();
foreach (var t in types)
{
var option = services.GetService(typeof(IOptions<>).MakeGenericType(new Type[] { t }));
option.GetPropertyValue("Value");
}
}
in startup:
builder.RegisterType<SettingsValidator>();
example of Settings
public class AzureStorageSettings : ISettings
{
[Required]
public string ConnectionString { get; set; }
[Required]
public string Container { get; set; }
[Required]
public string Path { get; set; }
}
extensions
public static class TypeExtensions
{
public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
{
foreach (var it in givenType.GetInterfaces())
{
if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
return true;
}
if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
return true;
Type baseType = givenType.BaseType;
if (baseType == null) return false;
return IsAssignableToGenericType(baseType, genericType);
}
}
in program.cs
using (var scope = webHost.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<Program>>();
try
{
logger.LogInformation("Starting settings validation.");
services.GetRequiredService<SettingsValidator>();
logger.LogInformation("The settings have been validated.");
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while validating the settings.");
}
}
Let me know if it works for you too :)

Categories