Options Configuration with dynamic (runtime) values - asp.net-core

I have a MySettings class which I want to configure as Options to make it available via the dependency injection.
Currently I do (in an service builder extension method):
services.Configure<MySettings>(configuration.GetSection(MySettings.CustomSectionName));
My problem is that one part of the settings comes from the appsettings and other values are only known at runtime (startup).
So I try to find out how to configure the settings with adding the runtime provided values. I tried to add the values to the configuration
configuration["SectionName:ValueX"] = "my runtime value";
That did not work and ValueX is always null (when the options are injected in the controller).
Any suggestions for me?

You could try register MySettings instead of IOptions<MySettings> like
public void ConfigureServices(IServiceCollection services)
{
var mySettings = new MySettings();
Configuration.Bind("MySettings", mySettings);
mySettings.Title = "Hello";
services.AddSingleton(mySettings);
}
And use MySettings like
public class HomeController : Controller
{
private readonly MySettings _settings;
public HomeController(MySettings settings)
{
_settings = settings;
}
public IActionResult Index()
{
return Ok(_settings);
}

You can use IPostConfigureOptions< TOptions > interface to achieve what you want
services.PostConfigure<CustomOptions>(customOptions =>
{
customOptions.Option1 = "post_configured_option1_value";
});
Reference: https://learn.microsoft.com/en-us/dotnet/core/extensions/options#options-post-configuration

Related

URLRewrite middleware that depends on Config dependency injection/scoped service. .Net Core

Struggling a little with Dependency Injection/Scoped Services with a rewrite rule class.
I have a redirects class which implements IRule
class ActivateRedirects : IRule
{
public void ApplyRule(RewriteContext context)
{
// Do stuff here which relies on CoreSettings (info below)
}
}
I also have a CoreSettings class which contains various settings, some of which are required for ApplyRule to work, it is initialised in startup.cs as follows.
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "site.json"), optional: false, reloadOnChange: true);
IConfigurationRoot root = configurationBuilder.Build();
CoreSettings s = new CoreSettings();
services.Configure<CoreSettings>(root.GetSection("Website"));
So you can see above, CoreSettings is created as a Service, which in most cases I can consume with DI:
public class SomeOtherClass{
private CoreSettings Settings;
public SomeOtherClass(Microsoft.Extensions.Options.IOptionsSnapshot<CoreSettings> S)
{
Settings = S.Value;
}
// Do stuff with Settings ....
}
I have read up on several pages on why I can't simply add DI to the ActivateRedirects Class, or explicitly pass the value in using app.ApplicationServices.GetRequiredService but everything I have read is telling what I can't do, I can't find anything to tell me what I can!!
Before I am told to rewrite the code to not require CoreSettings for Rewriting, I can't do that because one of the rewrite rules depends on a condition which is set by a remote server via a REST API and what is needed in the CoreSettings class is the API credentials used to create the REST Client.
It is possible to do what you want. I'll assume your class is defined as follows:
public class ActivateRedirects : IRule
{
private readonly CoreSettings _coreSettings;
public ActivateRedirects(CoreSettings coreSettings)
{
_coreSettings = coreSettings;
}
public void ApplyRule(RewriteContext context)
{
}
}
Read the configuration file, as before:
services.Configure<CoreSettings>(root.GetSection("Website"));
Next, setup your RewriteOptions:
var coreSettings = app.ApplicationServices.GetRequiredService<IOptions<CoreSettings>>();
var activateRedirects = new ActivateRedirects(coreSettings.Value);
var rewriteOptions = new RewriteOptions();
rewriteOptions.Rules.Add(activateRedirects);
app.UseRewriter(rewriteOptions);
If you put a breakpoint inside ActivateRedirects, and send a request, you'll see the CoreSettings field has been populated.
Update
I think this scenario is what IOptionsMonitor<T> might be designed for. It's registered as a singleton, but is notified of options changes. Change ActivateRedirects to:
public class ActivateRedirects : IRule
{
private readonly IOptionsMonitor<CoreSettings> _coreSettings;
public ActivateRedirects(IOptionsMonitor<CoreSettings> coreSettings)
{
_coreSettings = coreSettings;
}
public void ApplyRule(RewriteContext context)
{
}
}
and change how the instance is constructed to:
var coreSettings = app.ApplicationServices.GetRequiredService<IOptionsMonitor<CoreSettings>>();
var activateRedirects = new ActivateRedirects(coreSettings);
For me, editing the configuration does now show updated values in CoreSettings. One caveat is I don't know how that notification process works. If this ends up reading the configuration directly on each request, then it will scale really poorly, so I'd advise giving it a good test first.

How to inject HttpContextAccessor directly from ConfigureServices method

My goal is to set a username string based on the environment I'll be working on that must be:
an arbitrary string for the development and staging environment
the HttpContext.User.Identity.Name in production.
This is because I have to be able to simulate different kind of users and I achieve this by calling the FindByIdAsync method on my custom implementation of UserIdentity using this username string as a parameter, like this:
public class HomeController : Controller
{
UserManager<AppUser> userManager;
AppUser connectedUser;
public HomeController(UserManager<AppUser> usrMgr, IContextUser ctxUser)
{
connectedUser = usrMgr.FindByNameAsync(ctxUser.ContextUserId).Result;
}
}
I started creating three appsettings.{environment}.json file for the three usual development, staging and production environments; development and staging .json files both have this configuration:
...
"Data": {
...
"ConnectedUser" : "__ADMIN"
}
...
while the production environment configuration file doesn't have this key.
I have created a simple interface
public interface IContextUser
{
public string ContextUserId { get; }
}
and its implementation:
public class ContextUser : IContextUser
{
string contextUser;
IHttpContextAccessor contextAccessor;
public ContextUser(IHttpContextAccessor ctxAccessor, string ctxUser = null)
{
contextUser = ctxUser;
contextAccessor = ctxAccessor;
}
public string ContextUserId => contextUser ?? contextAccessor.HttpContext.User.Identity.Name;
}
Now, I thought of simply configuring the ConfigureServices method in the Startup class:
public void ConfigureServices(IServiceCollection services)
{
// --- add other services --- //
string ctxUser = Configuration["Data:ConnectedUser"];
services.AddSingleton(service => new ContextUser( ??? , ctxUser ));
}
but it needs an IHttpContextAccessor object, that seems unavailable at this stage of the application. How can I solve this issue?
The HttpContextAccessor makes use of a static AsyncLocal<T> property under the covers, which means that any HttpContextAccessor implementation will access the same data. This means you can simply do the following:
services.AddSingleton(c => new ContextUser(new HttpContextAccessor(), ctxUser));
// Don't forget to call this; otherwise the HttpContext property will be
// null on production.
services.AddHttpContextAccessor();
If you find this too implicit, or don't the HttpContextAccessor implementation from breaking in the future, you can also do the following:
var accessor = new HttpContextAccessor();
services.AddSingleton<IHttpContextAccessor>(accessor);
services.AddSingleton(c => new ContextUser(accessor, ctxUser));
Or you can "pull out" the registered instance out of the ServiceCollection class:
services.AddHttpContextAccessor();
var accessor = (IHttpContextAccessor)services.Last(
s => s.ServiceType == typeof(IHttpContextAccessor)).ImplementationInstance;
services.AddSingleton(c => new ContextUser(accessor, ctxUser));
What I find a more pleasant solution, however, especially from a design perspective, is to split the ContextUser class; it currently seems to implement two different solutions. You can split those:
public sealed class HttpContextContextUser : IContextUser
{
private readonly IHttpContextAccessor accessor;
public HttpContextContextUser(IHttpContextAccessor accessor) =>
this.accessor = accessor ?? throw new ArgumentNullException("accessor");
public string ContextUserId => this.accessor.HttpContext.User.Identity.Name;
}
public sealed class FixedContextUser : IContextUser
{
public FixedContextUser(string userId) =>
this.ContextUserId = userId ?? throw new ArgumentNullException("userId");
public string ContextUserId { get; }
}
Now, depending on the environment you're running in, you register either one of them:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
if (this.Configuration.IsProduction())
{
services.AddSingleton<IContextUser, HttpContextContextUser>();
}
else
{
string ctxUser = Configuration["Data:ConnectedUser"];
services.AddSingleton<IContextUser>(new FixedContextUser(ctxUser));
}
}

Simplified approach to IOptions<T>

I am trying to get a .NET Framework class library in line with an ASP.NET Core 2.1 application while using builtin DI mechanism. Now, I created a config class and added appropriate section to appsettings.json:
services.Configure<MyConfig>(Configuration.GetSection("MyConfiguration"));
services.AddScoped<MyService>();
In class lib:
public class MyService
{
private readonly MyConfig _config;
public MyService(IOptions<MyConfig> config)
{
_config = config.Value;
}
}
However, in order to build this classlib I have to add Microsoft.Extensions.Options NuGet package. The problem is that package carries a hell of a lot of dependencies which seem rather excessive to add just for the sake of one interface.
So, the question ultimately is, "is there another approach I can take to configure a DI service located in .NET Framework class library which is not dependency heavy?
Check this article written by Filip Wojcieszyn.
https://www.strathweb.com/2016/09/strongly-typed-configuration-in-asp-net-core-without-ioptionst/
You add extension method:
public static class ServiceCollectionExtensions
{
public static TConfig ConfigurePOCO<TConfig>(this IServiceCollection services, IConfiguration configuration) where TConfig : class, new()
{
if (services == null) throw new ArgumentNullException(nameof(services));
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
var config = new TConfig();
configuration.Bind(config);
services.AddSingleton(config);
return config;
}
}
Apply it in configuration:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.ConfigurePOCO<MySettings>(Configuration.GetSection("MySettings"));
}
And then use it:
public class DummyService
{
public DummyService(MySettings settings)
{
//do stuff
}
}
I bumped into this problem a little while ago, if you can even call it a problem really. I think we all tend to get a little shell-shocked when we see a dependency list like that. But as #Tseng mentioned, it's really not a big deal to include a bunch of extra tiny assemblies (they'll be included in the bin already anyways by virtue of a reference in another project). But I will admit it's annoying to have to include them just for the options interface.
How I solved it was by resolving the service dependency in startup.cs and adjust the service's constructor accordingly:
services.AddTransient<MyService>(Configuration.GetConfiguration("MyConfiguration"));
If you don't care about whatever IOptions provides you, why not just inject IConfiguration into your service?
public class MyService
{
private readonly IConfiguration _config;
public MyService(IConfiguration config)
{
_config = config;
}
public void DoSomething()
{
var value = _config["SomeKey"];
// doing something
}
}

How can we store configuration data in new asp.net vnext?

How can we store configuration data in new asp.net vnext? web.config still there(system.web removed, so no any web.config,but I like it) or using new json file for configuration data.
Configuration used best is a starting point to strongly typed options. So what you want to do in startup is register an Options object, set it up with data you read from configuration, and get it from the DI system in the rest of your code.
Example:
Create an Options object
public class ApplicationOptions
{
public bool MyOption { get; set; }
}
Read the configuration and setup the options object then register it in DI
public void Configure(IApplicationBuilder app)
{
// rest of setup
var applicationOptions = new ApplicationOptions();
string myOption;
if (configuration.TryGet("MyOption", out myOption) &&
myOption.Equals("True", StringComparison.OrdinalIgnoreCase))
{
applicationOptions.MyOption = true;
}
// For the scenario above also the code below will work.
// It is currently limited to desktop (ASPNET50, NET45) only.
// services.Configure<ApplicationOptions>(configuration);
services.AddInstance<ApplicationOptions>(applicationOptions);
// more setup
}
And resolve it in your code
public class MyController : Controller
{
public IActionResult Index(ApplicationOptions options)
{
// do something with the options
}
}
Edit: with any practical reusable options object, the code setting it up will probably happen outside the startup class.
In the breaking changes (beta 6), Microsoft.Framework.ConfigurationModel assembly name changed to Microsoft.Framework.Configuration
This is the modified startup class
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var configurationBuilder = new ConfigurationBuilder(appEnv.ApplicationBasePath).
.AddJsonFile("config.json")
.AddEnvironmentVariables();
Configuration = configurationBuilder.Build();
}
Not sure exactly what kind of data you are trying to store but this work for me. I created this file myconfig.json and store that data that I need in a JSON format.
This will register your configuration file, I added this code in the startup.cs page.
Configuration = new Configuration()
.AddJsonFile("config.json")
.AddJsonFile("myconfig.json")
.AddEnvironmentVariables();
To get the data that you need from the your JSON file you need to this at any point on your code.
IConfiguration Configuration = new Configuration().AddJsonFile("myconfig.json");
var jsonNodes = Configuration.Get("SomeJsonNode");
string someJsonString = Configuration.Get("someJsonString");
FYI: At the moment that I tested that code json array were not supported. Not sure if they fixed that now.

Windsor webservice inject properties

I have a MVC application and inject my repositories to my controller what works properly.
Additionally I have a Webservice in my solution which uses exactly the same repositories but when my Webservice is called my repository properties are null.
I register my repositories the following way:
container.Register(Classes.FromAssembly(Assembly.GetAssembly(typeof(HdtRepository))).InSameNamespaceAs<HdtRepository>().WithService.DefaultInterfaces().LifestyleTransient());
my repository properties look like:
public IUserRepository _userRepo { get; set; }
public IHdtRepository _hdtRepo { get; set; }
public ITimeRecordRepository _timeRepo { get; set; }
Can someone tell me why the repositories are not injected to my webservice?
For now I added the following to the constructor of my webservice:
public MyWebservice()
{
_userRepo = MvcApplication.container.Resolve<IUserRepository>();
_hdtRepo = MvcApplication.container.Resolve<IHdtRepository>();
_timeRepo = MvcApplication.container.Resolve<ITimeRecordRepository>();
_locationRepo = MvcApplication.container.Resolve<ILocationRepository>();
_wayRepo = MvcApplication.container.Resolve<IWayPointRepository>();
_wayDataRepo = MvcApplication.container.Resolve<IWayDataRepository>();
}
but as far as I know this is actually a antipattern.
I'm new to all that IoC stuff so could someone please tell me where the problem is.
Cheers,
Stefan
First lets get your project setup with some Windsor installers. They look like this for the most part.
public class ServiceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<IEncryptionService().ImplementedBy<EncryptionService>());
}
}
in your App_Start folder add a class called ContainerConfig.cs that could look something like this.
public class ContainerConfig
{
private static IWindsorContainer _container;
public static IWindsorContainer ConfigureContainer()
{
_container = new WindsorContainer();
_container.Install(FromAssembly.This()).Install(FromAssembly.Named("Project.Dependency"));
_container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel, true));
_container.AddFacility<TypedFactoryFacility>();
var controllerFactory = new WindsorControllerFactory(_container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
return _container;
}
}
Please note that I have a separate project for my Dependency Injection hence the _container.Install(FromAssembly.This()).Install(FromAssembly.Named("Project.Dependency")); line... You can remove the latter .Install(FromAssembly) part.
In your Global.asax you can do something like this:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ContainerConfig.ConfigureContainer();
}
Now in your controllers you can do this:
public class TempController : Controller
{
private readonly IEncryptionService _encryptionService;
public TempController(IEncryptionService encryptionService )
{
_encryptionService = encryptionService;
}
public ActionResult Index()
{
// Example of calling a method on the encryption service.
string hash, salt;
_encryptionService.GethashAndSaltString("I Need Some Loving", out hash, out salt);
return View();
}
}
Please let me know if you get something working with constructor injection. Solving that issue will be a great help going forward and you won't be using property injection. Once we get all of that sorted out we can look at your webervice issues.
I guess this is not possible as far as I've read some other posts.
The problem is that you can't create a custom factory for a Webservice like "WindsorControllerFactory" for the controller.
I'm going to switch to WCF Service.
Resolve a System.Web.Services.WebService instance with Castle (for AOP purposes)