C# Dynamically make settings within application then save persistently - c#

I am finding a lot of different ways to do this and I'm not sure the direction I should go...
I have an application that will run on several personal computers. I am looking for a way to keep a list of application settings persistently.
The idea being that the user will be able to choose amongst a list of applications. Those applications will then be saved until the user removes them. I need to save the application name and the corresponding path.
The problem is that I can't seem to save the key, value pairs to new settings in visual studio and have them persist. I need to write a file to save the files, how do I go about doing that... Should I write them to system.configuration, JSON or XML??? Does anyone have a good walkthrough?

Well, there are a lot of ways to do that. For a simple approach, you can use XML serialization. First create a class that represents all the settings you want to save, and add the Serializable attribute to it, for example:
[Serializable]
public class AppSettings
{
public List<UserApp> Applications { get; set; }
}
[Serializable]
public class UserApp
{
public string Path { get; set; }
public string Name { get; set; }
}
Then, add the following methods to it:
public static void Save(AppSettings settings)
{
string xmlText = string.Empty;
var xs = new XmlSerializer(settings.GetType());
using (var xml = new StringWriter())
{
xs.Serialize(xml, settings);
xml.Flush();
xmlText = xml.ToString();
}
string roamingPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
File.WriteAllText(roamingPath + #"\settings.xml", xmlText);
}
public static AppSettings Load()
{
string roamingPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (!File.Exists(roamingPath + #"\settings.xml"))
return new AppSettings();
string xmlText = File.ReadAllText(roamingPath + #"\settings.xml");
var xs = new XmlSerializer(typeof(AppSettings));
return (AppSettings)xs.Deserialize(new StringReader(xmlText));
}
Then, to save, do:
AppSettings settings = new AppSettings();
settings.Applications = new List<UserApp>();
settings.Applications.Add(new UserApp { Path = #"C:\bla\foo.exe", Name = "foo" });
AppSettings.Save(settings);
And to load:
AppSettings settings = AppSettings.Load();
You can also edit the loaded settings and save it again, overwriting the older.
For more a more complex approach, save into a database.

Add a setting to the settings using the instructions shown in below screenshot:
NOTE: Double click Properties shown with first arrow.
Then you can update that value at runtime like this:
namespace ConsoleApplication1
{
public class Program
{
public static void Main()
{
var defSettings = ConsoleApplication1.Properties.Settings.Default;
var props = defSettings.Test = "Whatever";
// Save it so it persists between application start-ups
defSettings.Save();
Console.Read();
}
}
}
The settings will be stored in the user's profile.

Related

Making json configs for applications c#

i need a config file for my applications and i've looked through internet without really finding what I want, I want to set my config to a var and use it like config.somethingInTheConfig.
I tried some things but it didn't work,
the config file :
{
"id": 00,
"somethings": true,
"yes": "idkjustsomething"
}
the Config class :
class Config
{
public static int id { get; set; }
public static bool somethings { get; set; }
public static string yes { get; set; }
}
Code to read it
using (StreamReader streamReader = new StreamReader("config.json"))
{
string json = streamReader.ReadToEnd();
Config config = JsonConvert.DeserializeObject<Config>(json);
Console.WriteLine(config.id);
}
I want it to show the id in the config in the console but it doesn't work nd gives me an error, anyone could help ?
The properties are marked static.
JsonConvert will not be able to read values into the static properties.
And since you are not defining values for them at design time, the properties will be set to their default values unless you manually change them.

Saving changes made to controls on form? [duplicate]

This question already has answers here:
How can I save application settings in a Windows Forms application?
(14 answers)
Closed 7 years ago.
Question
How can I save "settings" so that they can be used again after the application has been closed?
When I say settings I mean different properties of the controls on my form.
What is the easiest and most appropriate method? I've read you can save to the system registry or a xml file?
Background
I have a form that creates a row in a Table that changes depending on what control is set or change.
I would like to be able to save different configurations and display them in a combobox for repeated use.
Example
The end user fill in all textboxs and ticks checkboxes.
They then click add to favourites
They add a favourite name and save
The save is then permanently visible in the favourites combobox.
My form
There are many ways to do this. But in all these options, you need to store the users selection somewhere. You can store this in
A database table, associate the setting with a unique user ID, like LoginID
A Preferences XML file : Refer this
As a Setting in your project : Refer this
As a Registry Entry : Refer this
An INI File
You might want to take a look at Persisting Application Settings in the .NET Framework
One way you could save the data would be to write it to the registry, under the HKCU node. This way different users of your application will have their own settings even if the app is on the same machine. It also keeps the file system a little cleaner and doesn't require a database. But the downside is that the favorites only live on the machine, and don't roam with the user across devices.
A way to implement this would be to wrap your form settings in a class that knows how to save and load values from the registry. This, along with a registry helper class, could make it pretty easy to add "Favorites" functionality to your form.
For example, you could first create a Registry helper class that will read and write settings to the HKCU node (so the settings are specific to the logged in user):
public class RegHelper
{
private static readonly RegistryKey Root = Registry.CurrentUser
.CreateSubKey(#"Software\CompanyName\ApplicationName");
private readonly RegistryKey _thisKey = Root;
public RegHelper() { }
public RegHelper(string favoriteKey)
{
_thisKey = Root.CreateSubKey(favoriteKey);
}
public List<string> GetSubKeys()
{
return _thisKey.GetSubKeyNames().ToList();
}
public void SetProperty(string propertyName, string value)
{
_thisKey.SetValue(propertyName, value, RegistryValueKind.String);
}
public void SetProperty(string propertyName, bool value)
{
SetProperty(propertyName, value.ToString());
}
public string GetProperty(string propertyName)
{
return GetProperty(propertyName, string.Empty);
}
public string GetProperty(string propertyName, string defaultValue)
{
return _thisKey.GetValue(propertyName, defaultValue).ToString();
}
public bool GetPropertyAsBool(string propertyName)
{
return bool.Parse(GetProperty(propertyName, default(bool).ToString()));
}
}
Then, you could wrap the fields of your form into a class that not only has properties that match your form fields, but also has methods to save the values to the registry and some static methods to load all Favorites or a specific named Favorite. For example:
public class Favorite
{
public string Name { get; private set; }
public string Notes { get; set; }
public bool NotesFromPlanner { get; set; }
public string Project { get; set; }
public string DbLocation { get; set; }
public string AssesmentToolVersion { get; set; }
public string ProjectCodes { get; set; }
public bool StraightToNew { get; set; }
public Favorite(string name)
{
this.Name = name;
}
public void Save()
{
var reg = new RegHelper(this.Name);
reg.SetProperty("Name", Name);
reg.SetProperty("Notes", Notes);
reg.SetProperty("NotesFromPlanner", NotesFromPlanner);
reg.SetProperty("Project", Project);
reg.SetProperty("DbLocation", DbLocation);
reg.SetProperty("AssesmentToolVersion", AssesmentToolVersion);
reg.SetProperty("ProjectCodes", ProjectCodes);
reg.SetProperty("StraightToNew", StraightToNew);
}
public static Favorite GetFavorite(string favoriteName)
{
var reg = new RegHelper(favoriteName);
return new Favorite(favoriteName)
{
Notes = reg.GetProperty("Notes"),
NotesFromPlanner = reg.GetPropertyAsBool("NotesFromPlanner"),
Project = reg.GetProperty("Project"),
DbLocation = reg.GetProperty("DbLocation"),
AssesmentToolVersion = reg.GetProperty("AssesmentToolVersion"),
ProjectCodes = reg.GetProperty("ProjectCodes"),
StraightToNew = reg.GetPropertyAsBool("StraightToNew"),
};
}
public static List<Favorite> GetFavorites()
{
return new RegHelper().GetSubKeys().Select(GetFavorite).ToList();
}
public override string ToString()
{
return this.Name;
}
}
Then, you could use the Favorite class to populate your Favorites drop down:
private void Form1_Load(object sender, EventArgs e)
{
// Get all saved favorites and load them up in the combo box
foreach (var favorite in Favorite.GetFavorites())
{
cboFavorites.Items.Add(favorite);
}
}
Now, when a favorite is picked from the combo box, we want to populate our form with the details:
private void cboFavorites_SelectedIndexChanged(object sender, EventArgs e)
{
var favorite = (Favorite) cboFavorites.SelectedItem;
txtNotes.Text = favorite.Notes;
txtAssetToolVersion.Text = favorite.AssesmentToolVersion;
txtDbLocation.Text = favorite.DbLocation;
chkNotesFromPlanner.Checked = favorite.NotesFromPlanner;
txtProjectCodes.Text = favorite.ProjectCodes;
cboProjects.Text = favorite.Project;
chkStraightToNew.Checked = favorite.StraightToNew;
}
And when someone clicks "Save Favorite", we want to add (or update) the favorite details to the registry:
private void btnAddFavorite_Click(object sender, EventArgs e)
{
string favoriteName = cboFavorites.Text;
if (string.IsNullOrEmpty(favoriteName))
{
MessageBox.Show("Please type a name for the favorite in the Favorites box.");
return;
}
var favorite = new Favorite(favoriteName)
{
Notes = txtNotes.Text,
AssesmentToolVersion = txtAssetToolVersion.Text,
DbLocation = txtDbLocation.Text,
NotesFromPlanner = chkNotesFromPlanner.Checked,
ProjectCodes = txtProjectCodes.Text,
Project = cboProjects.Text,
StraightToNew = chkStraightToNew.Checked
};
favorite.Save();
// When saving a favorite, add it to the combo box
// (remove the old one first if it already existed)
var existingFav = cboFavorites.Items.Cast<Favorite>()
.FirstOrDefault(fav => fav.Name == favoriteName);
if (existingFav != null)
{
cboFavorites.Items.Remove(existingFav);
}
cboFavorites.Items.Add(favorite);
cboFavorites.Text = favoriteName;
}
This should be enough to get you started, if you want to go the registry route.
It depends on your application and what it's used for and its architecture.
There are multiple options:
You could save it in a database.
This option is nice when there are a lot of settings and especially nice in a multi-user platform. If this is a client server application, this may also be preferable for that reason. If you want to keep this simple and don't see user settings getting complex / having very many, this may not be the best option.
You could save it in a flat file. This option is similar to the first, but likely better in the case where your application is more stand-alone and/or you just don't have any other benefit of having those settings on a server.
You could store them in your Applications Settings. There is a good answer regarding how to do that here: https://msdn.microsoft.com/en-us/library/0zszyc6e%28v=vs.110%29.aspx
Another thing to consider is how you want to load those settings. For windows forms development, having classes which define your layout and binding to those classes can be useful. Therefore, you may want to store this data in XML which can be easily serialized directly into a class which defines what your form looks like. You would be able to store that XML anywhere really: Locally or on the server in a database.

How can I run the same Coded UI test with multiple input files

I am looking for a way to be able to run the same coded ui test class with different input files, e.g. I have a test with end to end flow in the app, I want to be able to run this test with two different users doing different workflows once inside the app. I do not want to run both the tests each time (This is possible with having two rows in the data input csv). I wasn't able to find a way of doing this so far. Any help/guidance is appreciated.
I can think of three possibilities.
1.
You could arrange the CSV to have two groups of columns, eg
UserName1,Password1,DataAa1,DataBb1,UserName2,Password2,DataAa2,DataBb2
Within the test method change the data source accesses to use something like
string testCode = (...) ? "1" : "2";
... = TestContext.DataRow["UserName" + testCode].ToString();
... = TestContext.DataRow["Password" + testCode].ToString();
This requires something else to specify which data file to use. That could be done via an environment variable.
2.
Have three CSV files within the solution. Two of them are the CSV files for the two runs. For example SourceData1.csv and SourceData2.csv. The third file is SourceData.csv and is named in the [DataSource(...) attribute as "|DataDirectory|\\SourceData.csv". In the ".testsettings" file give the name of a batch file that chooses the wanted SourceData1.csv or SourceData2.csv file and uses xcopy to copy that file and overwrite SourceData.csv.
3.
Assuming that the test is currently written as
[TestMethod, DataSource(...)]
public void MyCodedUiTestMethod() {
...body of the test
}
Then change to having two test methods that call a third method. These two methods specify different CSV files and the called method accesses values from the whichever file is being read.
[TestMethod, DataSource(... SourceData1.csv ...)]
public void MyFirstCodedUiTestMethod() {
BodyOfTheTest();
}
[TestMethod, DataSource(... SourceData2.csv ...)]
public void MySecondCodedUiTestMethod() {
BodyOfTheTest();
}
public void BodyOfTheTest() {
...body of the test
... = TestContext.DataRow["UserName"].ToString();
... = TestContext.DataRow["Password"].ToString();
}
Note that TextContext is visible in all methods of the class hence the TestContext.DataRow... expressions can be written outside the methods that specify the [DataSource...] attribute.
If it is a same test case then you should have same set of input parameters, create a class with your test arguments then serialize the list of class instance to XML file with different argument set. When running the test case you can deserialize the XML file (inside TestInitialize() block) and iterate though each class instance and pass the instance coded UI test method. you can call the test method as many time you want according to the count of class instances you have in the xml file. This is the method which I am using for the data driven testing on coded UI test.
Create a class with the test arguments
public class ClientDetails
{
public String ClientType { get; set; }
public String clientCode { get; set; }
public String Username { get; set; }
public String Password { get; set; }
}
Create some class instances and first serialize to a XML file
// location to store the XML file following relative path will store outside solution folder with
// TestResults folder
string xmlFileRelativePath = "../../../TestClientInfo.xml";
public List<ClientDetails> ListClientConfig = new List<ClientDetails>();
ClientDetails Client1 = new Classes.ClientDetails();
Client1.ClientType = "Standard";
Client1.clientCode = "xxx";
Client1.Username = "username";
Client1.Password = "password";
ClientDetails Client2 = new Classes.ClientDetails();
Client2.ClientType = "Easy";
Client2.clientCode = "xxxx";
Client2.Username = "username";
Client2.Password = "password";
ListClientConfig.Add(Client1);
ListClientConfig.Add(Client2);
XmlSerialization.genericSerializeToXML(ListClientConfig, xmlFileRelativePath );
Retrieve the stored XML objects within the test method or any where you prefer (better if inside inside TestInitialize() block)
[TestMethod]
public void CommonClientExecution()
{
List<ClientDetails> ListClientConfig = XmlSerialization.genericDeserializeFromXML(new ClientDetails(), xmlFileRelativePath );
foreach (var ClientDetails in ListClientConfig )
{
// you test logic here...
}
}
XML Serialization methods for serialize collection of objects
using System.Xml;
using System.Xml.Serialization;
class XmlSerialization
{
public static void genericSerializeToXML<T>(T TValue, string XmalfileStorageRelativePath)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
FileStream textWriter = new FileStream((string)System.IO.Path.GetFullPath(XmalfileStorageRelativePath), FileMode.OpenOrCreate, FileAccess.ReadWrite);
serializer.Serialize(textWriter, TValue);
textWriter.Close();
}
public static T genericDeserializeFromXML<T>(T value, string XmalfileStorageFullPath)
{
T Tvalue = default(T);
try
{
XmlSerializer deserializer = new XmlSerializer(typeof(T));
TextReader textReader = new StreamReader(XmalfileStorageFullPath);
Tvalue = (T)deserializer.Deserialize(textReader);
textReader.Close();
}
catch (Exception ex)
{
// MessageBox.Show(#"File Not Found", "Not Found", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
return Tvalue;
}
}

How to make application settings independent of .exe path?

Suppose I want to move my app from c:\myapp.exe to d:\myapp.exe. It will loose all settings. How to prevent that?
Personally I use the registry to save and load my settings, so the location of my application isn't affected, but if you're using User.Config etc and want to fix the location, this might help:
Can I control the location of .NET user settings to avoid losing settings on application upgrade?
It is an implementation detail of the LocalFileSettingsProvider class. Which has the unenviable job of storing user-scoped settings in a file that's guaranteed to be unique so that different apps cannot accidentally overwrite each others settings.
It does so by storing the file in an AppData directory that has a hashed name. The hash is computed from several properties of the application. It grabs as much as it can, starting with the attributes in the AssemblyInfo.cs file. Particularly the [AssemblyVersion] matters which is how it can detect that a new version of your app might not be compatible with the old version of the user.config file.
But the attributes are not enough to make it unique, it also uses the full path name of the .exe in the hash. Which is a very strong selector for the appropriate .config file.
So, inevitably, if you move the .exe somewhere else, that's going to change the hash and that's going to get you an empty user.config file with all the settings back to their default setting.
It's a bit questionable to tinker with this, an app should only ever have one install directory. c:\program files\companyname\appname is the standard. But you can by implementing your own SettingsProvider class. That's not exactly easy to do, System.Configuration is a pretty nasty namespace. But a decent starting point is the RegistrySettingsProvider sample, which is probably usable as-is.
-
This depends 100% on the application.
An application by itself just needs to find it's dependencies, or the list of DLLs it requires to run. It'll look in the current directory for these most of the time, so this usually isn't an issue.
The largest issue is in the registry. If the application has written where it was installed to the registry, it may look for certain files in the old directory at runtime.
If you installed the application, this is also stored in the registry, and uninstalling from Add/Remove programs will no longer work.
If the application does not use the registry though, it can be moved without consequence. Many portable apps which run off flash drives take this approach, and as a result can be moved or deleted as needed...
Hope It Helps Your Cause..:)
You can create your own Settings class. Which works just like the original. Below I'm posting my implementation of Settings class. Any improvements will be greatly appreciated.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using Microsoft.Win32;
namespace MyNamespace
{
static class Settings
{
private static string _connectionString = #"data source=C:\\database.s3db";
public static string ConnectionString
{
get { return GetSetting("_connectionString"); }
set { _connectionString = value; }
}
private static string _archives = "";
public static string Archives
{
get { return GetSetting("_archives"); }
set { _archives = value; }
}
public static bool CheckDuplicates
{
get { return bool.Parse(GetSetting("_checkDuplicates")); }
set { _checkDuplicates = value; }
}
private static bool _saveDocks = true;
public static bool SaveDocks
{
get { return bool.Parse(GetSetting("_saveDocks")); }
set { _saveDocks = value; }
}
private static Font _font = new Font("Tahoma", 12, GraphicsUnit.Pixel);
public static Font Font
{
get
{
var convert = new FontConverter();
var value = convert.ConvertFromString(GetSetting("_font"));
return (Font) value;
}
set { _font = value; }
}
public static void Save()
{
Type type = typeof(Settings);
var registryKey = Registry.CurrentUser.CreateSubKey(string.Format(#"Software\{0}\{1}\Settings", Application.CompanyName, Application.ProductName));
if (registryKey != null)
{
foreach (var field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Static))
{
var converter = TypeDescriptor.GetConverter(field.FieldType);
var value = converter.ConvertToString(field.GetValue(null));
registryKey.SetValue(field.Name, value ?? field.GetValue(null));
}
registryKey.Close();
}
}
public static void SetDefaults()
{
var registryKey = Registry.CurrentUser.OpenSubKey(string.Format(#"Software\{0}\{1}\Settings", Application.CompanyName, Application.ProductName));
if (registryKey == null)
{
Save();
}
else
{
registryKey = Registry.CurrentUser.CreateSubKey(string.Format(#"Software\{0}\{1}\Settings", Application.CompanyName, Application.ProductName));
if(registryKey == null) return;
Type type = typeof(Settings);
foreach (var field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Static))
{
if (registryKey.GetValue(field.Name) != null)
{
var converter = TypeDescriptor.GetConverter(field.FieldType);
var value = converter.ConvertFrom(registryKey.GetValue(field.Name, field.GetValue(null)));
field.SetValue(null, value);
}
}
registryKey.Close();
}
}
private static string GetSetting(string name)
{
var registryKey = Registry.CurrentUser.OpenSubKey(string.Format(#"Software\{0}\{1}\Settings", Application.CompanyName, Application.ProductName));
if (registryKey != null)
{
if (registryKey.GetValue(name) != null)
{
return registryKey.GetValue(name).ToString();
}
registryKey.Close();
}
return "";
}
}
}
To use this with your application just add properties and backing field for your settings just like above. Make sure you use backing field's name as string argument in GetSetting method in property's get accessor. Make sure you assign default values to settings fields.
For saving settings see below code.
Settings.Archives = ".7z,.rar,.zip";
Settings.CheckDuplicates = true;
Settings.SaveDocks = false;
Settings.Font = fontDialog.Font;
Settings.Save();
You must call SetDefaults method in your constructor of a main form. See below code.
namespace MyNamespace
{
public partial class FormMain : Form
{
public FormMain()
{
InitializeComponent();
Settings.SetDefaults();
}
}
}
If you have suggestion for improving this class. Then comment.
Make a separate dll to read the settings via whatever method you prefer - registry or xml reading, ... and put it in system path. Then you can call dll anywhere your exe is.
But under what circumstance do you need this requirement? I just wonder.

How to Read a Configuration Section from XML in a Database?

I have a Config class like this:
public class MyConfig : ConfigurationSection
{
[ConfigurationProperty("MyProperty", IsRequired = true)]
public string MyProperty
{
get { return (string)this["MyProperty"]; }
set { this["MyProperty"] = value; }
}
}
And it is being instantiated by another class like this
(MyConfig)ConfigurationManager.GetSection("myConfig")
We are making some changes and are now storing the configuration file in the DB as an xml, exactly like it is currently in the config file.
I would like to maintain the MyConfig as a ConfigurationSection for backwards compatibility but still be able to instantiate it by using the XML string retrieved from the DB.
Is it possible? If so, how? (Keep in mind it should still work as instantiated above)
Here is how I usually do it: just add these members to the MyConfig class:
public class MyConfig : ConfigurationSection
{
private static MyConfig _current;
public static MyConfig Current
{
get
{
if (_current == null)
{
switch(ConfigurationStorageType) // where do you want read config from?
{
case ConfigFile: // from .config file
_current = ConfigurationManager.GetSection("MySectionName") as MyConfig;
break;
case ConfigDb: // from database
default:
using (Stream stream = GetMyStreamFromDb())
{
using (XmlTextReader reader = new XmlTextReader(stream))
{
_current = Get(reader);
}
}
break;
}
}
return _current;
}
}
public static MyConfig Get(XmlReader reader)
{
if (reader == null)
throw new ArgumentNullException("reader");
MyConfig section = new MyConfig();
section.DeserializeSection(reader);
return section;
}
}
This way, you have nothing to change in the MyConfig class, but you still need to change the way your customers access it with this kind of code:
string myProp = MyConfig.Current.MyProperty;
If you need to get any System.Configuration.ConfigurationSection stored in database, you may consider writing generic section reader like this:
public class ConfigurationSectionReader where T : ConfigurationSection, new()
{
public T GetSection( string sectionXml )
{
T section = new T();
using ( StringReader stringReader = new StringReader( sectionXml ) )
using ( XmlReader reader = XmlReader.Create( stringReader, new XmlReaderSettings() { CloseInput = true } ) )
{
reader.Read();
section.GetType().GetMethod( "DeserializeElement", BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( section, new object[] { reader, true } );
}
return section;
}
}
This will work for all classes that override DeserializeElement method. e.g.
protected override void DeserializeElement( XmlReader reader, bool serializeCollectionKey )
{
XmlDocument document = new XmlDocument();
document.LoadXml( reader.ReadOuterXml() );
MyProperty = document.DocumentElement.HasAttribute( "MyProperty" )
? document.DocumentElement.Attributes[ "MyProperty" ].Value
: string.Empty;
}
Than you could get a section like this:
var reader = new ConfigurationSectionReader();
var section = reader.GetSection( sectionXml ); // where sectionXml is the XML string retrieved from the DB
My suggestion would be to keep your current MyConfig class but load your XML from your database in the constructor, then in each property of your MyConfig, you can put in logic to determine where you get the value from (either database or .config file) if you need to pull config from either location, or have it fall back if the value is empty.
public class MyConfig : ConfigurationSection
{
public MyConfig()
{
// throw some code in here to retrieve your XML from your database
// deserialize your XML and store it
_myProperty = "<deserialized value from db>";
}
private string _myProperty = string.Empty;
[ConfigurationProperty("MyProperty", IsRequired = true)]
public string MyProperty
{
get
{
if (_myProperty != null && _myProperty.Length > 0)
return _myProperty;
else
return (string)this["MyProperty"];
}
set { this["MyProperty"] = value; }
}
}
This is a hard problem. You could have a config file with some deliberately incorrect XML, override OnDeserializeUnrecognizedElement in your ConfigurationSection and then effectively bypass the file to ConfigurationSection mapping (essentially set your properties manually) - some refactoring would be needed, but you can still expose the same properties etc. It is a bit WTF, but possibly workable.
I essentially describe how to do this with LINQ to XML in this blog post. In all of my code now I don't have classes that rely on ConfigurationSection, I use the technique described in my blog post to bypass that and return POCOs through an interface. This has made my code more unit testable, as I can easily use a stub for the interface.
I can also easily move my configuration into a DB should I wish to do so- I just create a new class that implements my configuration interface and switch it in my IoC configuration. Microsoft didn't design the configuration system to be flexible, so you have to take that into consideration when using it in your own code.
The only other way I can think of is to write the DB config out to a file and then read it in, but that is also weird!
Rather old question, but just playing around with a solution to this. It's similar to Simon Mourier's approach (which I like a better in some ways - less hacky) but does mean any code that calls System.Configuration.ConfigurationManager.GetSection() will continue to work without having to change them to use the static method, so might result in less code change overall.
The first basic caveat is that I have no idea if this works with nested sections, but I'm almost certain it won't. The nearly-main caveat is that it requires changes to the config section class, so you can only use it with custom sections that you have the source to (and are allowed to change!)
The second and MAIN CAVEAT is that I'm just playing around with this, I'm not using it in either development and definitely not production, and simply smearing my own code over the base functionality like this may well have knock-on effects that do not show up in my example. Use at your own risk.
(Having said that, I'm testing it in an Umbraco site so with loads of other config sections going on, and they still all work, so I think it has no immediately awful effects)
EDIT: This is .NET 4, not 3.5 as per the original question. No idea if that wil make a difference.
So, here's the code, pretty simple, just override DeserializeSection to use an XML reader that loads from a database.
public class TestSettings : ConfigurationSection
{
protected override void DeserializeSection(System.Xml.XmlReader reader)
{
using (DbConnection conn = /* Get an open database connection from whatever provider you are using */)
{
DbCommand cmd = conn.CreateCommand();
cmd.CommandText = "select ConfigFileContent from Configuration where ConfigFileName = #ConfigFileName";
DbParameter p = cmd.CreateParameter();
p.ParameterName = "#ConfigFileName";
p.Value = "TestSettings.config";
cmd.Parameters.Add(p);
String xml = (String)cmd.ExecuteScalar();
using(System.IO.StringReader sr = new System.IO.StringReader(xml))
using (System.Xml.XmlReader xr = System.Xml.XmlReader.Create(sr))
{
base.DeserializeSection(xr);
}
}
}
// Below is all your normal existing section code
[ConfigurationProperty("General")]
public GeneralElement General { get { return (GeneralElement)base["General"]; } }
[ConfigurationProperty("UI")]
public UIElement UI { get { return (UIElement)base["UI"]; } }
...
...
}
I'm using ASP.Net, so to make it work, you do need a web.config, but then hey, I need somewhere for connection strings anyway or I'm not going to connect to a database at all.
Your custom section should be defined as normal in <configSections/>; the key to making this work is to then put an empty element in place of your normal settings; i.e. in place of <TestSettings configSource="..."/> or you inline settings, simply put <TestSettings/>
The configuration manager will then load all sections, see the existing <TestSettings/> element, and deserialize it, at which point it hits your override and loads the XML from the database instead.
NOTE: The deserialize expects a document fragment (it expects to be called when the reader is already located at a node), not a whole document, so if your sections are stored in separate files, you must remove the <?xml ?> declaration first, or you get Expected to find an element.

Categories

Resources