C#'s application configuration "app.config" file... how to store collections? - c#

I am in the process of writing a service using C# and I need to store a list of strings within the app.config file. I have 161 of these.
How can I store this information in the app.config file? There must be a way, because these are strongly typed values and thus I'm supposed to easily use any valid .NET type in the code to access them!
I want to avoid having one value that uses a comma-separated list for obvious performance issues.

I use Microsoft’s Visual Studio 2010.
In Solution Explorer, expand the Properties node of your project.
In Solution Explorer, double-click the .settings file in which you want to add a new setting. The default name for this file is Settings.settings.
In Settings Designer, set the Name, Type, Scope, and Value for your setting. Each row represents a single setting.
The Type that you need is System.Collections.Specialized.StringCollection. This can be located after clicking Browse at the end of the DropDownList that appears when you click to set the Type.
Click on the button that appears towards the end of the Value TextBox.
Type in your strings, one-by-one, in the dialog that appears.

There is a good article about storing lists (or any custom object) in your app.config files in Best Way Of Saving Lists in App.Config
Essentially, you create an object that represents the data.
public class MyConfig
{
public string[] myList;
public string someOtherValueIfYouWant;
}
And write a config handler for it...
public class ConfigSectionHandler : IConfigurationSectionHandler
{
public const string SECTION_NAME = "MyConfig";
public object Create(object parent, object configContext, XmlNode section)
{
string szConfig = section.SelectSingleNode("//MyConfig").OuterXml;
MyConfig retConf = null;
if (szConfig != string.Empty || szConfig != null)
{
XmlSerializer xsw = new XmlSerializer(typeof(MyConfig));
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(szConfig));
ms.Position = 0;
retConf = (MyConfig)xsw.DeSerialize(ms);
}
return retConf;
}
}
And this allows you to put the following XML in your app.config file...
Tell app.config about your cool config section
<configSections>
<section name="MyConfig" type="ConfigSectionHandler,someAssembly" />
</configSection>
And then add your config section...
<MyConfig>
<myList>First one</myList>
<myList>Second one</myList>
<myList>Keep going</myList>
<myList>And so on</myList>
<someOtherValueIfYouWant>some non array config</someOtherValueIfYouWant>
</MyConfig>

https://stackoverflow.com/a/30178144/878539
StringCollection is NOT recommended in VS2015RC for the purpose of annoyance'ness when loading the properties settings dialog window for the project and re-saving the data. If you have a dozen entries for serializeAs="Xml" and everytime you open the properties window it will popup with a dialog for EACH entry telling you to confirm overwrite. Yes/No does absolutely nothing.
The data mismatch for each file will set this off and is a known issue. See my other post related.
Use a workaround such as Custom Config storage or find a way to leave your collections out of the designer or app.config. One or the other.
Viable solution only for those willing to put up with the nag dialog windows or rarely edit the settings property information.

Related

How to output a file from a roslyn code analyzer?

I'm using the roslyn API to write a DiagnosticAnalyzer and CodeFix.
After I have collected all strings and string-interpolations, I want to write all of them to a file but I am not sure how to do this the best way.
Of course I can always simply do a File.WriteAllText(...) but I'd like to expose more control to the user.
I'm also not sure about how to best trigger the generation of this file, so my questions are:
I do not want to hard-code the filename, what would be the best way to expose this setting to the user of the code-analyzer? A config file? If so, how would I access that? ie: How do I know the directory?
If one string is missing from the file, I'd like to to suggest a code fix like "Project contains changed or new strings, regenerate string file". Is this the best way to do this? Or is it possible to add a button or something to visual studio?
I'm calling the devenv.com executable from the commandline to trigger builds, is there a way to force my code-fix to run either while building, or before/after? Or would I have to "manually" load the solution with roslyn and execute my codefix?
I've just completed a project on this. There are a few things that you will need to do / know.
You will probably need to switch you're portable class library to a class library. otherwise you will have trouble calling the File.WriteAllText()
You can see how to Convert a portable class library to a regular here
This will potentially not appropriately work for when trying to apply all changes to document/project/solution. When Calling from a document/project/solution, the changes are precalcuated and applied in a preview window. If you cancel, an undo action is triggered to undo all changes, if you write to a file during this time, and do not register an undo action you will not undo the changes to the file.
I've opened a bug with roslyn but you can handle instances by override the preview you can see how to do so here
And one more final thing you may need to know is how to access the Solution from the analyzer which, Currently there is a hack I've written to do so here
As Tamas said you can use additional files you can see how to do so here
You can use additional files, but I know on the version I'm using resource files, are not marked as additional files by default they are embeddedResources.
So, for my users to not have to manually mark the resource as additonalFiles I wrote a function to get out the Designer.cs files associated with resource files from the csproj file using xDoc you can use it as an example if you choose to parse the csproj file:
protected List<string> GetEmbeddedResourceResxDocumentPaths(Project project)
{
XDocument xmldoc = XDocument.Load(project.FilePath);
XNamespace msbuild = "http://schemas.microsoft.com/developer/msbuild/2003";
var resxFiles = new List<string>();
foreach (var resource in xmldoc.Descendants(msbuild + "EmbeddedResource"))
{
string includePath = resource.Attribute("Include").Value;
var includeExtension = Path.GetExtension(includePath);
if (0 == string.Compare(includeExtension, RESX_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase))
{
var outputTag = resource.Elements(msbuild + LAST_GENERATED_TAG).FirstOrDefault();
if (null != outputTag)
{
resxFiles.Add(outputTag.Value);
}
}
}
return resxFiles;
}
For config files you can use the AdditionalFiles msbuild property, which is passed to the analyzers through the context. See here.

Is it possible to initialize a ConfigurationSection from an external file?

I have a custom configuration property in my app. It looks something like this:
public class OverrideConfiguration : ConfigurationSection
{
[ConfigurationProperty(PROP_ADMIN_CONNSTR)]
public StringConfigurationElement AdminConnectionString
{
get { return base[PROP_ADMIN_CONNSTR] as StringConfigurationElement; }
set { base[PROP_ADMIN_CONNSTR] = value; }
}
// .. Various other properties, but you get the idea
}
However, what I'd like is to allow the .config file to be pointed to an external file source. Something like this:
<ServiceOverrides file="Overrides.local.config" />
Now, the built-in configSource attribute is close to what I need, but it has two major issues.
Files must exist. If the file doesn't exist, it errors out.
Files must be in the current directory or in a deeper directory. In other words, I can't point to ..\Overrides.local.config
What I want is pretty much identical to the <appSettings file="..." /> configuration element. However, that attribute seems to be something appSettings implemented, and is not part of the base ConfigurationSection class.
My Question:
Is it possible to override something in ConfigurationSection that will basically read XML data from a different location? I don't want to change any other aspect of my class or do my own XML deserialization or anything. I simply want to check if a file exists, if so, load in the XML contents from that file, otherwise load in the default XML contents.
Ok, I have a working solution. I'm not sure if it's the best approach, but it does appear to work exactly how I want.
private readonly Queue<String> externalSources = new Queue<String>();
protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
{
var externalFile = reader.GetAttribute("File");
if(!String.IsNullOrWhiteSpace(externalFile))
{
externalSources.Enqueue(externalFile);
}
base.DeserializeElement(reader, serializeCollectionKey);
}
protected override void PostDeserialize()
{
base.PostDeserialize();
// Override data with local stuff
if (externalSources.Count == 0) return;
string file = externalSources.Dequeue();
if (System.IO.File.Exists(file))
{
var reader = XmlReader.Create(file);
base.DeserializeSection(reader);
}
}
First, I trap the DeserializeElement event, which happens when we read the <ServiceOverrides> element. We check if it has a File attribute, and if so we add it to a queue of external sources to load.
Next, we trap the PostDeserialize event, which gets called after all the local XML is parsed. If there's an external source in the queue, we dequeue it, check if it actually exists, then create an XmlReader with the contents of that file. Now we can simply call DeserializeSection again and pass in our new reader. The ConfigurationSection class is smart enough to just overwrite or append any new data to the existing configuration. What I get at the end is an aggregation of both configuration files, where the include file wins in the event of a duplicate.
Now, what's this nonsense with the queue? Well, it seems every time you call DeserializeSection, it'll call PostDeserialize again. So, if we simply trapped PostDeserialize, check the File attribute, and call DeserializeSection again, we'd get in an infinite loop. We could just use a flag to remember if we already loaded the external file, but a queue has the added benefit of allowing the include file to load more include files (not that I'd ever want to do that, but you might).
Tips: This will probably work fairly well, and is simple to understand, but if you're using it in production code, there's a few things you could improve on. First, externalSources doesn't really need to be a queue, since these calls aren't actually recursive. You can probably just use a string, and set it to null after you're done processing that file. Second, this could cause an infinite loop in the event of a circular include chain. You could create a List<T> of previously included files, then check if the include already exists in that list before adding it to the queue.
Hope this helps someone!

Adding a user Settings property at runtime (Winforms)

I am using C# ,win forms and .net 2.0
I am trying to add a property inside user settings file at run time but i am not able to view that added property in settings.settings file in certain location i.e. file exists but property is not added
I am not getting error when i call this property it works
Using this below code
MessageBox.Show(***********.Properties.Settings.Default.Properties["NewProperty"].DefaultValue);
I have written this following code
Calling the function
clCommonFuncation cl = new clCommonFuncation();
if (***********.Properties.Settings.Default.Properties["NewProperty"] == null)
{
cl.addPropertyinSettingsFile("NewProperty",
***********.Properties.Settings.Default.Providers,
***********.Properties.Settings.Default.Providers["LocalFileSettingsProvider"],
***********.Properties.Settings.Default.Properties,
typeof(string),"ASD",null);
***********.Properties.Settings.Default.Save();
***********.Properties.Settings.Default.Reload();
}
And this is calling funaction
public void addPropertyinSettingsFile(string settingName,
SettingsProviderCollection settingsProviderCollection,
SettingsProvider settingsProvider,
SettingsPropertyCollection settingPrpertyCollection,
Type dataType,
object defaultValue,
object settingDefault)
{
SettingsProperty lvSettingProperty = new SettingsProperty(settingName);
lvSettingProperty.DefaultValue = defaultValue;
lvSettingProperty.IsReadOnly = false;
lvSettingProperty.PropertyType = dataType;
lvSettingProperty.Provider = settingsProvider;
lvSettingProperty.SerializeAs = SettingsSerializeAs.String;
lvSettingProperty.Name = settingName;
lvSettingProperty.Attributes.Add(typeof(System.Configuration.UserScopedSettingAttribute),
new System.Configuration.UserScopedSettingAttribute());
settingPrpertyCollection.Add(lvSettingProperty);
}
What is it i am doing wrong?
Any suggestion will be appreciated
Thank you
I think you'd better write a custom struct or class with your application settings and use serialization for loading and saving it - that is much more clear and relevant in your case.
You can not add or remove properties in .Net Setting files at runtime. There are some tricks round the web but none of them is applicable and a solution to what you want.
Settings files have not been designed for such a purpose. These files have been designed to be populated at design time and only be "read" or "modified" during runtime.
The reason is that when you create and edit settings file in designer (by double clicking on a settings file in solution explorer or choosing settings tab at project properties menu item) Visual studio creates an ApplicationSettings class (Derived from ApplicationSettingsBase class) which has data members for any setting field you created plus some additional attributes (like [ApplicationScopedSetting] or [UserScopedSetings] ). At run time the .Net runtime interacts with seetings files using this class. Therefore when you try to add properties at run time, you have no backing fileds and attributes in the ApplicationSettings class and CLR does not know what to do with them.
Conclusion: The Settings file have specific usage and are not suitable for any arbitrary configuration persistence in you application. Try using XML files which fully support what you want from reading, writing, adding, removing, updating basic types properties (string, char, int and so on) to supporting complex objects using XML.Seriliazation.

c# forms - Programatically add missing entrys in .config file

a question I haven't found an answer to after googling for a long time (then a long break from it and searching again)...
Say, I've got 2 Settings in my application settings. String1 and String2. Say, further, we shipped the product and start adding minor features (more things to configure) and we add a String3.
How, without traversing the .config file manually, can I add missing entries? When shipped as an update (without OneClick btw.) the existing .config file only has String1 and String2.
While defaulting for String3, the application somehow understands that an entry is missing, so it ought to be possible, or so I think, to add this one setting with the default value, so that either another program or a user, doesn't have to type the whole tags manually, without knowing what name it really is.
Thanks in advance!
Qudeid
Hi folks again,
I've just whipped up the following piece of code that works for me as I wanted.
Just to explain it a little:
I first open the config file using the ConfigurationManager, get the corresponding section and set the ForceSave to true, so that section is sure to save.
Then, the "magic" starts. I iterate through all properties of the assembly's settings and let linq do its magic to find if the element exists. If not, I create it and append it to the file.
Note: This piece of code is only for application settings, not so for user settings as this is a different section. I haven't tried/tested it, but it could be as simple as changing this line:
ConfigurationSectionGroup sectionGroup = configFile.SectionGroups["applicationSettings"];
to this line:
ConfigurationSectionGroup sectionGroup = configFile.SectionGroups["userSettings"];
as this is the corresponding name of that section. No guarantees, though.
Here's my code:
/// <summary>
/// Loads own config file and compares its content to the settings, and adds missing entries with
/// their default value to the file and saves it.
/// </summary>
private void UpdateSettings()
{
// Load .config file
Configuration configFile = ConfigurationManager.OpenExeConfiguration(typeof(Settings).Assembly.Location);
// Get the wanted section
ConfigurationSectionGroup sectionGroup = configFile.SectionGroups["applicationSettings"];
ClientSettingsSection clientSettings = (ClientSettingsSection)sectionGroup.Sections[0];
// Make sure the section really is saved later on
clientSettings.SectionInformation.ForceSave = true;
// Iterate through all properties
foreach (SettingsProperty property in Settings.Default.Properties)
{
// if any element in Settings equals the property's name we know that it exists in the file
bool exists = clientSettings.Settings.Cast<SettingElement>().Any(element => element.Name == property.Name);
// Create the SettingElement with the default value if the element happens to be not there.
if (!exists)
{
var element = new SettingElement(property.Name, property.SerializeAs);
var xElement = new XElement(XName.Get("value"));
XmlDocument doc = new XmlDocument();
XmlElement valueXml = doc.ReadNode(xElement.CreateReader()) as XmlElement;
valueXml.InnerText = property.DefaultValue.ToString();
element.Value.ValueXml = valueXml;
clientSettings.Settings.Add(element);
}
}
// Save config
configFile.Save();
}
When you create a setting in Visual Studio (Project -> Properties -> Settings.settings) you assign a value to that setting in the settings editor. From the settings definition (really an XML file) a code file is generated with a class that gives you access to the settings. This class will as a default use the value assigned to the setting in the settings editor. However, when the setting is accessed it will look for a value of that setting in the App.config file. If there is a value it will override the default value in the code generated file.
What this means is that if you add a setting to your project but doesn't provide a value for that setting in the App.config file the value of the setting will be the default value assigned in the settings editor.
To override the value assign it in the App.config file for the application.
Because your application can be split into multiple assemblies created by multiple projects there is no way to automate a process where adding a setting in a dependent assembly creates an entry for that setting the App.config file for the main project. You have to do that yourself I'm afraid.
But that is exactly the beauty of the system: Two .exe projects can have a dependency on the same .dll project that defines a setting. In each .exe project you can override the setting in the App.config file for the .exe project or you can decide to use the default value defined by the .dll project.

How to access app configuration from a .dll?

I recently broke out a part of my winform app in a .dll. Some of the classes in that dll
wants fetch/store user settings.
The classes just used the VS generated Settings file so it just did
Properties.Settings.Default.SomeSetting = var;Properties.Settings.Default.Save() etc.
What are my options now that I moved that code out to a class library/.dll ?
The hosting application should handle the interface to the config file, not the DLL. Either
Pass whatever settings need to be read/modified within the DLL as parameters, or
Pass in a name-value collection of settings that can be modified by the DLL, and save whatever changes are made by the DLL to the collection when control returns to the calling application.
This is similar in principle to removing a database interface from the business layer of a tiered application and encapsulating it into a data layer.
It doesn't make a lot of sense to me to have a DLL storing user settings. A DLL is a library, not an application, and doesn't directly interact with the user. If classes in the DLL need access to user settings, you can pass them in as parameters.
The Properties class is autogenerated. It is really a wrapper on the config file. If you don't want to change your design, just go into the code and copy it to your DLL. But remember it will no longer be magically maintained (regenerated). Or you can use ConfigurationManager to get at config file directly.
I would not recommand it (better use your own class for settings), but you can try this:
string sectionName = "applicationSettings/" +
appName + ".Properties.Settings";
System.Configuration.ClientSettingsSection section =
(System.Configuration.ClientSettingsSection)
System.Configuration.ConfigurationManager.GetSection(sectionName);
foreach (SettingElement setting in section.Settings)
{
string value = setting.Value.ValueXml.InnerText;
string name = setting.Name;
if (name.ToLower().StartsWith(searchName.ToLower()))
{
return value;
}
}
For those who need to read settings from userDirectory/user.config, here is a solution:
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
ConfigurationSectionGroup userSettings = config.GetSectionGroup("userSettings");
ClientSettingsSection settings = (ClientSettingsSection)userSettings.Sections.Get("[applicationName].Properties.Settings");
SettingElement elem = settings.Settings.Get([settingName]);
var sett = elem.Value.ValueXml.InnerText;

Categories