I created a CLI application which actually uses the standard config app.Config file.
In this file I put some subsections, like
<typicsTable>
<mainSettings>
<add key="sheetNumber" value="1"/>
<add key="firstDataRow" value="2"/>
</mainSettings>
</typicsTable>
I actually read these settings with
NameValueCollection TypicsConversionTableSettings = (NameValueCollection)ConfigurationManager.GetSection("typicsTable/mainSettings");
int ctSheetNumber = Int32.Parse(TypicsConversionTableSettings["sheetNumber"]);
int ctFirstDataRow = Int32.Parse(TypicsConversionTableSettings["firstDataRow"]);
Everything works fine in this way.
What I want to do now is
1) I want different config files with custom names (i.e. test1.config , test2.config) and take via CLI the right config file;
2) switch to a less ".net config file", and take data from a standard XML file.
I'm now focusing on the point 1, I tried different attempts, I used
ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap();
configFileMap.ExeConfigFilename = #"C:\folderTest\conf1.config";
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
But I absolutely don't get how to read sections AND subsections in the file. How can I do that?
The class that is going to help you, I believe, is System.Xml.Linq.
using System.Xml.Linq;
So Part 1 would be load the file into an XElement:
XElement xConfig = XElement.Load("app.simulated.config");
Here's a quick demo of how you can iterate through everything and also find a single element using a matching condition.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Iterating the config file values and attributes...");
Console.WriteLine("==================================================");
XElement xConfig = XElement.Load("app.simulated.config");
foreach (var element in xConfig.DescendantsAndSelf())
{
Console.WriteLine(element.Name);
foreach (var attribute in element.Attributes())
{
Console.WriteLine("\t" + attribute.Name + "," + attribute.Value);
}
}
Console.WriteLine();
Console.WriteLine("Finding a value using matching conditions.");
Console.WriteLine("==========================================");
XElement xel =
xConfig
.DescendantsAndSelf()
.FirstOrDefault(match =>
(match.Attribute("key") != null) &&
(match.Attribute("key").Value == "sheetNumber"));
Console.WriteLine(
"The value of 'sheetNumber' is " +
xel.Attribute("value").Value
);
// Pause
Console.ReadKey();
}
}
Clone or Download this example from GitHub.
Related
I have a console program 'A' that at a given point will run program 'B' and program 'C'. However I'm having an issue with the app.config associate with each of the program. Basically program A is just a wrapper class that calls different console application, It should not have any app.config but it should use the current running program's app config. So in theory there should be only 2 app.config one for Program B and another for program C.
So if we run Program A and program B gets executed, it should use program B's app.config to get the information and after when program C gets executed it should use Program C's app.config.
Is there a way to do this? Currently i'm doing this:
var value = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["ProgramBKey"].Value;
It does not seem to work. I checked the debug on Assembly.GetExecutingAssembly().Location it's variable is the \bin\Debug\ProgramB.exe and 'ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings' has setting with Count=0 when there are key values as seen below.
sample code Program A:
static void Main(string[] args)
{
if(caseB)
B.Program.Main(args)
else if(caseC)
C.Program.Main(args)
}
sample app.config for Program B:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="ProgramBKey" value="Works" />
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
</startup>
</configuration>
Edit: the following answer pertains to this question from the original post, "Is it possible to compile the app.config for B and C within the exe of the program."
You can use the "Embedded Resource" feature. Here's a small example of using an XML file that's been included as an embedded resource:
public static class Config
{
static Config()
{
var doc = new XmlDocument();
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Fully.Qualified.Name.Config.xml"))
{
if (stream == null)
{
throw new EndOfStreamException("Failed to read Fully.Qualified.Name.Config.xml from the assembly's embedded resources.");
}
using (var reader = new StreamReader(stream))
{
doc.LoadXml(reader.ReadToEnd());
}
}
XmlElement aValue = null;
XmlElement anotherValue = null;
var config = doc["config"];
if (config != null)
{
aValue = config["a-value"];
anotherValue = config["another-value"];
}
if (aValue == null || anotheValue == null)
{
throw new XmlException("Failed to parse Config.xml XmlDocument.");
}
AValueProperty = aValue.InnerText;
AnotherValueProperty = anotherValue.InnerText;
}
}
You can have multiple application using the same config file. That way when you switch applications, they can both find their own parts of the config file.
The way I usually do it is... first let each application "do its own thing", then copy the relevant sections of config file A into config file B.
It will look like this:
<configSections>
<sectionGroup>
<sectionGroup name="applicationSettings"...A>
<sectionGroup name="userSettings"...A>
<sectionGroup name="applicationSettings"...B>
<sectionGroup name="userSettings"...B>
<applicationSettings>
<A.Properties.Settings>
<B.Properties.Settings>
<userSettings>
<A.Properties.Settings>
<B.Properties.Settings>
For me, the whole thing sounds like a "design issue". Why should you want to open Programm B with the config of Programm A?
Are you the author of all those Programms? You might want to use a dll-file instead. This will save you the trouble as all code runs with the config of the Programm running.
Here how you can do it:
Make App.config as "Embedded Resource" in the properties/build action
Copy to output Directory : Do not copy
Add this code to Proram.cs Main
if (!File.Exists(Application.ExecutablePath + ".config"))
{
File.WriteAllBytes(Application.ExecutablePath + ".config", ResourceReadAllBytes("App.config"));
Process.Start(Application.ExecutablePath);
return;
}
Here are the needed functions:
public static Stream GetResourceStream(string resName)
{
var currentAssembly = Assembly.GetExecutingAssembly();
return currentAssembly.GetManifestResourceStream(currentAssembly.GetName().Name + "." + resName);
}
public static byte[] ResourceReadAllBytes(string resourceName)
{
var file = GetResourceStream(resourceName);
byte[] all;
using (var reader = new BinaryReader(file))
{
all = reader.ReadBytes((int)file.Length);
}
file.Dispose();
return all;
}
I have DefaultSchemaSet.xsd. Now I'm getting FileNotFoundException for the codes below. Give me any suggestion, please? May I know how to solve this?
public static void GetDefaultSchemas(string path, XmlSchemaSet schemas, ValidationEventHandler schemaValidationEventHandler)
{
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path))
{
if (stream == null)
{
throw new FileNotFoundException("Cannot find the embedded schemas in the assembly!");
}
var schema = XmlSchema.Read(stream, schemaValidationEventHandler);
schemas.Add(schema);
}
}
Check the format of the resource name:
DefaultNamespace[.Subfolder][...MoreSubfolers].FileName[.extension]
You need to set Build Action to Embedded Resource in project's file's properties.
Also, you need to check the namespace you use for your project:
Try to examine the available resources, so you can find if a particular one present:
var executingAssembly = Assembly.GetExecutingAssembly();
var resourceNames = executingAssembly.GetManifestResourceNames();
foreach (var resourceName in resourceNames)
{
Console.WriteLine("Resource: " + resourceName);
Console.WriteLine("Contents:");
using (var sr = new StreamReader(executingAssembly.GetManifestResourceStream(resourceName)))
{
Console.WriteLine(sr.ReadToEnd());
}
}
Output:
Resource: EmbeddingTests.TextFile1.txt
Contents:
Hello
Resource: EmbeddingTests.NewFolder1.TextFile2.txt
Contents:
Hello 2
In order to make sure you can access it from your code you need to ensure that the file's build action is set to "Embedded Resource"
To help further we really need to see where the file lies in your solution (to give you an exact answer), however in the mean time if you ensure that your parameter "path" follows the pattern:
[DefaultNamespace].[AnySubFolders].[filename.fileextension]
note without the square brackets
some times I will have to load up to 60 winforms or class library projects into a solution.. and change the output path and reference path for each of them..
so I wrote a wpf application for the same
private void Button_Click(object sender, RoutedEventArgs e)
{
var path = txtRootPath.Text;
var projFiles = System.IO.Directory.GetFiles(path, "*.csproj", SearchOption.AllDirectories);
foreach (var item in projFiles)
{
var xDoc = XDocument.Load(item);
var outputNodes = xDoc.Root.Descendants("OutputPath");
foreach (var outoutNode in outputNodes)
{
//this part is never hit..
outoutNode.Value = txtOutputPath.Text;
}
//similarly for referencePath
}
lblResult.Content = string.Format("Files: {0}", projFiles.Count());
}
but outputNodes collection will be empty
could somebody please tell what am I doing wrong here
EDIT:
I figured out that the problem is with xmlns="http://schemas.microsoft.com/developer/msbuild/2003" attribute in Project element..
Solution:
as given in this solution -
Parsing Visual Studio Project File as XML
Linq-to-XML with XDocument namespace issue
You have to use the default namespace when referring to "OutputPath" element.
XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
var outputNodes = xDoc.Root.Descendants(ns + "OutputPath");
<configuration>
<configSections>
<section name="ADMIN" type="System.Configuration.DictionarySectionHandler"/>
</configSections>
<User>
<add key="ExtendTime" value="20"/>
<add key="Name" value="sss"/>
</User>
<configuration>
i have to remove first child element in user config section i.e . Reply me if you have any idea for this.
i am using
Configuration config = ConfigurationManager.OpenExeConfiguration(Context.Parameters["assemblypath"]);
ConfigurationSection section = config.GetSection("USER");
This article may have what you're looking for : http://raquila.com/software/configure-app-config-application-settings-during-msi-install/
Excerpt from article:
string exePath = string.Format("{0}MyWindowsFormsApplication.exe", targetDirectory);
Configuration config = ConfigurationManager.OpenExeConfiguration(exePath);
config.AppSettings.Settings["Param1"].Value = param1;
config.AppSettings.Settings["Param2"].Value = param2;
config.AppSettings.Settings["Param3"].Value = param3;
config.Save();
EDIT: Adding additional code sample and blog reference: http://ryanfarley.com/blog/archive/2004/07/13/879.aspx
using System;
using System.Xml;
using System.Configuration;
using System.Reflection;
//...
public class ConfigSettings
{
private ConfigSettings() {}
public static string ReadSetting(string key)
{
return ConfigurationSettings.AppSettings[key];
}
public static void WriteSetting(string key, string value)
{
// load config document for current assembly
XmlDocument doc = loadConfigDocument();
// retrieve appSettings node
XmlNode node = doc.SelectSingleNode("//appSettings");
if (node == null)
throw new InvalidOperationException("appSettings section not found in config file.");
try
{
// select the 'add' element that contains the key
XmlElement elem = (XmlElement)node.SelectSingleNode(string.Format("//add[#key='{0}']", key));
if (elem != null)
{
// add value for key
elem.SetAttribute("value", value);
}
else
{
// key was not found so create the 'add' element
// and set it's key/value attributes
elem = doc.CreateElement("add");
elem.SetAttribute("key", key);
elem.SetAttribute("value", value);
node.AppendChild(elem);
}
doc.Save(getConfigFilePath());
}
catch
{
throw;
}
}
public static void RemoveSetting(string key)
{
// load config document for current assembly
XmlDocument doc = loadConfigDocument();
// retrieve appSettings node
XmlNode node = doc.SelectSingleNode("//appSettings");
try
{
if (node == null)
throw new InvalidOperationException("appSettings section not found in config file.");
else
{
// remove 'add' element with coresponding key
node.RemoveChild(node.SelectSingleNode(string.Format("//add[#key='{0}']", key)));
doc.Save(getConfigFilePath());
}
}
catch (NullReferenceException e)
{
throw new Exception(string.Format("The key {0} does not exist.", key), e);
}
}
private static XmlDocument loadConfigDocument()
{
XmlDocument doc = null;
try
{
doc = new XmlDocument();
doc.Load(getConfigFilePath());
return doc;
}
catch (System.IO.FileNotFoundException e)
{
throw new Exception("No configuration file found.", e);
}
}
private static string getConfigFilePath()
{
return Assembly.GetExecutingAssembly().Location + ".config";
}
}
Then you would use it like this:
// read the Test1 value from the config file
string test1 = ConfigSettings.ReadSetting("Test1");
// write a new value for the Test1 setting
ConfigSettings.WriteSetting("Test1", "This is my new value");
// remove the Test1 setting from the config file
ConfigSettings.RemoveSetting("Test1");
I have come to the conclusion that it is not possible to access a custom configuration section during installation using:
MyCustomConfigurationSection section = (MyCustomConfigurationSection)config.GetSection("MyCustomConfigurationSection");
When the MSI package is installed, the program executed is the Windows Install(MsiExec), not the program which contains the installer class.
'%windir%\system32\msiexec.exe
In order to access the config we need to workaround this issue either by using the context:
Configuration config = ConfigurationManager.OpenExeConfiguration(this.Context.Parameters["assemblypath"]);
Or, by using reflection retrieve the location of the executing assembly:
Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
As Chuck suggested, you can access the AppSettings and modify them:
AppSettingsSection appSettings = (AppSettingsSection)config.GetSection("appSettings");
appSettings.Settings["Environment"].Value = _Environment;
config.Save();
This is fine, because the installer knows exactly how to deal with
System.Configuration.AppSettingsSection
because that library is part of .NET. However, when it comes to a custom section, the installer needs to know how to deal with that custom config. More than likely, you have it in a class library (in a DLL) that is referenced by your application and that DLL has now been installed in the install directory.
The problem is, as we know from above, MSIExec.exe isn't running in the context of that directory, so the install fails, when it can't find the appropriate DLL in system32, it throws the error:
An error occurred creating the
configuration section handler for
'XXX': Could not load file or assembly
'XXX.dll' or one of its dependencies.
The system cannot find the file
specified.
The only way therefore, to access the custom config, is to treat the config file as an XML document, and edit it using traditional XML management tools:
// load the doc
XmlDocument doc = new XmlDocument();
doc.Load(Assembly.GetExecutingAssembly().Location + ".config");
// Get the node
XmlNode node = doc.SelectSingleNode("//MyCustomConfigurationSection");
// edit node here
// ...
// Save
doc.Save(Assembly.GetExecutingAssembly().Location + ".config");
This technique is described on Ryan Farley's blog, as Chuck pointed out in the comments to his original answer.
Good news! I have found a way how to work-around this problem.
Solution is to intercept loading of assembly and return one we have. To do so
ResolveEventHandler handler = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
AppDomain.CurrentDomain.AssemblyResolve += handler;
try
{
section = config.GetSection("mySection") as MySection;
}
catch(Exception)
{
}
AppDomain.CurrentDomain.AssemblyResolve -= handler;
and
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name == "Dead.Beef.Rocks")
{
return typeof(MySection).Assembly;
}
return null;
}
is there any tool to validate configuration file?
Well, basically your app is the validator - if the config file is not valid, you'll get an exception when starting up. Other than that, I'm not aware of any out-of-the-box validation support for app.config files.
In your directory C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas, you'll find some files called DotNetConfig.xsd / DotNetConfig20.xsd - those are Microsoft-supplied XML schema files, which you could easily use to validate any other config files you might have for validity.
The basic structure for programmatically validating your configs would be something like this:
using(StreamReader xsdReader = new StreamReader(xsdFileName))
{
XmlSchema Schema = new XmlSchema();
Schema = XmlSchema.Read(xsdReader, new ValidationEventHandler(XSDValidationEventHandler));
XmlReaderSettings ReaderSettings = new XmlReaderSettings();
ReaderSettings.ValidationType = ValidationType.Schema;
ReaderSettings.Schemas.Add(Schema);
ReaderSettings.ValidationEventHandler += new ValidationEventHandler(XMLValidationEventHandler);
using(XmlTextReader xmlReader = new XmlTextReader(xmlFileName))
{
XmlReader objXmlReader = XmlReader.Create(xmlReader, ReaderSettings);
while (objXmlReader.Read())
{ }
}
}
Console.WriteLine("Successful validation completed!");
What you need to do now is supply event handlers for those event that get raised when something in the validation goes wrong - that's about it! :-)
Very old question but I had the same question and here is my setup (.net framework 3.5 and up):
I created a console project named 'ConfigurationValidator' :
static void Main(string[] args)
{
try
{
string xsdFileName = ConfigurationManager.AppSettings["configXsdPath"];
string xmlFileName = args[0];
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add(null, xsdFileName);
XDocument doc = XDocument.Load(xmlFileName);
string validationMessage = string.Empty;
doc.Validate(schemas, (sender, e) => { validationMessage += e.Message + Environment.NewLine; });
if (validationMessage == string.Empty)
{
Console.WriteLine("CONFIG FILE IS VALID");
}
else
{
Console.WriteLine("CONFIG FILE IS INVALID : {0}", validationMessage);
}
}
catch(Exception ex)
{
Console.WriteLine("EXCEPTION VALIDATING CONFIG FILE : {0}", ex.Message);
}
}
and the following app.config :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="configXsdPath" value="C:\Program Files (x86)\Microsoft Visual Studio 11.0\Xml\Schemas\DotNetConfig35.xsd"/>
</appSettings>
</configuration>
For each project of the solution add post build event command, for example :
when validation succeeds i get the following output :
when validation fails i get an output like so: