1

i'm coming from PHP to C#, so please excuse some of my terminology.

Currently i'm working on a small project that requires multiple profiles to be stored in one single file, so i decided to use XML, because INI files (usually my go to guy for text based stuff) are not really supported by C#. At least not in a satisfying way.

Here the basic structure of my XML file:

<profile name="FooBar">
    <btnA pressed="actionA" released="actionB" />
    <btnB pressed="actionX" released="actionY" />
    ...
</profile>
<profile name="XYZ">
    <btnA pressed="actionA" released="actionB" />
    <btnB pressed="actionX" released="actionY" />
    ...
</profile>

In PHP i would generate an associative array with the following structure:

<?php
  foo = array(
    'FooBar' => array(
      'btnA_pressed' => 'actionA',
      'btnA_released' => 'actionB'
      // ...
    ),
    'XYZ' => array(
      // ...
    )
  );

EDIT START

Application Class Structure:

  • Settings (Contains all profiles and a reference to the current profile)
  • Profile (See below)

The Profile class:

public class Profile 
{
    private string _name;
    public string Name 
    {
        get { return this._name; }
        set { this._name = value;}
    }

    private string _btnA_pressed;
    public string BtnA_Pressed { get; set; }
    // and so on...

    public Profile(var data) { // arg is a placeholder
        // associate the data with the properties
    }
}

In short, the Settings class holds all profiles and a reference to the selected profile. Access to the profile goes over Settings.CurrentProfile.propertie_Name()

EDIT END

The question is now, how do i achieve the same or a similar thing in C#? Or are there better methods of achieving the same thing?

Thanks for your help in advance!

keenthinker
  • 7,645
  • 2
  • 35
  • 45
BrainInBlack
  • 139
  • 10
  • 1
    `INI files are not supported in C#` - welcome to the 21st century. Please be notified that it's not 1990 anymore =). Just kidding. Use XML or a DataBase if your data model gets larger – Federico Berasategui Apr 25 '14 at 18:54
  • @HighCore I love things simple and user friendly. INI's are simple and user friendly. At least up to a point. – BrainInBlack Apr 25 '14 at 19:34
  • 1
    `INI's are simple and user friendly` - until you need to represent and store any type of hierarchical data or nested data structures or even related data such as the `Person -> Address` example in my answer ;) – Federico Berasategui Apr 25 '14 at 19:38
  • I totally agree on that point, that's why i chose a different approach to this than my usual way of dealing with stuff like that. – BrainInBlack Apr 25 '14 at 19:52

3 Answers3

3

XML structures can be manipulated very easily with LINQ2XML without the need of typed models (classes).

Reading XML file containing many profile nodes (and i assume your XML file is correct and has one root node), can look like this:

// read existing XML structure
var xml = XDocument.Load("d:\\temp\\xml\\profile.xml");
// take all profile elements
var profiles = xml.Root.Elements("profile").ToList();
foreach (var profile in profiles)
{
    Console.WriteLine(profile.Attribute("name").Value);
    // find all button elements
    var buttons = profile
                    .Elements()
                    .Where (e => e.Name.ToString().StartsWith("btn"));
    // list elements
    foreach (var button in buttons)
    {
        // tag name
        var name = button.Name.ToString();
        // attributes
        var pressed = button.Attribute("pressed").Value;
        var released = button.Attribute("released").Value;
        Console.WriteLine(String.Format("{0} - P'{1}' - R'{2}'", name, pressed, released));
    }
}

The output is:

FooBar
btnA - P'actionA' - R'actionB'
btnB - P'actionX' - R'actionY'
XYZ
btnA - P'actionA' - R'actionB'
btnB - P'actionX' - R'actionY'

Reading a single profile XML structure from a string and then creating a new one can look like this:

var xmlCode = @"<profile name=""FooBar""><btnA pressed=""actionA"" released=""actionB"" /><btnB pressed=""actionX"" released=""actionY"" /></profile>";

try
{
    // read existing XML structure
    var xml = XDocument.Parse(xmlCode); // XDocument.Load("c:\\path\\to\\file.xml");
    // find all button elements
    var buttons = xml.Root
                     .Elements()
                     .Where (e => e.Name.ToString().StartsWith("btn"));
    // list elements
    foreach (var button in buttons)
    {
        // tag name
        var name = button.Name.ToString();
        // attributes
        var pressed = button.Attribute("pressed").Value;
        var released = button.Attribute("released").Value;
        Console.WriteLine(String.Format("{0} - P'{1}' - R'{2}'", name, pressed, released));
    }
    // create xml
    // root element
    var newXml = new XElement("profile", new XAttribute("name", "FooBaz"), 
                    new XElement("btnA", 
                        new XAttribute("pressed", "actionX"), 
                        new XAttribute("released", "actionXR")),
                    new XElement("btnB", 
                        new XAttribute("pressed", "actionZ"), 
                        new XAttribute("released", "actionZR")));
    Console.WriteLine(newXml.ToString());
}
catch (Exception exception)
{
    Console.WriteLine(exception.Message);
}

The output is:

btnA - P'actionA' - R'actionB'
btnB - P'actionX' - R'actionY'
<profile name="FooBaz">
    <btnA pressed="actionX" released="actionXR" />
    <btnB pressed="actionZ" released="actionZR" />
</profile>

You can use LINQ2XML to read the data and fill a list of objects of your Profile type like this:

// read existing XML structure
var xml = XDocument.Load("d:\\temp\\xml\\profile.xml");
// take all profile elements
var profiles = xml.Root.Elements("profile").ToList();
var listOfProfiles = new List<Profile>();
foreach (var profile in profiles)
{
    var profileObject = new Profile("");
    profileObject.Name = profile.Attribute("name").Value;
    // find all button elements
    var buttons = profile
                    .Elements()
                    .Where (e => e.Name.ToString().StartsWith("btn"));
    // list elements
    foreach (var button in buttons)
    {
        // attributes
        var pressed = button.Attribute("pressed").Value;
        var released = button.Attribute("released").Value;
        profileObject.BtnA_Pressed = pressed;
    }
    listOfProfiles.Add(profileObject);
}

You can also use XML serialization - you need to describe your XML structure as a class (typed model) and deserialize (read XML file into your class) resp. serialize (write your XML structure to a file). A generic implementation of the methods for serialization resp. deserialization can look like this:

public void SerializeModel<T>(string fqfn, T entity)
{
    var xmls = new XmlSerializer(entity.GetType());
    var writer = new StreamWriter(fqfn);
    xmls.Serialize(writer, entity);
    writer.Close();
}

public T DeserializeModel<T>(string fqfn)
{
    var fs = new FileStream(fqfn, FileMode.Open);
    var xmls = new XmlSerializer(typeof(T));
    var r = (T) xmls.Deserialize(fs);
    fs.Close();
    return r;
}

The typed model that describes your Profile class and the lists contained within, looks like this (note the usage of the different XML serialization attributes):

public class Profiles
{
    [XmlElement(ElementName="Profile")]
    public List<Profile> Profile { get; set; }
}

public class Profile
{
    [XmlArray(ElementName="Buttons")]
    public List<Button> Buttons { get; set; }
    [XmlAttribute]
    public String Name;
}

public class Button
{
    [XmlAttribute]
    public String Pressed { get; set; }
    [XmlAttribute]
    public String Released;
}

Creation of an XML file:

    var profiles = new Profiles();
    var profileA = new Profile();
    var profileB = new Profile();
    var buttonA = new Button();
    var buttonB = new Button();

    profileA.Buttons = new List<Button>();
    profileB.Buttons = new List<Button>();
    profiles.Profile = new List<Profile>();

    profileA.Name = "Profile A";
    profileB.Name = "Profile B";
    buttonA.Pressed = "Pressed A";
    buttonA.Released = "Release A";
    buttonB.Pressed = "Pressed B";
    buttonB.Released = "Release B";

    profileA.Buttons.Add(buttonA);
    profileB.Buttons.Add(buttonB);
    profiles.Profile.Add(profileA);
    profiles.Profile.Add(profileB);

    var xmlFile = "d:\\temp\\xml\\profile_model.xml";
    SerializeModel<Profiles>(xmlFile, profiles);

The new XML file looks like this (note, the structure was slightly modified because of the way XML serialization in .NET handles arrays/lists):

<?xml version="1.0" encoding="utf-8"?>
<Profiles xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Profile Name="Profile A">
    <Buttons>
      <Button Released="Release A" Pressed="Pressed A" />
    </Buttons>
  </Profile>
  <Profile Name="Profile B">
    <Buttons>
      <Button Released="Release B" Pressed="Pressed B" />
    </Buttons>
  </Profile>
</Profiles>

The file can be then read like this:

    var input = DeserializeModel<Profiles>(xmlFile);
    foreach (var profile in input.Profile)
    {
        var b = profile.Buttons.First();
        Console.WriteLine(String.Format("{0} - {1} - {2}", profile.Name, b.Pressed, b.Released));
    }

The output is as expected:

Profile A - Pressed A - Release A
Profile B - Pressed B - Release B

Both approaches have advantages and disadvantages.

IMO the answer to your question (changed a bit) Is XML the correct approach for saving structured data to a file? is - definitely yes! Nowadays XML is one of the standards for representing / manipulating / exchanging structured data and data generally (data kept in a string). As someone already mentioned INI files were not really meant to represent complex nested structures.

keenthinker
  • 7,645
  • 2
  • 35
  • 45
  • 2
    +1 for providing a different, totally valid approach and for `Both approaches have advantages and disadvantages.` – Federico Berasategui Apr 25 '14 at 19:45
  • While it would never ever be my go-to language for anything else, more recent versions of Visual Basic have *especially* good support for LINQ to XML - and the language pretty much supports everything C# does (libraries, etc) – Katana314 Apr 25 '14 at 20:51
  • @Katana314 it is true, VB.NET has a better *syntactic sugar* regrading XML handling. – keenthinker Apr 25 '14 at 21:22
2

In contrast to PHP where you might be used to doing a lot of arrays and magic-string based stuff, C# is a Statically Typed language where it's usually recommended to create a proper, formally defined, structured Data Model which allows you to manipulate the Data you're dealing with in a strongly typed manner.

This means, for example, that if you're dealing with personal information related data, and you need to deal with the concepts of last name, first name, and age you will want to create a class containing these pieces of data in the form of Properties, like so:

public class Person
{
    public string LastName {get;set;}

    public string FirstName {get;set;}

    public int Age {get;set;}

    //And so on...
}

Notice how each property has an adequate Data Type that allows you to constrain what values it can contain. For example, the fact that the Age property is of type int (integer numbers) automatically means you can never have something like "abc" inside it, and code like this:

var person = new Person();
person.Age = "abc";

will also produce a compile-time error, rather than blowing up at run time, or producing any sorts of inconsistencies in stored data.

Likewise, if your Person objects (in the real world data you're trying to model) have a relation to, say an Address, you're also going to create the Address class:

public class Address
{
    public string Line1 {get;set;} 

    public string Line2 {get;set;}

    public string City {get;set;}

    //And so on...
}

And then model this Person -> Address relationship by creating an additional property in the Person class:

public class Person
{
   //.. All of the above.

   public Address Address {get;set;}
}

Which can be illustrated with a diagram like this:

enter image description here

This approach has the following advantages:

  • It provides Compile Time checking for correctness of the code. Compile-Time errors can be trapped (and need to be corrected) very early in the development cycle. For example, you can't do something like:

person.LatsName where the property name LastName is mispelled LatsName because the compiler "knows" there is no such property in the object model and thus you recieve a compile-time error rather than having the application crash at run-time.

  • It provides IDE support for features such as AutoComplete because the IDE "knows" the object model you're dealing with and thus it knows what properties/methods/events (Members) every class has, and what are the expected parameters and data types for each of these.

So, to answer the overarching question, once you created a proper object model, you can use .Net's built-in Serialization/Deserialization mechanisms to transform instances of classes in your object model (with actual values and properties) to a data format that can be stored in a file on disk, such as XML.

Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • 1
    Never thought of the serialization way, because my mind is still in PHP mode. Where i had to do stuff by hand, for most of the time. Thank You! – BrainInBlack Apr 25 '14 at 19:26
0

Assuming you have a class for profile with all the relevant fields/properties, you can do

    Profile profile = new Profile();
    profile.actionA = "actionA"; // or whatever you have


    var file = new System.IO.StreamWriter(@"c:\temp\Profile_as_xml.xml");

    var writer = new System.Xml.Serialization.XmlSerializer(typeof(Profile));
    writer.Serialize(file, profile);

    file.Close();

See http://msdn.microsoft.com/en-us/library/ms172873.aspx

ErikTJ
  • 2,001
  • 3
  • 21
  • 38