How to make application settings independent of .exe path? - c#

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.

Related

C# Dynamically make settings within application then save persistently

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.

Best way for store a web application settings and options [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
i'm working on a ASP.Net MVC 5 web application which it's a blog and has a administrator area. The admin area has some functionality something like which user can comment on the post and etc... but maybe admin changes his mind and he wants to just allow registered users comment. I mean the options be dynamic.
The question is where to store data of these kind of options are the best place?
I'm avoiding from create a table for these data because of frequent queries!
The second idea i have is using database and cache together but i'm looking for the best way to implement that...
Is there a better idea?
Store it in the database. That part is easy. If you're concerned about querying the database too much, then cache the results of your query for some time period. At its most simplest, that would look something like:
ObjectCache cache = MemoryCache.Default;
var key = "MyAwesomeSettings";
var settings = cache.Get(key);
if (settings == null)
{
settings = // query settings;
cache.Add(key, settings, DateTimeOffset.UtcNow.AddHours(1));
}
However, one key issue with MemoryCache is that it's process-bound. If you're using web workers, you're going to have a unique cache per worker, as each worker is a separate process. You can use a NoSQL solution like Redis, though, to create a distributed cache that can be shared between workers and is more resilient to boot.
Use database tables to store the data, if the amount is not too big, when user logs in, retrieve the data once and store it in user session object for quick access.
Keep your primary store as your database, but serve requests from the cache. Your DB is always going to be actual store, for cases where the cache recycles(could be in memory or even external/distributed cache)
Cache the frequently changed settings on app startup or on first access of admin actions.
Serve all subsequent operations to/from the cache. Any changes to the settings, update to and from the cache.
Run a job/service to sync the cache and the database, every hour or so.
Pros:
Access is fast, since it is coming from the cache.
Updates are quick, since it is to the cache.
Very less and predictable database access.
Cons:
The database is going to be behind by a max of an hour or so.
In case of a cache recycle, you'll have an hour old stale data, but never a complete loss of it.
I do this same thing in a web application I maintain. Unless you have a whole lot (like thousands or millions) of different settings or an extremely slow database, saving the settings in the database and retrieving the specific settings you need each time you need them should be fine. If you are concerned about your database performance, you could check them once at user log in and save them as session(if they are user-specific) or application variables. However, the user would not see changes immediately, only after next log in.
Personally, I would go with the pure database option first and test it out. If it doesn't perform well enough, it will be trivial to switch to the session/application variable option.
i using this approach.. it using a json file. json file is very flexible for any type of value and it can be refresh when file change without application restart..
defining a config section..
namespace ConfigTest.Configuration
{
public class MyConfig : ConfigurationSection
{
private static JObject _props;
private static FileSystemWatcher watcher;
[ConfigurationProperty("path", IsRequired = true)]
public string Path
{
get { return (string)this["path"]; }
set { this["path"] = value; }
}
[ConfigurationProperty("watch", IsRequired = false)]
public bool Watch
{
get { return (bool)this["watch"]; }
set { this["watch"] = value; }
}
private static JObject GetValues(string path)
{
string data = File.ReadAllText(path);
JObject j = JObject.Parse(data);
return j;
}
public static T Read<T>(string path)
{
var token = _props.SelectToken(path);
if (token == null)
return default(T);
return token.ToObject<T>();
}
public static void Load()
{
MyConfig section = (MyConfig)ConfigurationManager.GetSection("myConfig");
string path = section.Path;
LoadValues(path);
if (section.Watch)
FileSystemWatcher(path);
}
private static void LoadValues(string path)
{
JObject props = GetValues(path);
_props = props;
}
private static void FileSystemWatcher(string path)
{
if (watcher != null)
watcher.Dispose();
watcher = new System.IO.FileSystemWatcher();
watcher.Path = System.IO.Path.GetDirectoryName(path);
watcher.Filter = System.IO.Path.GetFileName(path);
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
private static void OnChanged(object sender, FileSystemEventArgs e)
{
FileSystemWatcher watcher = (FileSystemWatcher)sender;
watcher.EnableRaisingEvents = false;
LoadValues(e.FullPath);
watcher.EnableRaisingEvents = true;
}
}
}
section definition
<configSections>
<!--important!! its should be your config type name space-->
<section name="myConfig" type="ConfigTest.Configuration.MyConfig, ConfigTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
configuration for setting file path and watch property
<!--important! locate it in App_Data or Outside the project directory-->
<myConfig watch="true" path="yourpath/myConfig.json" />
config file myConfig.json
{
"someIntProperty": 1,
"someStringProperty": "string",
"someObjectProperty": {
"id": 1,
"name": "parentObjectName",
"someChildeObject": {
"childObjectId": 1,
"childObjectName": "child object name "
}
}
}
how to use it..
u can get value with type
int intvalue = MyConfig.Read<int>("someIntProperty");
u can get defined type object
SomeClass objectValue = MyConfig.Read<SomeClass>("someObjectProperty");
public class SomeClass
{
public int Id { get; set; }
public string Name { get; set; }
public SomeClass2 SomeChildeObject { get; set; }
}
public class SomeClass2
{
public int ChildObjectId { get; set; }
public string ChildObjectName { get; set; }
}
or u can direct get any child with property path
string value = MyConfig.Read<string>("someObjectProperty.someChildeObject.childObjectName");
i hope this helpfull

C# App.config on a per-user basis

I'm writing an application that has some application level configurations, but also has some configurations that should be done on a per-user basis.
I'm writing a config section for BrowserStack to use the App.config file (per msdn) which has let me define the important things that need to be stored, however, there are things that belong in a config file that don't belong in version control. In this particular instance browserstack.user and browserstack.key (which are retrieved from their documentation) belong in a separate config file and they shouldn't be checked in.
Does c# configuration allow for this kind of behavior, or would I have to modify my config section to do this? (Included below for reference)
public class BrowserStackSettings : ConfigurationSection
{
[ConfigurationProperty("hubUrl", DefaultValue = "http://hub-cloud.browserstack.com/wd/hub/", IsRequired = false)]
public string HubUrl
{
get
{
return (string)this["hubUrl"];
}
set
{
this["hubUrl"] = value;
}
}
[ConfigurationProperty("user", IsRequired = true)]
public string User
{
get
{
return (string)this["user"];
}
set
{
this["user"] = value;
}
}
[ConfigurationProperty("key", IsRequired = true)]
public string Key
{
get
{
return (string)this["key"];
}
set
{
this["key"] = value;
}
}
}
C# has something called settings. The scope of one setting can be "user" or "application". Applications settings cannot be changed easily. They are stored in app's .config file in Program Files. But user settings can be changed, and of course can be different for each user. Those settings are stored in another .config file somewhere in user's profile.
So, app should somehow allow users to change those settings, and to save them just call Properties.Settings.Default.Save();

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 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