How modify loaded configuration by CacheManager in runtime - c#

I'm using #Victor P solution to manage the cache in my application.
The configuration is loaded from the application settings, but we have a policy of not add sensitive information in the code, and on the other hand, the production instance of Redis requires authentication. This password is loaded from the environment variables, but I can't find the way to modify the Redis configuration in runtime.
Here is how we are doing it now
// Locad configuration of cache type: MemoryCache or RedisCache
string cacheManagerName = ConfigurationManager.AppSettings["CacheManagerName"];
// Build cache configuration from configuration section
var config = ConfigurationBuilder.LoadConfiguration(cacheManagerName);
//TODO: Modify config if the variable environment for the password is set
// This will only necessary if the cache type is Redis
//Create cachemanager instance
_kernel.Bind(typeof(ICacheManager<>)).ToMethod((ctx) => CacheFactory.FromConfiguration(ctx.GenericArguments[0], config)).InSingletonScope();
Configuration example:
<add key="CacheManagerName" value="RedisCache" />
<cacheManager xmlns="http://cachemanager.michaco.net/schemas/CacheManagerCfg.xsd">
<managers>
<cache name="MemoryCache" updateMode="None" enableStatistics="false" enablePerformanceCounters="true">
<handle name="default" ref="MemoryCacheHandle" />
</cache>
<cache name="RedisCache" updateMode="Up" enablePerformanceCounters="true"
enableStatistics="false" backplaneName="RedisConfigurationId"
backplaneType="CacheManager.Redis.RedisCacheBackplane, CacheManager.StackExchange.Redis"
serializerType="CacheManager.Serialization.Json.JsonCacheSerializer, CacheManager.Serialization.Json">
<handle name="RedisConfigurationId" ref="RedisCacheHandle" isBackplaneSource="true"/>
</cache>
</managers>
<cacheHandles>
<handleDef id="MemoryCacheHandle" type="CacheManager.SystemRuntimeCaching.MemoryCacheHandle`1, CacheManager.SystemRuntimeCaching"
defaultExpirationMode="Sliding" defaultTimeout="30m" />
<handleDef id="RedisCacheHandle" type="CacheManager.Redis.RedisCacheHandle`1, CacheManager.StackExchange.Redis"
defaultExpirationMode="Sliding" defaultTimeout="30m" />
</cacheHandles>
</cacheManager>
<cacheManager.Redis xmlns="http://cachemanager.michaco.net/schemas/RedisCfg.xsd">
<connections>
<connection id="RedisConfigurationId"
allowAdmin="true"
password=""
ssl="false"
sslHost="">
<endpoints>
<endpoint host="127.0.0.1" port="6379" />
</endpoints>
</connection>
</connections>
</cacheManager.Redis>

Removing secrets from an app/web.config was always an issue by itself I guess.
There is a documentation post which explains some options.
Regarding CacheManager. You can use the <connectionStrings> section for configuring Redis, instead of the cacheManager.Redis section, and then store that connection string in a separated "secret" file
<connectionStrings configSource="ConnectionStrings.config">
</connectionStrings>
That's still pretty stupid though in my opinion. So the best way is to configure that part entirely by code and read secrets from some secure store. Btw, environment variables are not secure at all either.
You can "trick" cachemanager and add just the redis configuration by code via RedisConfigurations. And reference the config key as usual.

Related

C# - Unable to write to app.config after deployment

I am developing a WPF application which runs well in visual studio v.2022 very well. My problem is when I deploy the application to another computer for testing it does not seem to be updating app.config data. When I change the ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None) to NONE it works well in both debug and release mode in Visual Studio but throws an error when deployed to another computer. The error I am getting is "System configuration.configuration error exception - An error ocurred while loading the the configuration file myApp.dll.config. Access denied "
I gather that the user has no write privileges on the Program Files folder where the app.config is stored. However, when I change the user level to either Roaming or Roaming and local, then nothing happens as the configuration file is not located and if at all the changes are made then they are not persisting. Here is my app.config file
<configuration>
<appSettings>
<add key="Server" value=""/>
<add key="Port" value=""/>
<add key="Database" value=""/>
<add key="User" value=""/>
<add key="Pwd" value=""/>
<add key="Code" value=""/>
<add key="Access" value=""/>
<add key="Status" value=""/>
<add key="EndDate" value=""/>
</appSettings>
</configuration>
Here is my method for updating the the app.config
public static void UpdateSetting(string key, string value)
{
Configuration roaming = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = roaming.FilePath;
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
var appSettings = configuration.AppSettings;
foreach (var keys in appSettings.Settings.AllKeys.Where(x => x.StartsWith(key)))
{
appSettings.Settings[keys].Value = value;
}
configuration.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
}
and here is an example of how the method is called Settings.UpdateSetting("Code", _Code);
Any help will be much appreciated
Following #Joe's comment it occurred to me that it was not possible to write to config application data at runtime. I opted to create a new section in the config file in the Properties.Settings folder in my solution and assigned the scope to User and not application since the user is able to update the data at runtime. Double click on the settings.Settings file to open in designer view and literally entered the settings names to be stored.
The stored settings can be saved and retrieved using the methods below.Properties.Settings.Default.yourSetting. to get the settings.
And to store/save the settings
Properties.Settings.Default.yourSetting= val;`
Properties.Settings.Default.Save();`
That solved my problem.

CacheManger Using Redis Multiplexer with Web.Config Configuration

I need to implement Michael solution using two Cache Instances like he explain in WhatIfRedisStopsWorkingHowDoIkeepMyAppRunning but using configuration in web.config.
Finally i only have this line of code
var defaultConfig = ConfigurationBuilder.LoadConfiguration("defaultCache");
I don`t find how to access to the ConnectionMultiplexer to hook me in the events or do it by config...
Is posible?
There are two ways to configure Redis via app/web.config in CacheManager,
via ConnectionString
<connectionStrings>
<add name="redisFromConnectionStrings" connectionString="127.0.0.1:6379,allowAdmin=True,connectTimeout=11,ssl=False,abortConnect=False,connectRetry=10" />
</connectionStrings>
or Redis configuration section
<cacheManager.Redis xmlns="http://cachemanager.michaco.net/schemas/RedisCfg.xsd">
<connections>
<connection id="redisAppConfig" allowAdmin="true" password="" ssl="false" sslHost="" connectionTimeout="11" database="3">
<endpoints>
<endpoint host="127.0.0.1" port="6379" />
</endpoints>
</connection>
</connections>
</cacheManager.Redis>
:UPDATE:
There is currently no option to access the connection multiplexer used by CacheManager.
But you can pass in an existing multiplexer to the configuration.
var defaultConfig = ConfigurationBuilder.LoadConfiguration("defaultCache");
var multiplexer = ConnectionMultiplexer.Connect(...);
defaultConfig = defaultConfig
.Builder
.WithRedisConfiguration("redisConfig", multiplexer )
.Build();
Of course you have to instantiate the multiplexer yourself and cannot use the web/app config anymore to configure the Redis part. You'd have to handle that yourself...

Extend an element in App.config

We have an app.config we are using with Carbonator:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="carbonator" type="Crypton.Carbonator.Config.CarbonatorSection, Crypton.Carbonator"/>
</configSections>
<carbonator defaultCulture="en-US" logLevel="1" collectionInterval="1000" reportingInterval="1000" >
<statsd server="127.0.0.1" port="8125" />
<counters>
<add path="processor_information.pct_processor_time.total" category="Processor" counter="% Processor Time" instance="_Total" />
<add path="memory.available_MBytes" category="Memory" counter="Available MBytes" instance="" />
<add path="memory.pct_commited_bytes_in_use" category="Memory" counter="% Committed Bytes In Use" instance="" />
</counters>
</carbonator>
</configuration>
We want to allow users to configure their own custom counters in an external config file that we reference from the <counters> element. For example, we would like to allow the user config file to look like:
<add path="logical_disk.pct_free_space.C" category="LogicalDisk" counter="% Free Space" instance="C:" />
<add path="logical_disk.disk_read_bytes_per_sec.C" category="LogicalDisk" counter="Disk Read Bytes/sec" instance="C:" />
<add path="logical_disk.disk_write_bytes_per_sec.C" category="LogicalDisk" counter="Disk Write Bytes/sec" instance="C:" />
I don't even know if this is possible outside of an appConfig element, but any help is appreciated.
According to this answer it should be possible. Same way is also described in this article.
But I don't think it's a good idea for one reason - if a user makes a mistake in his configuration extension, it will prevent the application from executing since the application configuration became invalid.
I would rather use the configuration in the app.config file to provide default values and implement some user configuration myself. Is such case, you can use whatever configuration format you like, for example JSON, which would be also better (easier to create and edit) for users. In your application, you simply merge both configurations (app.config values are default values which will be overwritten by the user's configuration).

Make the web.config dynamic

We have an application and it is deployed across different pipeline dedicated for various release.
For ex - pipeline A - dedicated for march release
pipeline B - for June release etc
Within each pipeline we various environment like DEV, SIT etc
Now , with release , while deploying code , we need to make changes in the webcofig file , because the urls that we have in config are pipeline and environment dependent.
For Example we have a web server - box 1 for dev environment .
we have Pipeline A , Pipeline B deployed as websites. The web.config of pipeline A will look like -
<configMap hostnameList="box1" name="DevEnvironment">
<include set="Dev" />
</configMap>
<configSet name="Dev">
<add key="someUrl" value="http://somapp-piplelineA-Dev.app.com"/>
</configSet>
The web.config of pipeline B will look like -
<configMap hostnameList="box1" name="DevEnvironment">
<include set="Dev" />
</configMap>
<configSet name="Dev">
<add key="someUrl" value="http://somapp-piplelineB-Dev.app.com"/>
</configSet>
If you see this config , in the value for key someurl , the pipelineA was changed to pipelineB. These changes are tiresome when there are a lot of keys. So, we want to create a single web.config that can be used by all environment and which would not require any change.
With Octopus Deploy you can deploy your web applications semi- or fully-automatically. But also it can perform Web.config Transformation for each environment separately.
you can use config transforms ability built in inside visual studio
if you create a new asp .net web project you will see a sample inside web.debug.config and web.release.config.
you can also right click on web.config and click Add Config Transform and you will have a config transform file for each of your build configurations.
you can also use SlowCheetah. it is a very handy extension.
This is what I implemented to allow us to have the config dynamically created depending on the build type, utilizing build events.
Which will allow you to have 1 config to rule them all :)
https://xmlpreprocess.codeplex.com/
Project Description
XmlPreprocess is a command-line utility that can modify annotated XML files much like a code preprocessor. It is useful for deploying configuration files to different environments making substitutions such as connection strings. It is easily integrated into almost any script, build tool or deployment package to simplify and centralize your deployment strategy.
My build event
C:\XMLPreprocessor\XmlPreprocess.exe /i "C:\AppConfig\Core.config" /dbkind mssql /db "Server=localhost\SQLEXPRESS;Database=DB1;User Id=dbreader; Password=pass1;" /e $(ConfigurationName)
This is an example of my configuration XML file the {} params are retrieved from a SQL configuration database which contains the release type (1=Debug,2=Test,3=Release) and the values are populated accordingly.
<Nini>
<Section Name="AppSettings">
<!-- ifdef _xml_preprocess -->
<!--
<Key Name="RSAKeyStrength" Value="${RSAKeyStrength}"/>
<Key Name="EventLog_Name" Value="{EventLog_Name}"/>
<Key Name="DomainAddress" Value="${DomainAddress}"/>
<Key Name="AuthIssuer" Value="${AuthIssuer}"/>
-->
<!-- else -->
<Key Name="RSAKeyStrength" Value="2048"/>
<Key Name="EventLog_Name" Value="MyApp"/>
<Key Name="DomainAddress" Value="mydomain.com"/>
<Key Name="AuthIssuer" Value="auth.domain.com"/>
<!-- endif -->
</Section>
<Section Name="ConnectionStrings">
<!-- ifdef _xml_preprocess -->
<!--
<Key Name="IdentityUserModelEntities" Value="data source=${DB1ConnectionString};MultipleActiveResultSets=True"/>
<Key Name="DB1ModelEntities" Value="data source=${DB1ConnectionString};MultipleActiveResultSets=True"/>
<Key Name="LoggingDB1Entities" Value="data source=${LoggingDB1ConnectionString};MultipleActiveResultSets=True"/>
-->
<!-- else -->
<Key Name="IdentityUserModelEntities" Value="data source=localhost\SQLEXPRESS;initial catalog=DB1;user id=admin;password=pass1;MultipleActiveResultSets=True"/>
<Key Name="DB1ModelEntities" Value="data source=localhost\SQLEXPRESS;initial catalog=DB1;user id=admin;password=pass1;MultipleActiveResultSets=True"/>
<Key Name="LoggingDB1Entities" Value="data source=localhost\SQLEXPRESS;Initial Catalog=LoggingDB1;user id=logging_admin;Password=pass1;MultipleActiveResultSets=True"/>
<!-- endif -->
</Section>
</Nini>

Get connectionstring from web.config

I use System.Configuration.ConfigurationManager.AppSettings["key1"] in settings.designer.cs file. It's working fine in the development but after I moved all the .dll files into production it is not working.
In web.config file I added app settings in development and production both. What is the problem?
Code from settings.designer.cs file
get
{
return WebConfigurationManager.AppSettings["ConnectionString"];
//return (AppSettings["ConnectionString"]);
//return ((string)(this["ConnectionString"]));
}
I tried all three return statements. 3rd return is working fine in both dev & prod but it is not rendering from web.config.
Code in web.config
<add key="ConnectionString" value="connection string values are given here">
Don't use WebConfigurationManager.
Use System.Configuration.ConfigurationManager.AppSettings["key"] instead to read key-value pair kept in Web.config, e.g.:
<configuration>
<appSetttings>
<add key="key1" value="value1" />
</appSetttings>
</configuration>
and System.Configuration.ConfigurationManager.ConnectionStrings["name"].ConnectionString to read connection string, e.g.:
<configuration>
<connectionStrings>
<add name="name" connectionString="value1" />
</connectionStrings>
</configuration>
You have to add configuration setting (connectionstring) to last execution program config file.

Categories

Resources