Generic way of creating XML files in C# - c#

I am trying to create a class which saves/reads multiple XML files using LINQ and Genertics.
I am looking for generic way of saving XMl files of multiple type (i.e data in them)
Every XML file will have following format
<ROOT>
<!-- First Element/First Row -->
<NODE>
<COL1>Some value</COL1>
.....
<COLn>Some value</COLn>
</NODE>
.........
<!-- Nth Element = nth row -->
<NODE>
<COL1>Some value</COL1>
.....
<COLn>Some value</COLn>
</NODE>
</ROOT>
Every file will have a different ROOT, NODE and COLx. The Number of columns and data in them depends on the Multi-Dim array input. This input array also gives the number of NODES tags in xml (elements in array).
A method to save an XML file looks like
public void SaveFirstXmlFile()
{
XElement xdoc = new XElement("ThisDocsRoot");
//Iterate for number of rows(elements of data)
for (int nodes= 1; nodes<= NUMBER_OF_NODES; nodes++)
{
xdoc.Add(new XElement(row,
new XElement("Col 1", "Some Value"),
new XElement("Col 2", "Some Value"),
new XElement("Col 3", "Some Value")
));
}
xdoc.Save("/Path/To/XML/File");
}
I wanted to tweak this method so that it works for multiple XML files.I don't know if it is correct way of doiung it, but I strated creating CLasses which define the root,node,columns and path for the XML file. and The data comes from Multi-Dim Arrays created by some other classes.
private class AClassforSomeXMLFile
{
private readonly String _root;
private readonly String _row;
private readonly String[] _columns;
private readonly String _exportPath;
public UsageData()
{
_exportPath = string.Format(#"{0}\xyz.xml",Path.GetDirectoryName(Application.ExecutablePath));
_root = "ROOT";
_row = "NODE";
_columns = new string[]
{
"COL1","COL2", "COL3","COL4",
};
}
public string ROOT { get { return _root; } }
public string ROW { get { return _row; } }
public string[] COLS { get { return _columns; }}
public string EPATH { get { return _exportPath; }}
}

You can add constructor to AClassforSomeXMLFile class, to populate root, row and columns values, and then pass instance of definition to your SaveFirstXmlFile function:
private class XmlDefinition
{
public string ROOT { get;set; }
public string ROW { get;set; }
public string[] COLS { get;set; }
public string EPATH { get;set; }
}
void Foo()
{
var employeeDefinition = new XmlDefinition
{
ROOT = "Employees",
ROW = "Employee",
COLS = new[] { "Name", "Address", "Department", "Salary" },
EPATH = string.Format(#"{0}\employee.xml",Path.GetDirectoryName(Application.ExecutablePath))
};
SaveFirstXmlFile(employeeDefinition); //save employees
var productDefinition = new XmlDefinition
{
ROOT = "Products",
ROW = "Product",
COLS = new[] { "Name", "Description", "Cost" },
EPATH = string.Format(#"{0}\products.xml",Path.GetDirectoryName(Application.ExecutablePath))
};
SaveFirstXmlFile(productDefinition); //save products
}
public void SaveFirstXmlFile(AClassforSomeXMLFile definition)
{
XElement xdoc = new XElement(definition.ROOT);
//Iterate for number of rows(elements of data)
for (int nodes = 1; nodes <= NUMBER_OF_NODES; nodes++)
{
var cols = from c in definition.COLS select new XElement(c, "Some Value");
xdoc.Add(new XElement(definition.ROW, cols.ToArray()));
}
xdoc.Save(definition.EPATH);
}

I do not fully understand the ask here, but It looks like it might be satisfied if you format your XML like this
<NODE>
<COL>Some value</COL>
.....
<COL>Some value</COL>
</NODE>
and then you can read all your COLs into a list
XElement xdoc = new XElement("ThisDocsRoot");
List<XElement> Cols = xdoc.Elements("COL").ToList();

Related

C# Use CSV headers for assigning values to different variables

I'm reading a CSV file and am basically trying to use the headers to determine the ordinal position of the values in the file, though the last part is giving me some trouble. The following is what I have so far:
private static IEnumerable<Cow> ReadCowStream(Stream source)
{
bool isHeader = true;
var cows = new List<Cow>();
using (var reader = new StreamReader(source))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line != null)
{
var values = line.Split(',');
if (isHeader && values.Contains("Weight") && values.Contains("Age"))
{
isHeader = false;
}
else
{
cows.Add(new Cow(
weight: values[0],
age: values[1]));
}
}
}
}
return animals;
}
Example CSV:
Weight,Age
300,10
319,11
100,1
370,9
In this case the output would be a List<Cow> with the first entry having values "Weight": "300" and "Age": "10" obviously, but what if "Weight" and "Age" are reversed? Then I'll assign the wrong values to the wrong variables.
Basically, I want to use the headers for determining whether to put values[0] into weight or age etc., as I assume I can't be guaranteed which comes first in the CSV I'm reading.
Using a library like CsvHelper, the values can be extracted based on the header name rather than index.
private static IEnumerable<Cow> ReadCowStream(Stream source) {
var cows = new List<Cow>();
using (var reader = new StreamReader(source)) {
var csv = new CsvReader(reader);
csv.Read();
csv.ReadHeader();
while (csv.Read()) {
cows.Add(new Cow(weight: csv["Weight"], age: csv["Age"]));
}
}
return cows;
}
So now it does not matter which header comes first in the CSV being read.
The library allows for strongly typed parsing.
If Cow is defined with a default constructor and have the properties in the appropriate typed
public class Cow {
public int Age { get; set; }
public int Weight { get; set; }
}
ReadCowStream could be simplified to
private static IEnumerable<Cow> ReadCowStream(Stream source) {
using (var reader = new StreamReader(source)) {
var csv = new CsvReader(reader);
return csv.GetRecords<Cow>().ToList();
}
}
the CSV reader will parse the lines, create instances and assign the values by matching the headers to the property names.
You could store the header indices for example:
int weightIndex = Array.FindIndex(values, v => v == "Weight");
int ageIndex = Array.FindIndex(values, v => v == "Age");
Then access the values as follows:
cows.Add(new Cow(
weight: values[weightIndex],
age: values[ageIndex]));
With Cinchoo ETL - an open source library, you can do the csv parsing easily with few lines of code
public class Cow
{
public int Age { get; set; }
public int Weight { get; set; }
}
static void Main(string[] args)
{
string csv = #"Weight,Age
300,10
319,11
100,1
370,9";
foreach (Cow rec in ChoCSVReader<Cow>.LoadText(csv).WithFirstLineHeader())
{
Console.WriteLine($"Age: {rec.Age}, Weight: {rec.Weight}");
}
}

C# Adding multiple elements to a list using xdocument

<?xml version="1.0" encoding="utf-8" ?>
<root>
<fileUploadSpecification>
<DirectoryPath>C:\watchFolder</DirectoryPath>
<Region>us-west-2</Region>
<UploadBucket>configurationtestbucket</UploadBucket>
<FileType>
<type>*.txt</type>
<type>*.OpticomCfg</type>
</FileType>
</fileUploadSpecification>
<fileUploadSpecification>
<DirectoryPath>C:\watchFolder</DirectoryPath>
<Region>us-west-2</Region>
<UploadBucket>loguploadbucket</UploadBucket>
<FileType>
<type>*.Xml</type>
<type>*.Json</type>
</FileType>
</fileUploadSpecification>
</root>
This is the XML file I need to parse, I want to get each instance of fileUploadSpecification so that I can put each set of details into a list, I think some type of for loop would be appropriate, where I loop through and add the first set of upload details and then loop through and add the second. This is what I currently have, but it never gets to the second fileUploadSpecification element, it just returns the same one again.
The idea would be to create a new SettingsData for every set of fileUploadSpecification elements, whether it be two like shown above, or 10.
public interface ISettingsEngine
{
IEnumerable<SettingsData> GetSettings();
}
public class SettingsEngine : ISettingsEngine
{
public IEnumerable<SettingsData> GetSettings()
{
List<SettingsData> dataList = new List<SettingsData>();
try
{
var xDoc = XDocument.Load("File1.xml");
var instancesToParse = xDoc.Root.Elements().Count();
var fileCount = xDoc.Root.Elements("FileType").Count();
for (int x = 0; x < instancesToParse; x++)
{
var newSettingsData = new SettingsData();
newSettingsData.UploadBucket = xDoc.Root.Element("fileUploadSpecification").Element("UploadBucket").Value;
newSettingsData.Region = xDoc.Root.Element("fileUploadSpecification").Element("Region").Value;
newSettingsData.DirectoryPath = xDoc.Root.Element("fileUploadSpecification").Element("DirectoryPath").Value;
var query = xDoc.Root.Descendants("FileType").Elements("type");
foreach (XElement e in query)
{
newSettingsData.FileType.Add(e.Value);
}
dataList.Add(newSettingsData);
}
return dataList;
}
catch(Exception)
{
return dataList;
}
}
}
public class SettingsData
{
public List<string> FileType { get; set; }
public string DirectoryPath { get; set; }
public string Region { get; set; }
public string UploadBucket { get; set; }
public SettingsData()
{
FileType = new List<string>();
}
}
var dataList = (from fus in xDoc.Root.Elements("fileUploadSpecification")
select new SettingsData
{
UploadBucket = fus.Element("UploadBucket").Value,
Region = fus.Element("Region").Value,
DirectoryPath = fus.Element("DirectoryPath").Value,
FileType = fus.Element("FileType")
.Elements("type").Select(f =>f.Value).ToList()
}).ToList();
Each time through the loop, you're looking up the first fileUploadSpecification element all over again. You used the Elements() method already, in a few places. That's the one you want. Always favor foreach over for in C#, when you're looping over a collection. It's quicker (to code, not at runtime) and less error prone.
foreach (var uploadSpec in xDoc.Root.Elements("fileUploadSpecification"))
{
var newSettingsData = new SettingsData();
newSettingsData.UploadBucket = uploadSpec.Element("UploadBucket").Value;
newSettingsData.Region = uploadSpec.Element("Region").Value;
newSettingsData.DirectoryPath = uploadSpec.Element("DirectoryPath").Value;
var types = uploadSpec.Descendants("FileType").Elements("type").Select(e => e.Value);
foreach (var type in types)
{
newSettingsData.FileType.Add(type);
}
// Or if newSettingsData.FileType is List<String>...
//newSettingsData.FileType.AddRange(types);
dataList.Add(newSettingsData);
}
James Curran's answer is functionally the same, but it's better form.

Retrieve values of attributes from XML using XDocument

I am using XDocument.Parse method to load following XML :
<AuditMessage>
<Event Action="Read" DateTime="2013/26/7" EventID="100"/>
<User Role="Admin" UserID="12123"/User>
<SourceIdentification SourceID="TeamLondon" SourceType="3"/>
<Network AccessPointID="143.176.8.32" AccessPointTypeCode="1" />
<Network AccessPointID="143.176.8.32" AccessPointTypeCode="`2" />
<Participant ParticipantID="0001" ParticipantType ="2"/>
<Participant ParticipantID="0002" ParticipantType ="3"/>
<Participant ParticipantID="0003" ParticipantType ="3" ParticipantName = "Housh Mangrove"/>
</AuditMessage>
I need to retrieve the values of following attributes in the above XML.
-DateTime
-Role
-AccessPointID
-ParticipantID
-ParticipantName
I have used sourceXML.Root.Element(nodeName).Attribute(attributeToMatch).Value to read a single attribute. I am failing to understand how can I iterate the same thing over different nodes, provided some nodes might be missing.
Please notice :
<Network> and <Participant> nodes are repeating.
ParticipantName attribute exists only in one Instance of
Lastly, any node could be missing in different XMLs provided as Input. Therefore I need to write code in such a way that if a node is missing, the application doesn't throw OBJECT REFERENCE NOT FOUND Exception
here's a quick attempt, you can figure out the ones I didn't add in:
public static void Main()
{
GetAtts(xml);
}
public static Atts GetAtts(string xml)
{
Atts atts = new Atts();
XDocument doc = XDocument.Parse(xml);
if (doc.Root.Element("Event") != null)
atts.datetime = doc.Root.Element("Event").Attribute("DateTime").Value;
//...
foreach (XElement ele in doc.Descendants("Network"))
atts.accesspointid.Add(ele.Attribute("AccessPointID").Value);
return atts;
}
public class Atts
{
public string datetime { get; set; }
public string role { get; set; }
private List<string>_accesspointid = new List<string>();
public List<string> accesspointid { get { return _accesspointid; } set { _accesspointid = value; } }
public List<string> _participantid = new List<string>();
public List<string> participantid { get { return _participantid; } set { _participantid = value; } }
public string participantname { get; set; }
}
you'll have an object you can handle more easily
You can use the Elements method to get an enumeration of nodes of a given name.
You can then test if the enumeration returned any results and look for the appropriate attributes in them.
Something like this, if you want it as CSV:
var data = new List<string>();
var events = doc.Root.Elements("Event");
if (events.Any())
{
foreach (var evt in events)
{
data.Add(evt.Attribute("DateTime").Value);
}
}
var participants = doc.Root.Elements("Participant");
if (participants.Any())
{
foreach (var participant in participants)
{
data.Add(participant.Attribute("ParticipantID").Value);
}
}
var csv = string.Join(", ", data);
Quick and dirty solution - hopefully you can take it from here:
var participantID = String.Join(", ",
xdoc.Root.Elements("Participant")
.Select(e => e.Attribute("ParticipantID"))
.Where(a => a != null)
.Select(a => a.Value)
.Distinct());

C# grid DataSource polymorphism

I have a grid, and I'm setting the DataSource to a List<IListItem>. What I want is to have the list bind to the underlying type, and disply those properties, rather than the properties defined in IListItem. So:
public interface IListItem
{
string Id;
string Name;
}
public class User : IListItem
{
string Id { get; set; };
string Name { get; set; };
string UserSpecificField { get; set; };
}
public class Location : IListItem
{
string Id { get; set; };
string Name { get; set; };
string LocationSpecificField { get; set; };
}
How do I bind to a grid so that if my List<IListItem> contains users I will see the user-specific field? Edit: Note that any given list I want to bind to the Datagrid will be comprised of a single underlying type.
Data-binding to lists follows the following strategy:
does the data-source implement IListSource? if so, goto 2 with the result of GetList()
does the data-source implement IList? if not, throw an error; list expected
does the data-source implement ITypedList? if so use this for metadata (exit)
does the data-source have a non-object indexer, public Foo this[int index] (for some Foo)? if so, use typeof(Foo) for metadata
is there anything in the list? if so, use the first item (list[0]) for metadata
no metadata available
List<IListItem> falls into "4" above, since it has a typed indexer of type IListItem - and so it will get the metadata via TypeDescriptor.GetProperties(typeof(IListItem)).
So now, you have three options:
write a TypeDescriptionProvider that returns the properties for IListItem - I'm not sure this is feasible since you can't possibly know what the concrete type is given just IListItem
use the correctly typed list (List<User> etc) - simply as a simple way of getting an IList with a non-object indexer
write an ITypedList wrapper (lots of work)
use something like ArrayList (i.e. no public non-object indexer) - very hacky!
My preference is for using the correct type of List<>... here's an AutoCast method that does this for you without having to know the types (with sample usage);
Note that this only works for homogeneous data (i.e. all the objects are the same), and it requires at least one object in the list to infer the type...
// infers the correct list type from the contents
static IList AutoCast(this IList list) {
if (list == null) throw new ArgumentNullException("list");
if (list.Count == 0) throw new InvalidOperationException(
"Cannot AutoCast an empty list");
Type type = list[0].GetType();
IList result = (IList) Activator.CreateInstance(typeof(List<>)
.MakeGenericType(type), list.Count);
foreach (object obj in list) result.Add(obj);
return result;
}
// usage
[STAThread]
static void Main() {
Application.EnableVisualStyles();
List<IListItem> data = new List<IListItem> {
new User { Id = "1", Name = "abc", UserSpecificField = "def"},
new User { Id = "2", Name = "ghi", UserSpecificField = "jkl"},
};
ShowData(data, "Before change - no UserSpecifiedField");
ShowData(data.AutoCast(), "After change - has UserSpecifiedField");
}
static void ShowData(object dataSource, string caption) {
Application.Run(new Form {
Text = caption,
Controls = {
new DataGridView {
Dock = DockStyle.Fill,
DataSource = dataSource,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false
}
}
});
}
As long as you know for sure that the members of the List<IListItem> are all going to be of the same derived type, then here's how to do it, with the "Works on my machine" seal of approval.
First, download BindingListView, which will let you bind generic lists to your DataGridViews.
For this example, I just made a simple form with a DataGridView and randomly either called code to load a list of Users or Locations in Form1_Load().
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Equin.ApplicationFramework;
namespace DGVTest
{
public interface IListItem
{
string Id { get; }
string Name { get; }
}
public class User : IListItem
{
public string UserSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Location : IListItem
{
public string LocationSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void InitColumns(bool useUsers)
{
if (dataGridView1.ColumnCount > 0)
{
return;
}
DataGridViewCellStyle gridViewCellStyle = new DataGridViewCellStyle();
DataGridViewTextBoxColumn IDColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn NameColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn DerivedSpecificColumn = new DataGridViewTextBoxColumn();
IDColumn.DataPropertyName = "ID";
IDColumn.HeaderText = "ID";
IDColumn.Name = "IDColumn";
NameColumn.DataPropertyName = "Name";
NameColumn.HeaderText = "Name";
NameColumn.Name = "NameColumn";
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField";
DerivedSpecificColumn.HeaderText = "Derived Specific";
DerivedSpecificColumn.Name = "DerivedSpecificColumn";
dataGridView1.Columns.AddRange(
new DataGridViewColumn[]
{
IDColumn,
NameColumn,
DerivedSpecificColumn
});
gridViewCellStyle.SelectionBackColor = Color.LightGray;
gridViewCellStyle.SelectionForeColor = Color.Black;
dataGridView1.RowsDefaultCellStyle = gridViewCellStyle;
}
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = false;
Random rand = new Random();
bool useUsers = rand.Next(0, 2) == 0;
InitColumns(useUsers);
if(useUsers)
{
TestUsers();
}
else
{
TestLocations();
}
}
private void TestUsers()
{
List<IListItem> items =
new List<IListItem>
{
new User {Id = "1", Name = "User1", UserSpecificField = "Test User 1"},
new User {Id = "2", Name = "User2", UserSpecificField = "Test User 2"},
new User {Id = "3", Name = "User3", UserSpecificField = "Test User 3"},
new User {Id = "4", Name = "User4", UserSpecificField = "Test User 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
}
private void TestLocations()
{
List<IListItem> items =
new List<IListItem>
{
new Location {Id = "1", Name = "Location1", LocationSpecificField = "Test Location 1"},
new Location {Id = "2", Name = "Location2", LocationSpecificField = "Test Location 2"},
new Location {Id = "3", Name = "Location3", LocationSpecificField = "Test Location 3"},
new Location {Id = "4", Name = "Location4", LocationSpecificField = "Test Location 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
}
}
}
The important lines of code are these:
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField"; // obviously need to bind to the derived field
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
dataGridView1.AutoGenerateColumns = false; // Be specific about which columns to show
and the most important are these:
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
If all items in the list are known to be of the certain derived type, just call ConvertAll to cast them to that type.
You'll need to use a Grid template column for this. Inside the template field you'll need to check what the type of the object is and then get the correct property - I recommend creating a method in your code-behind which takes care of this. Thus:
<asp:TemplateField HeaderText="PolymorphicField">
<ItemTemplate>
<%#GetUserSpecificProperty(Container.DataItem)%>
</ItemTemplate>
</asp:TemplateField>
In your code-behind:
protected string GetUserSpecificProperty(IListItem obj) {
if (obj is User) {
return ((User) obj).UserSpecificField
} else if (obj is Location) {
return ((Location obj).LocationSpecificField;
} else {
return "";
}
}
I tried projections, and I tried using Convert.ChangeType to get a list of the underlying type, but the DataGrid wouldn't display the fields. I finally settled on creating static methods in each type to return the headers, instance methods to return the display fields (as a list of string) and put them together into a DataTable, and then bind to that. Reasonably clean, and it maintains the separation I wanted between the data types and the display.
Here's the code I use to create the table:
DataTable GetConflictTable()
{
Type type = _conflictEnumerator.Current[0].GetType();
List<string> headers = null;
foreach (var mi in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
if (mi.Name == "GetHeaders")
{
headers = mi.Invoke(null, null) as List<string>;
break;
}
}
var table = new DataTable();
if (headers != null)
{
foreach (var h in headers)
{
table.Columns.Add(h);
}
foreach (var c in _conflictEnumerator.Current)
{
table.Rows.Add(c.GetFieldsForDisplay());
}
}
return table;
}
When you use autogeneratecolumns it doesnt automatically do this for you?
My suggestion would be to dynamically create the columns in the grid for the extra properties and create either a function in IListItem that gives a list of available columns - or use object inspection to identify the columns available for the type.
The GUI would then be much more generic, and you would not have as much UI control over the extra columns - but they would be dynamic.
Non-checked/compiled 'psuedo code';
public interface IListItem
{
IList<string> ExtraProperties;
... your old code.
}
public class User : IListItem
{
.. your old code
public IList<string> ExtraProperties { return new List { "UserSpecificField" } }
}
and in form loading
foreach(string columnName in firstListItem.ExtraProperties)
{
dataGridView.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = columnName, HeaderText = columnName );
}
If you are willing to use a ListView based solution, the data-bindable version ObjectListView will let you do this. It reads the exposed properties of the DataSource and creates columns to show each property. You can combine it with BindingListView.
It also looks nicer than a grid :)

How to store a collection of custom objects to an user.config file?

I would like to store a collection of custom objects in a user.config file and would like to add and remove items from the collection programmatically and then save the modified list back to the configuration file.
My items are of the following simple form:
class UserInfo
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
In my app.config I already created a custom section:
<configuration>
<configSections>
<section name="userInfo" type="UserInfoConfigurationHandler, MyProgram"/>
</configSections>
<userInfo>
<User firstName="John" lastName="Doe" email="john#example.com" />
<User firstName="Jane" lastName="Doe" email="jane#example.com" />
</userInfo>
</configuration>
I am also able to read in the settings by implementing IConfigurationSectionHandler:
class UserInfoConfigurationHandler : IConfigurationSectionHandler
{
public UserInfoConfigurationHandler() { }
public object Create(object parent, object configContext, System.Xml.XmlNode section)
{
List<UserInfo> items = new List<UserInfo>();
System.Xml.XmlNodeList processesNodes = section.SelectNodes("User");
foreach (XmlNode processNode in processesNodes)
{
UserInfo item = new UserInfo();
item.FirstName = processNode.Attributes["firstName"].InnerText;
item.LastName = processNode.Attributes["lastName"].InnerText;
item.Email = processNode.Attributes["email"].InnerText;
items.Add(item);
}
return items;
}
}
I did all this following this article. However, using this approach I'm only able to read the settings from app.config into a List<UserInfo> collection, but I would also need to write a modified list back.
I was searching the documentation without success and now I'm kind of stuck. What am I missing?
The way to add custom config (if you require more than just simple types) is to use a ConfigurationSection, within that for the schema you defined you need a ConfigurationElementCollection (set as default collection with no name), which contains a ConfigurationElement, as follows:
public class UserElement : ConfigurationElement
{
[ConfigurationProperty( "firstName", IsRequired = true )]
public string FirstName
{
get { return (string) base[ "firstName" ]; }
set { base[ "firstName" ] = value;}
}
[ConfigurationProperty( "lastName", IsRequired = true )]
public string LastName
{
get { return (string) base[ "lastName" ]; }
set { base[ "lastName" ] = value; }
}
[ConfigurationProperty( "email", IsRequired = true )]
public string Email
{
get { return (string) base[ "email" ]; }
set { base[ "email" ] = value; }
}
internal string Key
{
get { return string.Format( "{0}|{1}|{2}", FirstName, LastName, Email ); }
}
}
[ConfigurationCollection( typeof(UserElement), AddItemName = "user", CollectionType = ConfigurationElementCollectionType.BasicMap )]
public class UserElementCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new UserElement();
}
protected override object GetElementKey( ConfigurationElement element )
{
return ( (UserElement) element ).Key;
}
public void Add( UserElement element )
{
BaseAdd( element );
}
public void Clear()
{
BaseClear();
}
public int IndexOf( UserElement element )
{
return BaseIndexOf( element );
}
public void Remove( UserElement element )
{
if( BaseIndexOf( element ) >= 0 )
{
BaseRemove( element.Key );
}
}
public void RemoveAt( int index )
{
BaseRemoveAt( index );
}
public UserElement this[ int index ]
{
get { return (UserElement) BaseGet( index ); }
set
{
if( BaseGet( index ) != null )
{
BaseRemoveAt( index );
}
BaseAdd( index, value );
}
}
}
public class UserInfoSection : ConfigurationSection
{
private static readonly ConfigurationProperty _propUserInfo = new ConfigurationProperty(
null,
typeof(UserElementCollection),
null,
ConfigurationPropertyOptions.IsDefaultCollection
);
private static ConfigurationPropertyCollection _properties = new ConfigurationPropertyCollection();
static UserInfoSection()
{
_properties.Add( _propUserInfo );
}
[ConfigurationProperty( "", Options = ConfigurationPropertyOptions.IsDefaultCollection )]
public UserElementCollection Users
{
get { return (UserElementCollection) base[ _propUserInfo ]; }
}
}
I've kept the UserElement class simple, although it really should follow the pattern of declaring each property fully as described in this excellent CodeProject article. As you can see it represents the "user" elements in your config you provided.
The UserElementCollection class simply supports having more than one "user" element, including the ability to add/remove/clear items from the collection if you want to modify it at run-time.
Lastly there is the UserInfoSection which simply stats that it has a default collection of "user" elements.
Next up is a sample of the App.config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup>
<section
name="userInfo"
type="ConsoleApplication1.UserInfoSection, ConsoleApplication1"
allowDefinition="Everywhere"
allowExeDefinition="MachineToLocalUser"
/>
</sectionGroup>
</configSections>
<userInfo>
<user firstName="John" lastName="Doe" email="John.Doe#company.com" />
<user firstName="Jane" lastName="Doe" email="Jane.Doe#company.com" />
</userInfo>
</configuration>
As you can see, in this example I've included some userInfo/user elements in the App.config. I've also added settings to say they can be defined at machine/app/user/roaming-user levels.
Next we need to know how to update them at run-time, the following code shows an example:
Configuration userConfig = ConfigurationManager.OpenExeConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );
var userInfoSection = userConfig.GetSection( "userInfo" ) as UserInfoSection;
var userElement = new UserElement();
userElement.FirstName = "Sample";
userElement.LastName = "User";
userElement.Email = "Sample.User#company.com";
userInfoSection.Users.Add( userElement );
userConfig.Save();
The above code will create a new user.config file if needed, buried deep inside the "Local Settings\Application Data" folder for the user.
If instead you want the new user added to the app.config file simply change the parameter for the OpenExeConfiguration() method to ConfigurationUserLevel.None.
As you can see, it's reasonably simple, although finding this information required a bit of digging.
I wouldn't store that kind of data in an app.config, at least not if it's meant to be updated programatically. Conceptually, it's for configuration settings, not application data so perhaps you want to store your username and password info in a separate XML file (assuming you can't or don't want to use a database)?
Having said that, then I think your best bet is to read in the app.config as a standard XML file, parse it, add the nodes you want and write it back. The built in ConfigurationManager API doesn't offer a way to write back new settings (which I suppose gives a hint as to Microsoft's intended use).
Use the new System.Configuration API from .Net Framework 2.
(Assembly: System.Configuration) IConfigurationSectionHandler is obsolete.
You can find a lot of very good samples and descriptions at http://www.codeproject.com/KB/dotnet/mysteriesofconfiguration.aspx
There is also a code sample, about how you can modify and save values.
EDIT: Relevant part of the documentation in codeproject
Saves only modified values if any changes exist
Configuration.Save()
Saves the specified level of changes, if any changes exist
Configuration.Save(ConfigurationSaveMode)
Saves the specified level of changes, forcing a save to take place if the second parameter is true
Configuration.Save(ConfigurationSaveMode, bool)
The ConfigurationSaveMode enumeration has the following values:
Full - Saves all configuration properties, whether they have changed or not
Modified - Saves properties that have been modified, even if the current value is the same as the original
Minimal - Saves only properties that have been modified and have different values than the original
private void frmLocalFileTransfer_Load(object sender, EventArgs e)
{
try
{
dt.Columns.Add("Provider");
dt.Columns.Add("DestinationPath");
string[] folders = null;
dt.Columns.Add("SourcePath");
for (int i = 1; i < System.Configuration.ConfigurationManager.ConnectionStrings.Count; i++)
{
string key = System.Configuration.ConfigurationManager.ConnectionStrings[i].Name;
string constr = System.Configuration.ConfigurationManager.ConnectionStrings[i].ConnectionString;
DataGridViewTextBoxColumn column = new DataGridViewTextBoxColumn();
folders = constr.Split('\\');
string newstr = (folders[folders.Length - 2]);
if (!newstr.Contains("BackUp"))
{
DataRow row = dt.NewRow();
row[dt.Columns[0].ToString()] = key;
row[dt.Columns[1].ToString()] = constr;
row[dt.Columns[2].ToString()] = constr;
dt.Rows.InsertAt(row, i - 1);
}
}
foreach (DataColumn dc in dt.Columns)
{
DataGridViewTextBoxColumn column = new DataGridViewTextBoxColumn();
column.DataPropertyName = dc.ColumnName;
column.HeaderText = dc.ColumnName;
column.Name = dc.ColumnName;
column.SortMode = DataGridViewColumnSortMode.Automatic;
column.ValueType = dc.DataType;
GVCheckbox();
gevsearch.Columns.Add(column);
}
if (gevsearch.ColumnCount == 4)
{
DataGridViewButtonColumn btnColoumn = new DataGridViewButtonColumn();
btnColoumn.Width = 150;
btnColoumn.HeaderText = "Change SourcePath";
btnColoumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
gevsearch.Columns.Insert(4, btnColoumn);
}
gevsearch.DataSource = dt;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void btnAddProvider_Click(object sender, EventArgs e)
{
try
{
System.Xml.XmlDocument xDoc = new System.Xml.XmlDocument();
path = "D:\\Pandurang\\Jitendra\\LocalFileTransfer\\LocalFileTransfer
xDoc.Load(path);
System.Xml.XmlElement element = xDoc.CreateElement("add");
element.SetAttribute("name", txtProviderName.Text.Trim());
element.SetAttribute("connectionString", txtNewPath.Text.Trim());
System.Xml.XmlElement elementBackup = xDoc.CreateElement("add");
elementBackup.SetAttribute("name", BackUpName);
elementBackup.SetAttribute("connectionString", txtBackupPath.Text.Trim());
System.Xml.XmlNode node= xDoc.ChildNodes[1].ChildNodes[0];
node.AppendChild(element);
node.AppendChild(elementBackup);
xDoc.Save(path);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
you should create a class like:
public class MySettings : ConfigurationSection
{
public MySettings Settings = (MySettings)WebConfigurationManager.GetSection("MySettings");
[ConfigurationProperty("MyConfigSetting1")]
public string DefaultConnectionStringName
{
get { return (string)base["MyConfigSetting1"]; }
set { base["MyConfigSetting1"] = value; }
}
}
after that in your web.config use:
<section name="MySettings" type="MyNamespace.MySettings"/>
<MySettings MyConfigSetting1="myValue">
That's the way)
If you want to use not attibutes, but properties, just create the class derived from ConfigurationElement and include it to your class defived from ConfigurationSettings.

Categories