Using multiple values for one key in appSettings - c#

I'm learning about how to use config files and I ran into some problems that I'm hoping someone here can give me some advice. It doesn't matter if my files are XML or not but the majority of examples I have read are using them and Im all for anything that makes my life easier.
the problem Im running into is that the appSettings file seems to be setup to only accept one value for one key and I would like to have something similar to:
<key="Machine List" value="Server105" />
<key="Machine List" value="Server230" />
Ive found a hack here but it was written over 6 years ago and I didn't know if there was a better way.
Again, it doesnt matter if this is XML, a flat file, etc.... Im just trying to learn how to use config files instead of hard coding values directly into the app.
Thanks for your help.

if you really need to store multiple machines under the key, it would be more appropriate to do:
<key="Machine List" value="Server105,Server230" />
with the delimiter being a character of your choosing.

An alternative to entry attributes would be to add child nodes to your setting node:
<setting key="Machine List">
<value>Server105</value>
<value>Server230</value>
</setting>
This way you don't need string manipulations to extract the different values.

You can make use of configuration sections where you can define your own configuration. Just add
<configSections>
<sectionGroup name="MyConfiguration">
<section name="MyQuery" type="namespace.QueryConfigurationSection" allowLocation="true" allowDefinition="Everywhere"/>
</sectionGroup>
</configSections>
after the <configuration> and you can add your custom section just after the appsetting
</appSettings>
<!-- custom query configuration -->
<MyConfiguration>
<MyQuery>
<Query1> </Query1>
<Query2> </Query2>
To read you need to create few classes
/// <summary>
/// Creates a custom configuration section inside web.config
/// </summary>
public class QueryConfigurationSection : ConfigurationSection
{
//query 2
[ConfigurationProperty("Query1")]
public QueryElement1 Query1
{
get { return this["Query1"] as QueryElement1; }
}
//query 2
[ConfigurationProperty("Query2")]
public QueryElement2 Query2
{
get { return this["Query2"] as QueryElement2; }
}
}
public class QueryElement1 : ConfigurationElement
{
public string Value { get; private set; }
protected override void DeserializeElement(XmlReader reader, bool s)
{
Value = reader.ReadElementContentAs(typeof(string), null) as string;
}
}
public class QueryElement2 : ConfigurationElement
{
public string Value { get; private set; }
protected override void DeserializeElement(XmlReader reader, bool s)
{
Value = reader.ReadElementContentAs(typeof(string), null) as string;
}
}
The overridden DeserializedElement will deserialize the Xml(inside) the QueryElement1 & 2.
To read the values from the main application, you just need to call the following:
//calling my query config
QueryConfigurationSection wconfig = (QueryConfigurationSection)ConfigurationManager.GetSection("MyConfiguration/MyQuery");
string _query1 = wconfig.Query1.Value;
string _query2 = wconfig.Query2.Value;

Maybe you should rethink your design. I would just put the list you want in another file and not the config. You could do a delimited string but then if the list got long it would be hard to manage it. You could just put it in a text file or an XML/JSON file. Here is some code that might be a good place to start.
public static class MyClass
{
private static string _path = ConfigurationManager.AppSettings["FilePath"];
private static List<string> _list;
static MyClass()
{
_list = new List<string>();
foreach (string l in File.ReadAllLines(_path))
_list.Add(l);
}
public static List<string> GetList()
{
return _list;
}
}
I made it a static class so it would only read from the file once and not everytime you need to get information from it.
This might also be a good thing to put in a database if you need more functionality. But for a small read-only kind of thing, this will work better than a delimited string for longer values.

Related

ConfigurationBinder bind to arrays with one or more elements

I'm currently trying to use the default ConfigurationBinder
from AspNetCore 6 especially IConfiguration.Get<T>()
Get<T>() is used to map a config file section to an object,
but it doesn't work correctly with collections like arrays and lists.
It works fine if there are at least 2 items in the array
but if there's only one item in the array then the mapping doesn't work. This seems to only affect XML files.
The array can be nested deep inside my sections.
The main problem seems to be that the config keys are generated like this:
collection1:item:myName for arrays with one element.
vs
collection2:item:0:myName for arrays with more than one element.
Has anyone a good idea how to accomplish the mapping to arrays
inside XML config sections that might have one or more elements?
// Nugets:
// Microsoft.Extensions.Configuration
// Microsoft.Extensions.Configuration.Xml
// Microsoft.Extensions.Configuration.Binder
public void Test()
{
var builder = new ConfigurationBuilder();
builder.AddXmlFile("MyConfig.config");
var config = builder.Build();
var section1 = config.GetSection("collection1");
var section2 = config.GetSection("collection2");
var bound1 = section1.Get<MyObject>(); // ERROR: Mapped to empty List
var bound2 = section2.Get<MyObject>(); // mapped correctly to 2 items
}
public class MyObject
{
public List<MyItem> Item { get; set; }
}
public class MyItem
{
public string MyName { get; set; }
public string MyVal { get; set; }
public override string ToString() => $"{MyName} = {MyVal}";
}
MyConfig.config
<configuration>
<collection1 >
<item myName="MyName1" myVal="MyString1" />
</collection1>
<collection2 >
<item myName="MyName1" myVal="MyString1" />
<item myName="MyName2" myVal="MyString2" />
</collection2>
</configuration>
Do not get objects yourself, configure a mapping:
services.Configure<MyObject>(configuration.GetSection("collection1"));
Then inject your settings where necessary as Options:
public class MyClass{
public MyClass(IOptions<MyObject> options){
...
}
}
This should handle the keys correctly.
That said, keep in mind that arrays in configurations can produce a lot of problems when using multiple stages, i.e. production and dev configurations files, see my answer here (as I see now from your question, the samples there are probably not 100% correct for an array with one element, but they definitely are for multiple elements).
On the other hand, is the number of elements really unbound or like 5 or 10 would be enough? You could add normal properties, e.g. 1 to 5, and collect them in an array in your MyObject, like:
public class MyObject{
public string P1 {get;set;}
public string P2 {get;set;}
public List<string> GetProperties(){...}
}
I think i found an easy solution to the Problem for now.
<configuration>
<collection1 >
<item name="MyName1" myVal="MyString1" />
</collection1>
<collection2 >
<item name="MyName1" myVal="MyString1" />
<item name="MyName2" myVal="MyString2" />
</collection2>
</configuration>
If we give each element of the collection the attribute name
then the mapping works fine as long as the name is unique.
Binding a single element to an array on ConfigurationBinder will be fixed in .NET 7.
https://github.com/dotnet/runtime/issues/57325

Can DisplayNameAttribute extension with XML source update in runtime?

This might be more of a question related to how .NET Framework works, than looking for an actual solution. Reason is I would like to know if this is something I should pursue in fixing, or try something else entirely. I did some searching, but couldn't find the right answer in my opinion.
I am working on an ASP.NET MVC5 application that utilizes a translation provider with an XML file as it source. In some scenarios I use a DisplayNameAttribute extension to decorate model properties to provide translations. It is made by referencing the solution here: https://stackoverflow.com/a/9723620/1501132
This is my implementation:
[AttributeUsage(AttributeTargets.Property)]
public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
public LocalizedDisplayNameAttribute(string key, string page = null) : base(FormatMessage(key, page))
{
}
private static string FormatMessage(string key, string page = null)
{
if (!string.IsNullOrWhiteSpace(key) && string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key);
}
else if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key, page);
}
else
{
return string.Empty;
}
}
}
The "TextGetter" is a separate library that handles fetching strings from the XML data source.
The attribute extension is used like so:
[LocalizedDisplayName("Timestamp", "/report")]
public DateTimeOffset Timestamp { get; set; }
The website also has a feature where a super user can edit the translation XML file, in case some translations are wrong or missing. Usually an edit in the XML file is visible immediately, except for properties with this particular attribute. I know that normally when using DisplayName attribute with a hardcoded value can not be changed because it is compiled, though I was under the assumption that since this uses an XML file as reference, I believed that if the XML was changed it would be reflected immediately in this case as well. But that seems not to happen.
Being able to change translations on the fly is an important feature; should I seek some other solution? I can set the property names with translations in the views, which is working, but that will entail a LOT of refactoring, and keeping it as annotations is just more neat.
I don't really know where to take it from here.
Found a solution in the meantime, and just putting it out there if anyone stumbles across it. So this is what you should do, if you want to make an attribute that derives from DisplayName used for localization, and on top of that have a localization source that can change and update during runtime:
[AttributeUsage(AttributeTargets.Property)]
public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private readonly string _key;
private readonly string _page;
public LocalizedDisplayNameAttribute(string key, string page = null) : base(key)
{
this._key = key;
this._page = page;
}
public override string DisplayName => this.FormatMessage(this._key, this._page);
private string FormatMessage(string key, string page = null)
{
if (!string.IsNullOrWhiteSpace(key) && string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key);
}
else if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key, page);
}
else
{
return string.Empty;
}
}
}
The contents of "FormatMessage" can be whatever you want really, just insert there whatever code you need to fetch your translated string.

loading configuration settings from a database

I'm currently writing a MVVMC application in C# (utilising Entity Framework).
What is the correct (best practice) way for loading and storing configuration values?
First of all, there is the question of how to store the configuration settings... would the best way to have the table designed like this: tblConfig(configid, configref, configval) or like this: tblConfig(configid,option1,option2,option3) - replacing option1 etc, with meaningful names (e.g. logoimage)
Next, there is how to retreive/store the settings in the app; My idea was to store the configuration in a table in the database, and then create a static class - something like this:
public static class ConfigClass
{
public static ConfigClass()
{
//load values from database
ContextClass SiteContext = new ContextClass();
logoImage = SiteContext.ConfigSettings.ToList().Find(c => c.configreference == "logoimage").configvalue;
admintoauthoriseaccounts = Convert.ToBoolean(SiteContext.ConfigSettings.ToList().Find(c => c.configreference == "logoimage").configvalue);
defaultaccountrole = Convert.ToInt32(SiteContext.ConfigSettings.ToList().Find(c => c.configreference == "logoimage").configvalue);
}
public static string logoImage = "";
public static bool admintoauthoriseaccounts = true;
public static Int32 defaultaccountrole = 1;
}
Class for the ConfigSetting DbSet Repository:
public class ConfigSetting
{
public string configreference { get; set; }
public string configvalue { get; set; }
}
Is this a good idea? Or should I forget the static class and just read settings directly out of the the DbSet repository?
Thanks.
Most common practice is to store configuration --including the string to connect to that database-- in app.config (or web.config) for the project. Any settings you store in the database would only add the value to, i guess, allow user modification.
If that's your goal:
Store user customization settings in db
Store application config in app.config/web.config
Otherwise:
Store both in app.config.
Serve to the rest of your layers via class, static class, singleton pattern, what have you.
Regardless of that choice:
Best if you do not use static class to hit the database over and over again for settings. This will cause performance degradation.
Branding like a logoimage doesn't sound like something that needs to change often, or per user, and wouldn't really be worth storing in a database.

Store a range of values in web.config - what data structure to use

What is the best data structure to use for the following scenario?
I want to have a fee percentage based on the price of certain items.
For example if (simplest scenario, could have way more entries here.)
Price -> Percentage
<$5 -> 20%
$5-$10 -> 15%
$10-$20 -> 13.5%
>$20 -> 12.5%
And i want this to be flexible and so i want to put this in the web.config (or if you think its a better idea - in sql server)
How to go about implementing this?
You can use ConfigurationSections (http://msdn.microsoft.com/en-us/library/2tw134k3.aspx)
Basically, it let you serialize and deserialize from your web.config complex structures directly to a C# class you define. This is an example, it may not work perfectly (or even compile!) but it gives you the idea of what you can get from ConfigurationSection. Hope it helps.
namespace Project
{
public class PricesConfiguration : System.Configuration.ConfigurationSection
{
public static PricesConfiguration GetConfig()
{
return (PricesConfiguration )System.Configuration.ConfigurationManager.GetSection("pricesConfiguration") ?? new ShiConfiguration();
}
[System.Configuration.ConfigurationProperty("prices")]
public PricesCollection Prices
{
get
{
return (PricesCollection)this["prices"] ??
new PricesCollection();
}
}
}
public class PricesCollection : System.Configuration.ConfigurationElementCollection
{
public PriceElement this[int index]
{
get
{
return base.BaseGet(index) as PriceElement;
}
set
{
if (base.BaseGet(index) != null)
base.BaseRemoveAt(index);
this.BaseAdd(index, value);
}
}
protected override System.Configuration.ConfigurationElement CreateNewElement()
{
return new PriceElement();
}
protected override object GetElementKey(System.Configuration.ConfigurationElement element)
{
var price = (PriceElement)element;
return string.Format("{0}-{1}->{2}%",price.Start,price.End,price.Percentage);
}
}
public class PriceElement : System.Configuration.ConfigurationElement
{
[System.Configuration.ConfigurationProperty("start", IsRequired = false)]
public int? Start
{
get
{
return this["start"] as int?;
}
}
[System.Configuration.ConfigurationProperty("end", IsRequired = false)]
public int? End
{
get
{
return this["end"] as int?;
}
}
[System.Configuration.ConfigurationProperty("percentage", IsRequired = true)]
public string Percentage
{
get
{
return this["percentage"] as string;
}
}
}
}
And the web.config will look like:
<configuration>
<configSections>
<section name="pricesConfig" type="Project.PricesConfig, Project"/>
</configSections>
<pricesConfig>
<prices>
<add end="5" percentage="20" />
<add start="5" end="10" percentage="15" />
<add start="10" end="20" percentage="13.5" />
<add start="20" percentage="12.5" />
</prices>
</pricesConfig>
</configuration>
for using it, just call
var config = PricesConfiguration.GetConfig();
I would put it on table on SQL Server; the table would have 4 columns:
Id
StartValue
EndValue
Percentage
I would catch this data on the application side if necessary. You can use a SqlCacheDependency object to ensure that any update on the database gets quickly reflected on the application side. I would not put on Web.config since Web.config is mostly suitable for Key-Value pairs and I don't see this being the case here.

unable to save settings in app.exe.config

i am facing one problem.
i want to save settings in app.config file
i wrote separate class and defined section in config file..
but when i run the application. it does not save the given values into config file
here is SettingsClass
public class MySetting:ConfigurationSection
{
private static MySetting settings = ConfigurationManager.GetSection("MySetting") as MySetting;
public override bool IsReadOnly()
{
return false;
}
public static MySetting Settings
{
get
{
return settings;
}
}
[ConfigurationProperty("CustomerName")]
public String CustomerName
{
get
{
return settings["CustomerName"].ToString();
}
set
{
settings["CustomerName"] = value;
}
}
[ConfigurationProperty("EmailAddress")]
public String EmailAddress
{
get
{
return settings["EmailAddress"].ToString();
}
set
{
settings["EmailAddress"] = value;
}
}
public static bool Save()
{
try
{
System.Configuration.Configuration configFile = Utility.GetConfigFile();
MySetting mySetting = (MySetting )configFile.Sections["MySetting "];
if (null != mySetting )
{
mySetting .CustomerName = settings["CustomerName"] as string;
mySetting .EmailAddress = settings["EmailAddress"] as string;
configFile.Save(ConfigurationSaveMode.Full);
return true;
}
return false;
}
catch
{
return false;
}
}
}
and this is the code from where i am saving the information in config file
private void SaveCustomerInfoToConfig(String name, String emailAddress)
{
MySetting .Settings.CustomerName = name;
MySetting .Settings.EmailAddress = emailAddress
MySetting .Save();
}
and this is app.config
<configuration>
<configSections>
<section name="MySettings" type="TestApp.MySettings, TestApp"/>
</configSections>
<MySettings CustomerName="" EmailAddress="" />
</configuration>
can u tell me where is the error.. i tried alot and read from internet. but still unable to save information in config file..
i ran the application by double clicking on exe file also.
According to the MSDN: ConfigurationManager.GetSection Method,
The ConfigurationManager.GetSection method accesses run-time configuration information that it cannot change. To change the configuration, you use the Configuration.GetSection method on the configuration file that you obtain by using one of the following Open methods:
OpenExeConfiguration
OpenMachineConfiguration
OpenMappedExeConfiguration
However, if you want to update app.config file, I would read it as an xml document and manipulate it as a normal xml document.
Please see the following example:
Note: this sample is just for proof-of-concept. Should not be used in production as it is.
using System;
using System.Linq;
using System.Xml.Linq;
namespace ChangeAppConfig
{
class Program
{
static void Main(string[] args)
{
MyConfigSetting.CustomerName = "MyCustomer";
MyConfigSetting.EmailAddress = "MyCustomer#Company.com";
MyConfigSetting.TimeStamp = DateTime.Now;
MyConfigSetting.Save();
}
}
//Note: This is a proof-of-concept sample and
//should not be used in production as it is.
// For example, this is not thread-safe.
public class MyConfigSetting
{
private static string _CustomerName;
public static string CustomerName
{
get { return _CustomerName; }
set
{
_CustomerName = value;
}
}
private static string _EmailAddress;
public static string EmailAddress
{
get { return _EmailAddress; }
set
{
_EmailAddress = value;
}
}
private static DateTime _TimeStamp;
public static DateTime TimeStamp
{
get { return _TimeStamp; }
set
{
_TimeStamp = value;
}
}
public static void Save()
{
XElement myAppConfigFile = XElement.Load(Utility.GetConfigFileName());
var mySetting = (from p in myAppConfigFile.Elements("MySettings")
select p).FirstOrDefault();
mySetting.Attribute("CustomerName").Value = CustomerName;
mySetting.Attribute("EmailAddress").Value = EmailAddress;
mySetting.Attribute("TimeStamp").Value = TimeStamp.ToString();
myAppConfigFile.Save(Utility.GetConfigFileName());
}
}
class Utility
{
//Note: This is a proof-of-concept and very naive code.
//Shouldn't be used in production as it is.
//For example, no null reference checking, no file existence checking and etc.
public static string GetConfigFileName()
{
const string STR_Vshostexe = ".vshost.exe";
string appName = Environment.GetCommandLineArgs()[0];
//In case this is running under debugger.
if (appName.EndsWith(STR_Vshostexe))
{
appName = appName.Remove(appName.LastIndexOf(STR_Vshostexe), STR_Vshostexe.Length) + ".exe";
}
return appName + ".config";
}
}
}
I also added "TimeStamp" attribute to MySettings in app.config to check the result easily.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="MySettings" type="TestApp.MySettings, TestApp"/>
</configSections>
<MySettings CustomerName="" EmailAddress="" TimeStamp=""/>
</configuration>
In many cases (in a restricted user scenario) the user do not have write access directly to the application config file. To come around this, you need to use the usersettings.
If you right-click your project and select the tab named "Settings", you can make a list of settings that are stored with the config file. If you select the "Scope" to be "User", the setting are automatically stored in the config file using a type that will automatically store the settings under the users AppData area, where the user allways has write access. The settings are also automatically provided as properties created in the Properties\Settings.Designer.cs code file, and are accessible in your code in Properties.Settings.Default .
Example:
Let's say you add a user setting called CustomerName:
On loading the app, you would want to retreive the value from the stored setting ( either default value as it is in the app config, or if it is stored for this user, the config file for the user):
string value = Properties.Settings.Default.CustomerName;
When you want to change the value, just write to it:
Properties.Settings.Default.CustomerName = "John Doe";
When you want to save all the settings, just call Save:
Properties.Settings.Default.Save();
Note that when you develop, the user file will occationally be reset to the default, but this only happens when building the app. When you just run the app, the settings you store will be read from the user-config file for the app.
If you still want to create your own handling of the settings, you can try this once, and look at what VisualStudio has automatically created for you to get an idea of what you need to get this working.
In order to actually update the config file, you'll need to call .Save() on the Configuration object - not just your config section object.
You should check out Jon Rista's three-part series on .NET 2.0 configuration up on CodeProject.
Unraveling the mysteries of .NET 2.0 configuration
Decoding the mysteries of .NET 2.0 configuration
Cracking the mysteries of .NET 2.0 configuration
Highly recommended, well written and extremely helpful! It shows all the ins and outs of dealing with .NET 2.0 and up configuration, and helped me very much getting a grasp on the subject.
Marc
Be aware that the appname.vshost.exe.Config is reverted to it's the original state at the end of the debugging session. So you might be saving to the file (you can check by using Notepad during execution), then losing the saved contents when the debugging stops.

Categories