I have this method which populates an object from my XML "custom configuration" config file:
public static BindingList<StationConfiguration> GetStationsFromConfigFile()
{
string xmlDocumentText = File.ReadAllText(GetConfigFilePath());
var doc = new XmlDocument();
doc.LoadXml(xmlDocumentText);
BindingList<StationConfiguration> stations = new BindingList<StationConfiguration>();
foreach (XmlNode node in doc.DocumentElement["StationsSection"].ChildNodes[0].ChildNodes)
{
stations.Add(
new StationConfiguration(
node.Attributes["Comment"].Value
, node.Attributes["FtpUsername"].Value
, node.Attributes["FtpPassword"].Value
, node.Attributes["DestinationFolderPath"].Value
));
}
return stations;
}
As you can see, I'm using File.ReadAllText to pull the contents of the XML config file into a String.
This all works well for a non-encrypted config file. But now I need to encrypt it. The way MSDN suggests doing that begins like this:
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None);
(Incidentally, when I look at the config.FilePath property, it shows me the correct path of the XML config file, just having an extra ".config" extension for some strange reason. It ends with ".exe.config.config" for some odd reason. But I digress...)
This is my StationConfigurationSection class:
public class StationConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("Stations", IsDefaultCollection = false)]
[ConfigurationCollection(typeof(StationCollection),
AddItemName = "add",
ClearItemsName = "clear",
RemoveItemName = "remove")]
public StationCollection Stations
{
get
{
return (StationCollection)base["Stations"];
}
}
public override bool IsReadOnly()
{
return false;
}
}
My complete XML config file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="StationsSection" type="EcFtpClient.StationConfigurationSection, EcFtpClient" />
</configSections>
<StationsSection>
<Stations>
<add Comment="ABC" FtpUsername="eliezer" FtpPassword="secret" DestinationFolderPath="C:\Users\eliezer\Desktop\local dest" FtpTimeoutInSeconds="30" FtpHostname="ftp://192.168.1.100/" FtpFolderPath="" />
</Stations>
</StationsSection>
<startup>
<supportedRuntime version="v2.0.50727" />
</startup>
<appSettings>
<add key="NameOfService" value="ECClient" />
<add key="PollingFrequencyInSeconds" value="60" />
</appSettings>
</configuration>
I would like to use the MSDN System.Configuration approach, since it makes en/de-cryption very easy, but how can I blend their approach with what I have to make it work?
-- UPDATE --
I've got the loading of the file but am still stuck when it comes to saving the file.I've changed the loading method (at the very top of this question) to this:
public static BindingList<StationConfiguration> GetStationsFromConfigFile()
{
Configuration config = ConfigurationManager.OpenExeConfiguration(GetConfigFilePath());
StationConfigurationSection stationsConfig = (StationConfigurationSection)config.GetSection("StationsSection");
var stationCollection = ((StationCollection)stationsConfig.Stations);
BindingList<StationConfiguration> stationsToReturn = new BindingList<StationConfiguration>();
for (int index = 0; index < stationCollection.Count; index++)
{
stationsToReturn.Add(
new StationConfiguration(
stationCollection[index].Comment,
stationCollection[index].FtpUsername,
stationCollection[index].FtpPassword,
stationCollection[index].DestinationFolderPath)
);
return stationsToReturn;
}
What that gets me is the ability to load a file regardless of whether it's encrypted or not - it loads successfully. That's great.
But I'm still not sure how to get saving working. Here's my save method:
public static void SaveStationsToConfigFile(BindingList<StationConfiguration> updatedStations, bool isConfigToBeEncrypted)
{
string configFilePath = GetConfigFilePath();
var xDoc = XDocument.Load(configFilePath);
var xDeclaration = xDoc.Declaration;
var xElement = xDoc.XPathSelectElement("//StationsSection/Stations");
// clear out existing station configurations
xDoc.Descendants("Stations").Nodes().Remove();
foreach (var station in updatedStations)
{
xElement.Add(new XElement("add",
new XAttribute("Comment", station.Station),
new XAttribute("FtpUsername", station.Username),
new XAttribute("FtpPassword", station.Password),
new XAttribute("DestinationFolderPath", station.FolderName),
new XAttribute("FtpTimeoutInSeconds", 30),
new XAttribute("FtpHostname", GetEnvironmentAppropriateFtpHostName()),
new XAttribute("FtpFolderPath", GetEnvironmentAppropriateFtpFolderPath())
));
}
xDoc.Declaration = xDeclaration;
xDoc.Save(configFilePath);
}
And in order to save it with the protection/encrpytion, I need to do something like this - in other words, using the System.Configuration.Configuration objects:
stationsConfig.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider");
stationsConfig.SectionInformation.ForceSave = true;
objConfig.Save(ConfigurationSaveMode.Modified);
But I'm currently still doing the save with XDocument.Save...
Is there a way to convert my XDocument into a System.Configuration.Configuration compatible object?The hack that comes to mind is after the call to XDocument.Save - to load it and save it again using the System.Configuration.Configuration stuff. But that's a hack...
I believe you have to play along with the configuration settings framework. Rather than trying to open the xml file yourself, you need to create a descendant of ConfigurationSection.
That way it will read and write from the encrypted configuration for you.
It looks like you already started that route (EcFtpClient.StationConfigurationSection), but you didn't include any of that code.
Related
I'm relatively new to C#, and just started using XmlElement and the SingleNode Method. For some reason "customSettings" keeps returning null, although the XML Document is being loaded correctly. I've checked that by loading it as a string. I've tried everything i could imagine so far including the attempt in the comment. Any help or suggestions are much appreciated. Here is my XML Doc:
EDIT: works with solution from CodingYoshi, but is there a better way?
EDIT: changed XML and code for NSGaga to resolve read only exception with NameValueCollection in user.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="users_fächer" type="System.Configuration.NameValueSectionHandler" />
</configSections>
<users_faecher>
<add key="user1" value="value1" />
<add key="user2" value="value2" />
</users_faecher>
</configuration>
Code:
string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"Users.config");
string new_fächer = input[1];
ExeConfigurationFileMap configMap = new ExeConfigurationFileMap();
configMap.ExeConfigFilename = dir;
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigurationUserLevel.None);
ConfigurationSection myParamsSection = config.GetSection("users_faecher");
string myParamsSectionRawXml = myParamsSection.SectionInformation.GetRawXml();
XmlDocument sectionXmlDoc = new XmlDocument();
sectionXmlDoc.Load(new StringReader(myParamsSectionRawXml));
NameValueSectionHandler handler = new NameValueSectionHandler();
NameValueCollection users_fächer = handler.Create(null, null, sectionXmlDoc.DocumentElement) as NameValueCollection;
users_fächer.Set(user, new_fächer);
config.Save(ConfigurationSaveMode.Modified, true);
This will get you the first add element:
doc.ChildNodes[0].NextSibling.ChildNodes[1].ChildNodes[0].ChildNodes[0];
This will get you the first add element's value attribute.
doc.ChildNodes[0].NextSibling.ChildNodes[1]
.ChildNodes[0].ChildNodes[0].Attributes["value"].Value;
I am developing an c# application in which I need to fetch data from oracle database server.
In my application I do not want to make a hard coded connection string because sometimes we have to connect it with different DB (for testing purpose it has same schema).
For this I have a plan that I create an xml file and a new form(with admin rights) using this form I update/insert database credentials in xml file or use app.config, but the problem is i don't have any idea how to read and write xml file in predefined manner (in same manner as a connection string should be).
Can you please help me out for creating new xml file or have any batter idea?
how about this code?
public static string ClientName, DbType, ConnectionString;
static DB()
{
try
{
DataTable Dt = new DataTable("Settings");
DataColumn Client = new DataColumn("Client", typeof(string));
DataColumn DataBaseType = new DataColumn("DataBaseType", typeof(string));
DataColumn ConString = new DataColumn("ConnectionString", typeof(string));
Dt.Columns.Add(Client);
Dt.Columns.Add(DataBaseType);
Dt.Columns.Add(ConString);
Dt.ReadXml(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\Settings.xml");
DB.ClientName = Dt.Rows[0]["Client"].ToString();
DB.DbType = Dt.Rows[0]["DataBaseType"].ToString();
DB.Port = Dt.Rows[0]["Port"].ToString();
DB.ConnectionString = Dt.Rows[0]["ConnectionString"].ToString();
}
catch(Exception ex)
{
// Exception message
}
and xml file code is
<?xml version="1.0" standalone="yes"?>
<DocumentElement>
<Settings>
<Client>DSCL</Client>
<DataBaseType>ORACLE</DataBaseType>
<ConnectionString>providerName=system.data.oracleclient;User ID=****;password=****;Data Source=*****;Persist Security Info=True;Provider=OraOLEDB.Oracle;</ConnectionString>
</Settings>
</DocumentElement>
Create that class
[Serializable()]
public class Car
{
[System.Xml.Serialization.XmlElement("StockNumber")]
public string StockNumber { get; set; }
[System.Xml.Serialization.XmlElement("Make")]
public string Make { get; set; }
[System.Xml.Serialization.XmlElement("Model")]
public string Model { get; set; }
}
[Serializable()]
[System.Xml.Serialization.XmlRoot("CarCollection")]
public class CarCollection
{
[XmlArray("Cars")]
[XmlArrayItem("Car", typeof(Car))]
public Car[] Car { get; set; }
}
The Deserialize function:
CarCollection cars = null;
string path = "cars.xml";
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
StreamReader reader = new StreamReader(path);
cars = (CarCollection)serializer.Deserialize(reader);
reader.Close();
And the slightly tweaked xml (I needed to add a new element to wrap ...Net is picky about deserializing arrays):
<?xml version="1.0" encoding="utf-8"?>
<CarCollection>
<Cars>
<Car>
<StockNumber>1020</StockNumber>
<Make>Nissan</Make>
<Model>Sentra</Model>
</Car>
<Car>
<StockNumber>1010</StockNumber>
<Make>Toyota</Make>
<Model>Corolla</Model>
</Car>
<Car>
<StockNumber>1111</StockNumber>
<Make>Honda</Make>
<Model>Accord</Model>
</Car>
</Cars>
</CarCollection>
Also take a look at
Loading custom configuration files
http://www.codeproject.com/Articles/32490/Custom-Configuration-Sections-for-Lazy-Coders
use the app config for your connectionStrings:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<connectionStrings>
<add name="MyDatabase" connectionString="......"/>
</connectionStrings>
</configuration>
add a reference to System.Configuration in your application and get the connection string via :
string connectionString = ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString;
App.Config:
<configuration>
<connectionStrings>
<add name="connstring1"
connectionString="DataSource=123.123.123.123;UserID=XXX;Password=XXX;" />
</connectionStrings>
</configuration>
C# code:
var appConfig = ConfigurationManager.OpenExeConfiguration("your.exe");
string connectionstring = appConfig.ConnectionStrings.ConnectionStrings["connstring1"].ConnectionString;
You are trying to reinvent a wheel.
As people mentioned here, .config files are meant for application configuration. There is a dedicated section for connection strings in them, so you're advised to use it as it is standard and convenient.
Here is your config:
<configuration>
....
<connectionStrings>
<add name="OracleConnectionString" connectionString="providerName=system.data.oracleclient;User ID=****;password=****;Data Source=*****;Persist Security Info=True;Provider=OraOLEDB.Oracle;" providerName="System.Data.OracleClient" />
</connectionStrings>
</configuration>
and here is the code snippet to modify it:
string user = "Me";
string pwd = "qwerty";
Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ConnectionStringsSection section = (ConnectionStringsSection)cfg.GetSection("connectionStrings");
var currentConnectionString = section.ConnectionStrings["OracleConnectionString"];
currentConnectionString.ConnectionString = string.Format("providerName=system.data.oracleclient;User ID={0};password={1};Data Source=*****;Persist Security Info=True;Provider=OraOLEDB.Oracle;", user, pwd);
cfg.Save();
ConfigurationManager.RefreshSection("connectionStrings");
Notes: 1. Be aware that you are saving .config in the /bin, not in your project folder, so when you rerun/redeploy your application, the changes will be discarded.
2. When debugging you won't see the changes as VS creates HOST process and copies a fresh .config file to the /bin. To see the changes you should run without debugging. Cheers.
Create a datatable with your values. do
Dt.WriteXml(fileName);
It will generate xml file. maintain this xml in future.
use Dt.ReadXml(filename) to get the values back in application.
I am struggling with the configuration and setting classes in .NET 2.0
If the following is contaned in a file called app.config
<config>
<appSettings>
<add key="Foo" value="Hello World!"/>
</appSettings>
</config>
I know I can access the appSetting by
// this returns "Hello World!"
ConfigurationManager.AppSettings["Foo"]
However if the file is called app1.config (or any other name) I cannot access the appSetting.
As long as I understand, with ConfigurationManager.OpenExeConfiguration I should read custom config setting files.
Configuration conf = ConfigurationManager.OpenExeConfiguration(#"..\..\app1.config");
// this prints an empty string.
Console.WriteLine(conf.AppSettings.Settings["Foo"]);
However conf.AppSettings.Settings["Foo"] returns an empty string.
I have also tried the following code but no success
ExeConfigurationFileMap exeFileMap = new ExeConfigurationFileMap();
exeFileMap.ExeConfigFilename = System.IO.Directory.GetCurrentDirectory()
+ "\\App1.config";
Configuration myConf = ConfigurationManager.OpenMappedExeConfiguration
(exeFileMap, ConfigurationUserLevel.None);
// returns empty string as well
Console.WriteLine(myConf.AppSettings.Settings["Foo"]);
How to read setting from a file not called app.config?
I have created custom file myCustomConfiguration and changes its property Copy to Output Directory to true
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="Foo" value="Hello World!"/>
</appSettings>
</configuration>
In CS file
static void Main(string[] args)
{
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "myCustomConfiguration.config");
Dictionary<string, string> dictionary = GetNameValueCollectionSection("appSettings", filePath);
//To get your key do dictionary["Foo"]
Console.WriteLine(dictionary["Foo"]);
Console.ReadLine();
}
private static Dictionary<string, string> GetNameValueCollectionSection(string section, string filePath)
{
var xDoc = new XmlDocument();
var nameValueColl = new Dictionary<string, string>();
var configFileMap = new ExeConfigurationFileMap { ExeConfigFilename = filePath };
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
string xml = config.GetSection(section).SectionInformation.GetRawXml();
xDoc.LoadXml(xml);
XmlNode xList = xDoc.ChildNodes[0];
foreach (XmlNode xNodo in xList.Cast<XmlNode>().Where(xNodo => xNodo.Attributes != null))
{
nameValueColl.Add(xNodo.Attributes[0].Value, xNodo.Attributes[1].Value);
}
return nameValueColl;
}
Although this is working but I am also looking for better approach.
You should make use of a Settings-File, it's way more comfortable to use, has save and load methods and you can name it what ever you want. Eg. my Settings-File is called "EditorSettings.settings" and I access its properties like this:
MyNamespace.MyProject.EditorSettings.Default.MyProperty1
I need to read key values from custom sections in app/web.config.
I went through
Reading a key from the Web.Config using ConfigurationManager
and
How can I retrieve list of custom configuration sections in the .config file using C#?
However, they do not specify how to read a custom section when we need to explicitly specify the path to the configuration file (in my case, the configuration file is not in it's default location)
Example of my web.config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<MyCustomTag>
<add key="key1" value="value1" />
<add key="key2" value="value2" />
</MyCustomTag>
<system.web>
<compilation related data />
</system.web>
</configuration>
in which i need to read key value pairs inside MyCustomTag.
When i try (configFilePath is the path to my configuration file):-
var configFileMap = new ExeConfigurationFileMap { ExeConfigFilename = configFilePath };
var config =
ConfigurationManager.OpenMappedExeConfiguration(
configFileMap, ConfigurationUserLevel.None);
ConfigurationSection section = config.GetSection(sectionName);
return section[keyName].Value;
I get a error stating "Cannot access protected internal indexer 'this' here" at section[keyName]
Unfortunately, this is not as easy as it sounds. The way to solve the problem is to get file config file with ConfigurationManager and then work with the raw xml. So, I normally use the following method:
private NameValueCollection GetNameValueCollectionSection(string section, string filePath)
{
string file = filePath;
System.Xml.XmlDocument xDoc = new System.Xml.XmlDocument();
NameValueCollection nameValueColl = new NameValueCollection();
System.Configuration.ExeConfigurationFileMap map = new ExeConfigurationFileMap();
map.ExeConfigFilename = file;
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
string xml = config.GetSection(section).SectionInformation.GetRawXml();
xDoc.LoadXml(xml);
System.Xml.XmlNode xList = xDoc.ChildNodes[0];
foreach (System.Xml.XmlNode xNodo in xList)
{
nameValueColl.Add(xNodo.Attributes[0].Value, xNodo.Attributes[1].Value);
}
return nameValueColl;
}
And the call of the method:
var bla = GetNameValueCollectionSection("MyCustomTag", #".\XMLFile1.xml");
for (int i = 0; i < bla.Count; i++)
{
Console.WriteLine(bla[i] + " = " + bla.Keys[i]);
}
The result:
Formo makes it really easy, like:
dynamic config = new Configuration("customSection");
var appBuildDate = config.ApplicationBuildDate<DateTime>();
See Formo on Configuration Sections
I'm not sure I am going about this right, but I am trying to write a custom configuration file for an asp.NET web project. I want to make it clear this is not a windows form, because half the stuff I find is only for those. I am trying to read and write to this file to change a couple of application settings.
I wrote this huge class using this tutorial. Here's a simplified version:
namespace Tedski.Configuration {
public class TedskiSection : ConfigurationSection {
private static ConfigurationProperty s_propName;
private static ConfigurationPropertyCollection s_properties;
static TedskiSection() {
s_propName = new ConfigurationProperty(
"name",
typeof(string),
null,
ConfigurationPropertyOptions.IsRequired
);
s_properties = new ConfigurationPropertyCollection();
s_properties.Add(s_propName);
}
protected override ConfigurationPropertyCollection Properties {
get { return s_properties; }
}
[ConfigurationProperty("name")]
public string Name {
get {
return (string)base[s_propName];
}
set {
base[s_propName] = value;
}
}
}
}
I am now not sure where to define my configuration. I can put this in my Web.config file like this:
<configuration>
<configSections>
<section name="Tedski" type="Tedski.Configuration.TedskiSection" />
</configSections>
<Tedski name="Ted" />
</configuration>
and everything loads up fine with this:
TedskiSection section = ConfigurationManager.GetSection("Tedski") as TedskiSection;
Console.WriteLine(section.Name); //produces "Ted"
However, I need to be able to load this up with the Configuration object, in order to be able to call Configuration.Save(). I can't seem to load up that specific section and save the Web.config (from what I understand this is dangerous). Another solution I'm trying out is creating a separate .config file (Tedski.config) with the same XML syntax as defined above.
I tried using this answer to load up Tedski.config, but I get an error:
ExeConfigurationFileMap configMap = new ExeConfigurationFileMap();
configMap.ExeConfigFilename = Server.MapPath("~/Tedski.config");
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigUserLevel.None);
TedskiSection section = config.GetSection("Tedski") as TedskiSection; //fails
ConfigurationErrorsException "An error occurred creating the
configuration section handler for Tedski: Could not load type
'Tedski.Configuration.TedskiSection' from assembly
'System.Configuration, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a'
If I got this to load, I could then modify section.Name and call config.Save(), but I'm stuck here.
In your type property, you have to tell it which assembly contains your Tedski.Configuration.TedskiSection. For example:
<section name="Tedski" type="Tedski.Configuration.TedskiSection, TedskiAssemblyName" />
Replace "TedskiAssemblyName" there with the name of the assembly that contains the class.