Blog Archives

Remoting (part 2, advanced), WCF

WCF improves code concepts substantially. There are templates like “WCF Service Application” in Visual Studio making programming extremely easy. You get results with a few clicks.
I am concentrating on an independent code solution today. This basically is a similar solution to my remoting example in part 1. You can clearly see the differences rather than starting an entirely new project type.

WcfSolution

We need two App.config files.
One on the client side

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<system.serviceModel>

    <client>
      <endpoint name="ServerInstance" address="net.tcp://localhost/ServerInstance" binding="netTcpBinding" bindingConfiguration="tcp_Unsecured" contract="Shared.MyInterface"/>
		</client>
    <bindings>
      <netTcpBinding>
        <binding name="tcp_Unsecured">
          <security mode="None" />
        </binding>
      </netTcpBinding>
    </bindings>
    
  </system.serviceModel>
</configuration>

and one on the server-side.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <system.serviceModel>

    <services>
      <service name="Server.ServerInstance">
        <endpoint address="net.tcp://localhost/ServerInstance" binding="netTcpBinding" bindingConfiguration="tcp_Unsecured" contract="Shared.MyInterface" />
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding name="tcp_Unsecured">
          <security mode="None" />
        </binding>
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

I guess you have already noticed the Shared.MyInterface. This is the same approach as in the classical Remoting. We share data definitions.

using System;
using System.Collections.Generic;
using System.ServiceModel;

namespace Shared {
   [ServiceContract(SessionMode = SessionMode.Allowed)]
   public interface MyInterface {
      [OperationContract]
      List<TradeData> GetTrades();
      [OperationContract]
      string HelloWorld(string xName);
      [OperationContract]
      DateTime Ping();     
   } // interface

} // namespace

The shared area also needs our TradeData class, which is used inside the Interface. Do not forget the Serializable attribute. You do not necessarily get an error message. The program can simply refuse proper execution and only provide mind-boggling error messages.

using System;

namespace Shared {

   [Serializable]
   public class TradeData {
      public DateTime tradeTime;
      public string ticker;
      public double price;
      public int quantity;

      public override string ToString() {
         return tradeTime.ToString("dd.MMM.yy HH:mm:ss ") + ticker + " " + quantity + " @ " + price + " EUR";
      } //

      public TradeData(DateTime xTradeTime, string xTicker, double xPrice, int xQuantity) {
         tradeTime = xTradeTime;
         ticker = xTicker;
         price = xPrice;
         quantity = xQuantity;
      } // constructor

   } // class
} // namespace

The class ServerInstance is part of the server-side. It inherits MyInterface and adds the required statements of MyInterface.

using System;
using System.Collections.Generic;
using System.ServiceModel;
using Shared;

namespace Server {
   [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
   internal class ServerInstance : MyInterface {

      public List<TradeData> GetTrades() {
         DateTime lDummyTime = new DateTime(2014, 02, 12, 16, 30, 10);
         TradeData lTrade1 = new TradeData(lDummyTime, "DTE", 11.83, 100);
         TradeData lTrade2 = new TradeData(lDummyTime.AddSeconds(2), "DAI", 66.45, 300);
         TradeData lTrade3 = new TradeData(lDummyTime.AddSeconds(5), "DBK", 35.91, 100);
         return new List<TradeData>() { lTrade1, lTrade2, lTrade3 };
      } //

      public string HelloWorld(string xName) {
         return "Hello " + xName;
      } //

      public DateTime Ping() {
         return DateTime.Now;
      } //

   } // class  
} // namespace

The server side code is relatively short. It is more or less a two liner to instantiate a ServiceHost and open it.

using System;
using System.ServiceModel;
using System.Threading;

namespace Server {
   public class Program {
      
      public static void Main(string[] arguments) {

         Thread.Sleep(10000); // we let the client wait 10 seconds to simulate a server that is down

         using (ServiceHost lServiceHost = new ServiceHost(typeof(ServerInstance))) {
            lServiceHost.Open();

            Console.WriteLine("Service is available.");
            Console.ReadKey();
         }
      } // main

   } // class
} // namespace

And the client side is not much longer. Have a look for yourself. Most of the lines are taking care of printing the data to the console window.

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
using Shared;

namespace Client {
   public class Program {

      public static void Main(string[] args) {

         ChannelFactory<MyInterface> lFactory = new ChannelFactory<MyInterface>("ServerInstance");

         MyInterface lShared = null;
         while (true) {
            try {
               lShared = lFactory.CreateChannel();                              

               var o = lShared.GetTrades();
               var s = lShared.HelloWorld("Mr. Ohta");
               var p = lShared.Ping();

               Console.WriteLine("GetTrades():");
               foreach (var lTrade in o) Console.WriteLine(lTrade.ToString());
               Console.WriteLine();
               Console.WriteLine("HelloWorld(): " + s);
               Console.WriteLine();
               Console.WriteLine("Ping(): " + p);

               break;
            }
            catch (Exception ex) {
               Console.WriteLine(ex.Message);
               Thread.Sleep(2000);
            }
            finally {
               IChannel lChannel = lShared as IChannel;
               if (lChannel.State == CommunicationState.Opened) lChannel.Close();
            }
         }

         Console.ReadKey();
      } // main

   } // class
} // namespace

After starting the client and server concurrently,

MultipleProject

you should get output results like these:

SERVER example output:
Service is available.

CLIENT example output:
Could not connect to net.tcp://localhost/ServerInstance. The connection attempt
lasted for a time span of 00:00:02.0521174. TCP error code 10061: No connection
could be made because the target machine actively refused it 127.0.0.1:808.
Could not connect to net.tcp://localhost/ServerInstance. The connection attempt
lasted for a time span of 00:00:02.0391166. TCP error code 10061: No connection
could be made because the target machine actively refused it 127.0.0.1:808.
GetTrades():
12.Feb.14 16:30:10 DTE 100 @ 11.83 EUR
12.Feb.14 16:30:12 DAI 300 @ 66.45 EUR
12.Feb.14 16:30:15 DBK 100 @ 35.91 EUR

HelloWorld(): Hello Mr. Ohta

Ping(): 17/02/2014 12:30:59

The error messages on the client side are as expected. They are caused by “Thread.Sleep(10000);” on the server side. I thought it would be interesting to show a server which is not running at the time when starting the client.

Put aside all the complex and confusing examples on the internet. This one here is spot on. It provides a nice and comfortable entry point into WCF. The code does not have to be complex.
I recommend YouTube for further WCF explanations.

Advertisements

JSON (basics), follow-up to XML

XML is quite verbose. This leads to higher memory usage and slowness during the (de-)serialization process. There are alternatives in the market that also support the cross-platform approach. The most basic one, which is used among web APIs, is JSON. It is well-integrated into JavaScript.
The .Net framework supports JSON. Thus JSON is a good start to slowly delve into posts about networking. There are more advanced cross-platform solutions. Jackson, GSON and BSON are some of the really good ones. Besides this you can always use the good old .Net data serialization (or remoting). If you need speed rather than flexibility, then I would probably recommend to use your own binary protocol. Unfortunately custom binary protocols are error-prone, debugging can be hell and the control factor for IT support people is close to zero in case the protocol is corrupt. Does transmitting data take longer than encoding/packing data? That is the fundamental question. Protocol Buffers will most likely be the “binary number cruncher’s” best friend. It is a free (Apache License) cross plattforms product. There is also a specific version for the .Net environment. Protocol Buffers is not the fastest tool in the market, but all faster tools I know are (like kryo) for Java only. Protocol Buffers is supported by many programming languages. Its speed is pretty good.
As usual plenty of posts lie ahead of us.

Back to Json.
Here is a quick example from wiki:

This is a follow-up on XML. So we have to start with an XML example somehow 🙂

<person>
  <firstName>John</firstName>
  <lastName>Smith</lastName>
  <age>25</age>
  <address>
    <streetAddress>21 2nd Street</streetAddress>
    <city>New York</city>
    <state>NY</state>
    <postalCode>10021</postalCode>
  </address>
  <phoneNumbers>
    <phoneNumber type="home">212 555-1234</phoneNumber>
    <phoneNumber type="fax">646 555-4567</phoneNumber>
  </phoneNumbers>
</person>

or

<person firstName="John" lastName="Smith" age="25">
  <address streetAddress="21 2nd Street" city="New York" state="NY" postalCode="10021" />
  <phoneNumbers>
     <phoneNumber type="home" number="212 555-1234"/>
     <phoneNumber type="fax"  number="646 555-4567"/>
  </phoneNumbers>
</person>

And JSON looks more like C#, doesn’t it?
Btw. there is a really nice JSON online viewer here.

{
    "firstName": "John",
    "age": 25,
    "address": {
        "streetAddress": "21 2nd Street",
        "city": "New York",
        "state": "NY",
        "postalCode": "10021"
    },
    "phoneNumber": [
        {
            "type": "home",
            "number": "212 555-1234"
        },
        {
            "type": "fax",
            "number": "646 555-4567"
        }
    ]
}

First of all we need to make JavaScriptSerializer available. Go to your Solution Explorer and right-click your project, then select “Properties”. Change your “Target Framework” on the “Application” page to “.Net Framework 4” or higher. Do not select “.Net Framework 4 Client Profile“.

Settings

Add the reference “System.Web.Extensions” to your project. The namespace “System.Web.Script.Serialization” is available now.
(In case it is still in your AssemblyInfo.cs, remove [assembly: AllowPartiallyTrustedCallersAttribute])

Let’s now convert our “Walmart.xml” example to JSON. The example shows how to serialize objetcs. Nothing was changed in the class WalmartWorld (see earlier post).

public static void SaveObjectsJSON(WalmartWorld xWalmartWorld) {
    string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\";
    string lFile = lDesktopPath + "Walmart.json";

    JavaScriptSerializer lSerializer = new JavaScriptSerializer();
    string lSerialized = lSerializer.Serialize(xWalmartWorld);

    File.WriteAllText(lFile, lSerialized);
} //

Formatted example output:

{
  "Food": [
    {
      "Attribute": null,
      "Name": "Banana",
      "Price": 1.99,
      "Description": "Mexican delicious",
      "SomethingElse": null
    },
    {
      "Attribute": null,
      "Name": "Rice",
      "Price": 0.79,
      "Description": "the best you can get",
      "SomethingElse": null
    },
    {
      "Attribute": "What a wonderful world.",
      "Name": "Cornflakes",
      "Price": 3.85,
      "Description": "buy some milk",
      "SomethingElse": null
    },
    {
      "Attribute": null,
      "Name": "Milk",
      "Price": 1.43,
      "Description": "from happy cows",
      "SomethingElse": null
    },
    {
      "Attribute": null,
      "Name": "baked beans",
      "Price": 1.35,
      "Description": "very British",
      "SomethingElse": null
    }
  ],
  "Electronic": [
    {
      "Attribute": null,
      "Name": "Kindle fire",
      "Price": 100,
      "Description": "Amazon loves you",
      "SomethingElse": "the perfect Xmas gift for your kids"
    }
  ],
  "Text": "Luis Armstrong had a fabulous voice."
}
public static void LoadObjectJSON() {
    string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\";
    string lFile = lDesktopPath + "Walmart.json";

    JavaScriptSerializer lSerializer = new JavaScriptSerializer();
    string lSerialized = File.ReadAllText(lFile);
    WalmartWorld lWalmartWolrd = lSerializer.Deserialize<WalmartWorld>(lSerialized);

    foreach (WalmartItem lItem in lWalmartWolrd.Electronic) Console.WriteLine(lItem);
    foreach (WalmartItem lItem in lWalmartWolrd.Food) Console.WriteLine(lItem);
            
    Console.ReadLine();
} //

example output:

Kindle fire   100.00 Amazon loves you  !!! => the perfect Xmas gift for your kids
Banana          1.99 Mexican delicious
Rice            0.79 the best you can get
Cornflakes      3.85 buy some milk
Milk            1.43 from happy cows
baked beans     1.35 very British

Unfortunately we have the same mess as with XML. There are several classes dealing with JSON in the .Net framework.
There is another serializer in the System.Runtime.Serialization.Json namespace called DataContractJsonSerializer. It is intended for use with WCF client applications. It does make use of DataContractAttribute and DataMemberAttribute to identify the data to serialize. If you do not add any of these attributes, then data is ignored.
Add the “System.Runtime.Serialization” reference to your project.

Let’s extend our Walmart classes and add the required attributes. You can remove the XML attributes if you do not want to work with XML anymore. You can leave them in the code to keep the back door open for quick and easy XML conversion. The XML attributes do not affect the program, there are just some extra bytes.

BUT beware of a little typo. If you use DataContractSerializer instead of DataContractJsonSerializer, then you will accidentally create XML and not Json files. Such little problems in life can drive you nuts.

Further reading for DataContractAttribute and DataMemberAttribute.

[Serializable]
[DataContract(Name="WalmartWorld")]
[XmlRoot("Walmart", Namespace = "")]
public class WalmartWorld {
    [XmlElement("food")]
    [DataMember]
    public List<WalmartItem> Food { get; set; }

    [XmlElement("electronic")]
    [DataMember]
    public List<WalmartItem> Electronic { get; set; }

    [XmlText]
    [DataMember]
    public string Text { get; set; }
} // class

[Serializable]
[DataContract(Name = "WalmartItem")]
[XmlRoot("Walmart", Namespace = "")]
public class WalmartItem {
    [XmlAttribute("attr")]
    [DataMember(Name = "Attribute")]  // just to demonstrate that you could define a name
    public string Attribute { get; set; }

    [XmlElement("name")]
    [DataMember]
    public string Name { get; set; }

    [XmlElement("price")]
    [DataMember]
    public double Price { get; set; }

    [XmlElement("description")]
    [DataMember]
    public string Description { get; set; }

    [XmlElement("somethingElse")]
    [DataMember]
    public string SomethingElse { get; set; }

    public override string ToString() {
        return Name.PadRight(12) +
            Price.ToString("#,##0.00").PadLeft(8) + " " +
            Description +
            (string.IsNullOrEmpty(SomethingElse) ? string.Empty : ("  !!! => " + SomethingElse));
    } //
} // class

public static void SaveObjectsJSON2(WalmartWorld xWalmartWorld) {
    string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\";
    string lFile = lDesktopPath + "Walmart2.json";

    using (FileStream lFileStream = new FileStream(lFile, FileMode.OpenOrCreate)) {
        DataContractJsonSerializer lSerializer = new DataContractJsonSerializer(typeof(WalmartWorld));
        lSerializer.WriteObject(lFileStream, xWalmartWorld);
    }
} //

public static WalmartWorld LoadObjectJSON2() {
    string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\";
    string lFile = lDesktopPath + "Walmart.json";

    DataContractJsonSerializer lSerializer = new DataContractJsonSerializer(typeof(WalmartWorld));

    using (FileStream lFileStream = new FileStream(lFile, FileMode.Open)) {
        WalmartWorld lWalmartWolrd = lSerializer.ReadObject(lFileStream) as WalmartWorld;

        foreach (WalmartItem lItem in lWalmartWolrd.Electronic) Console.WriteLine(lItem);
        foreach (WalmartItem lItem in lWalmartWolrd.Food) Console.WriteLine(lItem);
                
        return lWalmartWolrd;
    }
} //

You could define your custom Name for each DataMember attribute. The JSON file would then address this name instead of the property name.

[DataMember(Name = "UseWhateverYouLike")]
public string Attribute { get; set; }

XML (part 4, advanced), XSD verification

Data integrity is a big issue in XML. You can generate XML Schemas to define your data structure and automate data integrity checks. As mentioned before, I am not going to explain the XSD structure itself. My purpose here is to explain the C# part.
Follow the link for a brilliant XSD introduction.

We are still using the embedded “Walmart.xml” file from the previous posts (1, 2, 3).

The easiest way to create an XML Schema is to use Visual Studio 2013. Open the XML file “Walmart.xml”. Then select “Create Schema” in the Window-Menu “XML” (between TEAM and TOOLS). The resulting XSD file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Walmart">
    <xs:complexType>
      <xs:sequence>
        <xs:choice maxOccurs="unbounded">
          <xs:element maxOccurs="unbounded" name="food">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="name" type="xs:string" />
                <xs:element name="price" type="xs:decimal" />
                <xs:element name="description" type="xs:string" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
          <xs:element name="electronic">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="name" type="xs:string" />
                <xs:element name="price" type="xs:unsignedByte" />
                <xs:element name="description" type="xs:string" />
                <xs:element name="somethingElse" type="xs:string" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

You can play with this file and modify it later on. Btw. one line should be amended from

<xs:element name="price" type="xs:unsignedByte" />

to

<xs:element name="price" type="xs:decimal" />

But for now let’s stick to the automatically generated XSD file as it is. Add the file to your Solution Explorer right next to “Walmart.xml” and set the property “Build Action” of “Walmart.xsd” to “Embedded Resource”.

private static void ValidationEvents(object sender, ValidationEventArgs e) {
    switch (e.Severity) {
        case XmlSeverityType.Error:
            Console.WriteLine("Error: " + e.Message);
            break;
        case XmlSeverityType.Warning:
            Console.WriteLine("Warning: " + e.Message);
            break;
    }
} //

public static void ValidateXML() {
    Assembly lAssembly = Assembly.GetExecutingAssembly();
    using (Stream lStreamXml = lAssembly.GetManifestResourceStream("DemoApp.XmlFiles.Walmart.xml")) {
        using (Stream lStreamXsd = lAssembly.GetManifestResourceStream("DemoApp.XmlFiles.Walmart.xsd")) {
            XmlDocument lXmlDocument = new XmlDocument();
            XmlSchema x = XmlSchema.Read(lStreamXsd, new ValidationEventHandler(ValidationEvents));
            lXmlDocument.Schemas.Add(x);
            lXmlDocument.Load(lStreamXml);

            lXmlDocument.Validate(ValidationEvents);
        }
    }

    Console.WriteLine("press enter to exit program");
    Console.ReadLine();
} //

Run ValidateXML(). There should be no error message. Now change the XSD or XML file and see what error messages you get.

XML (part 3, basics, advanced), serialize

We are still working with the environment of part 2. We are using the embedded file “Walmart.xml”.

In part 1 we were loading an XML file the following way:

public static void LoadXml() {
    string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\";
    string lFile = lDesktopPath + "Walmart.xml";
 
    XDocument lXDocument = XDocument.Load(lFile);
 
    // food (using WmItem)
    WmItem[] lFood = (from lData in lXDocument.Descendants("Walmart").Descendants("food")
                        select new WmItem(
                            lData.Element("name").Value,
                            double.Parse(lData.Element("price").Value),
                            lData.Element("description").Value)
                ).ToArray();
 
    foreach (WmItem lItem in lFood) Console.WriteLine(lItem);
 
    Console.WriteLine();
 
    // electronic (quick and dirty, using var)
    var lElectronic = from lData in lXDocument.Descendants("Walmart").Descendants("electronic")
                        select lData;
 
    foreach (var lItem in lElectronic) {
        Console.WriteLine(lItem);
        Console.WriteLine();
        Console.WriteLine(lItem.Element("name").Value);
        Console.WriteLine(lItem.Element("price").Value);
        Console.WriteLine(lItem.Element("description").Value);
    }
 
    Console.ReadLine();
} //

LINQ queries were parsing the XML file and initialized the objects.
Well, there is a more direct way out there. Let the .Net framework do the dirty job. Just make use of properties. Here are some of them:

Serializable
Tells that data can be arranged in a sequence. The object can then be sent across networks or be saved.

Xml Attributes

XmlRoot
This is the root of all elements. Each XML file can only have one root element. Compared to hard disk drives on your computer it would be the “C:” directory.

XmlElement
A field or property of an object becomes an XML element during the conversion.

XmlText
A text field. Only one instance of the XmlTextAttribute can be applied in a class.

XmlAttribute
This attribute is self-explanatory. Use it whenever you want to add attributes to XML items.

Add the following two classes in your project. They define the structure of your XML file. You do not have to use attributes for all types. Attributes are optional. This means you can add as many non-Xml-Elements to a class as you like. They are simply ignored during (de-)serialization processes.

[Serializable]
[XmlRoot("Walmart", Namespace = "")]
public class WalmartWorld {
    [XmlElement("food")]
    public List<WalmartItem> Food { get; set; }

    [XmlElement("electronic")]
    public List<WalmartItem> Electronic { get; set; }

    [XmlText]
    public string Text { get; set; }
} // class

[Serializable]
[XmlRoot("Walmart", Namespace = "")]
public class WalmartItem {
    [XmlAttribute("attr")]
    public string Attribute { get; set; }

    [XmlElement("name")]
    public string Name { get; set; }

    [XmlElement("price")]
    public double Price { get; set; }

    [XmlElement("description")]
    public string Description { get; set; }

    [XmlElement("somethingElse")]
    public string SomethingElse { get; set; }

    public override string ToString() {
        return Name.PadRight(12) + 
            Price.ToString("#,##0.00").PadLeft(8) + " " + 
            Description + 
            (string.IsNullOrEmpty(SomethingElse) ? string.Empty : ("  !!! => " + SomethingElse));
    } //
} // class

Step one is to load our embedded file “Walmart.xml” using above classes. The objects will be created for you. There is no need for further parsing or instanciating.

public static WalmartWorld LoadObjects() {
    string lPath = "DemoApp.XmlFiles.Walmart.xml";
    Assembly lAssembly = Assembly.GetExecutingAssembly();

    using (Stream lStream = lAssembly.GetManifestResourceStream(lPath)) {
        XmlSerializer lXmlSerializer = new XmlSerializer(typeof(WalmartWorld));

        using (StreamReader lStreamReader = new StreamReader(lStream)) {

            return lXmlSerializer.Deserialize(lStreamReader) as WalmartWorld;
        }
    }
} //

Above method returns fully initialized classes. Nice, eh?
The code is amazingly short. Let’s convert and save objects on the desktop with this code:

public static void SaveObjects(WalmartWorld xWalmartWorld) {
    string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\";
    string lFile = lDesktopPath + "Walmart2.xml";

    var lXmlSerializer = new XmlSerializer(typeof(WalmartWorld));

    using (var lStreamWriter = new StreamWriter(lFile)) {
        lXmlSerializer.Serialize(lStreamWriter, xWalmartWorld);
    }    
} //

Again, the code could not be shorter.
You can test above methods with the following:

public static void SerializerTest() {
            
    // load
    WalmartWorld lWalmartWorld = LoadObjects();
    if (lWalmartWorld == null) return; // error

    // print results
    foreach (WalmartItem f in lWalmartWorld.Food) Console.WriteLine(f.ToString());
    foreach (WalmartItem e in lWalmartWorld.Electronic) Console.WriteLine(e.ToString());

    // add some stuff
    lWalmartWorld.Text = "Luis Armstrong had a fabulous voice.";
    lWalmartWorld.Food[2].Attribute = "What a wonderful world.";

    // save
    SaveObjects(lWalmartWorld);

    Console.ReadLine();
} //

Have a look at your file “Walmart2.xml”, which was saved onto your desktop. It is not exactly the same as “Walmart.xml”. We added some text and attributes.
Add “Walmart2.xml” to your Visual Studio Solution Explorer and see that you can load it without any trouble. Remember to set the file property “Build Action” to “Embedded Resource”.
You can also use a file stream to load the XML file. The program just gets shorter again, not longer:

public static WalmartWorld LoadXmlAsObject() {
    string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\";
    string lFile = lDesktopPath + "Walmart2.xml";

    XmlSerializer lXmlSerializer = new XmlSerializer(typeof(WalmartWorld));

    using (StreamReader lStreamReader = new StreamReader(lFile)) {
        return lXmlSerializer.Deserialize(lStreamReader) as WalmartWorld;
    }
} //

XML (part 2, basics), embedded, XDocument, XmlDocument, XmlTextReader, XPathNavigator

Embedded

We covered basic file IO for XML files yesterday. This is very useful for settings files. There are some XML files though that you want to hide from the user. I am not talking about encryption. Today we are going to embed the XML file the project itself.
We are still using yesterday’s “Walmart.xml” example. Create a folder “XmlFiles” in your Solution Explorer and drag the file into it.

XmlFileLocation

Now right-click the file “Walmart.xml” and select “Properties”. Change the “Build Action” to “Embedded Resource”.

EmbeddedResourceProperty

Use the following code to load the resource as an XDocument:

// since .Net 3.5
// namespace System.Xml.Linq
// xPath: namespace.folder.file.ext => here: "DemoApp.XmlFiles.Walmart.xml"
public static XDocument getAssemblyXDocument(string xPath) {
    Assembly lAssembly = Assembly.GetExecutingAssembly();
    using (Stream lStream = lAssembly.GetManifestResourceStream(xPath)) {
        XDocument lXDocument = XDocument.Load(lStream);
        return lXDocument;
    }
} //

You can also load the document as an XmlDocument.

// since .Net 1.1
// namespace System.Xml
// xPath: namespace.folder.file.ext => here: "DemoApp.XmlFiles.Walmart.xml"
public static XmlDocument getAssemblyXml(string xPath) {
    Assembly lAssembly = Assembly.GetExecutingAssembly();
    using (Stream lStream = lAssembly.GetManifestResourceStream(xPath)) {
        XmlDocument lXmlDocument = new XmlDocument();
        lXmlDocument.Load(lStream);
        return lXmlDocument;
    }
} //
public static void LoadXmlExample() {
    string lPath = "DemoApp.XmlFiles.Walmart.xml";
    XmlDocument lXml = getAssemblyXml(lPath);
    XDocument lXDoc = getAssemblyXDocument(lPath);
} //

Using an XmlDocument is similar to XDocument. XmlDocument does not support LINQ, which is a big disadvantage.

XmlDocument reads the entire document and initializes a tree structure, which needs a lot of memory. Processing bigger Xml files can cause issues. The tree can be accessed either by browsing through the nodes or by using XPath queries.
You can browse through documents forward/backward and carry out modifications.

XDocument (LINQ to XML) is easy to code and understand. Again the entire document is loaded into memory. To read Xml files we can use the XDocument or XElement class. Both classes support Load() methods. XElement represents an XML fragment while XDocument represents an entire XML document.

XmlTextReader is very fast and memory efficient. Unlike XmlDocument it can only read in forward direction. Searching for something specific is awkward. As the name says, there is no method to write or to modify data. And data validation is also left behind.

XPathNavigator is more complex and slower than XmlReader. XPathNavigator has to read enough data to be able to execute XPath queries. XmlDocument internally uses XPathNavigator. Using XPathNavigator directly is faster than using it via XmlDocument.

Using XmlTextReader:

public static void UseTextReader() {
    string lPath = "DemoApp.XmlFiles.Walmart.xml";
    Assembly lAssembly = Assembly.GetExecutingAssembly();

    using (Stream lStream = lAssembly.GetManifestResourceStream(lPath)) {
        using (XmlTextReader lXmlReader = new XmlTextReader(lStream)) {
            while (lXmlReader.Read()) {
                if (!lXmlReader.IsStartElement()) continue;
                switch (lXmlReader.Name) {
                    case "name":
                        Console.WriteLine("name: " +  lXmlReader.ReadString());
                        break;
                    case "price":
                        Console.WriteLine("price: " + lXmlReader.ReadString());
                        break;
                    case "description":
                        Console.WriteLine("description: " + lXmlReader.ReadString());
                        break;
                    case "somethingElse":
                        Console.WriteLine("somethingElse: " + lXmlReader.ReadString());
                        break;
                    default:
                        Console.WriteLine(Environment.NewLine + lXmlReader.Name);
                        break;
                }
            }
        }
    }
    Console.ReadLine();
} //

example output:

Walmart

food
name: Banana
price: 1.99
description: Mexican delicious

food
name: Rice
price: 0.79
description: the best you can get

food
name: Cornflakes
price: 3.85
description: buy some milk

food
name: Milk
price: 1.43
description: from happy cows

electronic
name: Kindle fire
price: 100
description: Amazon loves you
somethingElse: the perfect Xmas gift for your kids

food
name: baked beans
price: 1.35
description: very British

Using XPathNavigator:

public static void UseXPathNavigator() {
    string lPath = "DemoApp.XmlFiles.Walmart.xml";
    Assembly lAssembly = Assembly.GetExecutingAssembly();

    using (Stream lStream = lAssembly.GetManifestResourceStream(lPath)) {
        XPathDocument lXPathDoc = new XPathDocument(lStream);
        XPathNavigator lXNavi = lXPathDoc.CreateNavigator();
        lXNavi.MoveToRoot();
        lXNavi.MoveToFirstChild();

        do {
            if (lXNavi.MoveToFirstAttribute()) {
                Console.WriteLine(lXNavi.Name + "=" + lXNavi.Value);
                lXNavi.MoveToParent();
            }

            if (!lXNavi.MoveToFirstChild()) continue;
            do {
                if (!lXNavi.MoveToFirstChild()) break;
                do {
                    Console.WriteLine(lXNavi.Name + "=" + lXNavi.Value);
                } while (lXNavi.MoveToNext());
                Console.WriteLine();
                lXNavi.MoveToParent();
            } while (lXNavi.MoveToNext());
            lXNavi.MoveToParent();
        } while (lXNavi.MoveToNext());

        Console.ReadLine();
    }
} //

example output:
name=Banana
price=1.99
description=Mexican delicious

name=Rice
price=0.79
description=the best you can get

name=Cornflakes
price=3.85
description=buy some milk

name=Milk
price=1.43
description=from happy cows

name=Kindle fire
price=100
description=Amazon loves you
somethingElse=the perfect Xmas gift for your kids

name=baked beans
price=1.35
description=very British

Using XPathNavigator with query:

public static void UseXPathNavigator(string xQuery) {
    string lPath = "DemoApp.XmlFiles.Walmart.xml";
    Assembly lAssembly = Assembly.GetExecutingAssembly();

    using (Stream lStream = lAssembly.GetManifestResourceStream(lPath)) {
        XPathDocument lXPathDoc = new XPathDocument(lStream);
        XPathNavigator lXNavi = lXPathDoc.CreateNavigator();

        XPathNodeIterator lIterator = lXNavi.Select(xQuery);

        if (lIterator.Count <= 0) {
            Console.WriteLine("Nothing found!");
            return;
        }

        while (lIterator.MoveNext()) {
            Console.WriteLine(lIterator.Current.Value);
        }
    }            
} //

public static void TestQueries() {
    UseXPathNavigator(@"/Walmart/electronic");
    Console.WriteLine();
    UseXPathNavigator(@"/Walmart/food[name='Banana']");
            
    Console.ReadLine();
} //

example output:
Kindle fire100Amazon loves youthe perfect Xmas gift for your kids

Banana1.99Mexican delicious

My opinion is: Make use of LINQ to XML. It is very comfortable and can save you a lot of time. If you have a lot of data, then you should consider a database rather than XML files.

We will discuss XML object serialization tomorrow. Let C# save and load objects the nice way.

XML (part 1, basics), file IO

The last days were tough. Writing posts requires a lot of time. Explaining some basic know-how will give me some recreation today.

I will concentrate on the C# parts. There will be no explanations about XML, which is well documented across the internet.

XML stands for eXtensible Markup Language. XML is a versatile and flexible data structure. It is easy to read, learn and understand. The downside is the amount of bytes it needs. You can use XML to transport and/or store data, but it quickly becomes quite inefficient when a lot of datasets are involved.

XML is a well established industry standard. So there is no way around it, you have to know at least something about it.

Here is an XML file that we will use in the loading example:

<?xml version="1.0" encoding="UTF-8"?>
<!-- XML EXAMPLE -->
<Walmart>
	<food>
		<name>Banana</name>
		<price>1.99</price>
		<description>Mexican delicious</description>
	</food>
	<food>
		<name>Rice</name>
		<price>0.79</price>
		<description>the best you can get</description>
	</food>
	<food>
		<name>Cornflakes</name>
		<price>3.85</price>
		<description>buy some milk</description>
	</food>
	<food>
		<name>Milk</name>
		<price>1.43</price>
		<description>from happy cows</description>
	</food>
  <electronic>
    <name>Kindle fire</name>
    <price>100</price>
    <description>Amazon loves you</description>
    <somethingElse>the perfect Xmas gift for your kids</somethingElse>
  </electronic>
	<food>
		<name>baked beans</name>
		<price>1.35</price>
		<description>very British</description>
	</food>
</Walmart>

Paste the XML structure into a text editor and save it as an XML file (“Walmart.xml”) onto your desktop.
The following class will be used to store datasets in memory.

 public class WmItem {
    public readonly string name;
    public readonly double price;
    public readonly string description;

    public WmItem(string xName, double xPrice, string xDescription) {
        name = xName;
        price = xPrice;
        description = xDescription;
    } // constructor

    public override string ToString() {
        return name.PadRight(12) + price.ToString("#,##0.00").PadLeft(8) + " " + description;
    } //
} // class

Loading XML files is easy. Processing files takes a bit longer. The centerpiece generally is the parsing algorithm.
There is a field called “somethingElse” in the XML file. It does not cause any trouble. The code simply disregards it. I added it to demonstrate the “eXtensible” in “eXtensible Markup Language”.

public static void LoadXml() {
    string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\";
    string lFile = lDesktopPath + "Walmart.xml";

    XDocument lXDocument = XDocument.Load(lFile);

    // food (using WmItem)
    WmItem[] lFood = (from lData in lXDocument.Descendants("Walmart").Descendants("food")
                        select new WmItem(
                            lData.Element("name").Value,
                            double.Parse(lData.Element("price").Value),
                            lData.Element("description").Value)
                ).ToArray();

    foreach (WmItem lItem in lFood) Console.WriteLine(lItem);

    Console.WriteLine();

    // electronic (quick and dirty, using var)
    var lElectronic = from lData in lXDocument.Descendants("Walmart").Descendants("electronic")
                        select lData;

    foreach (var lItem in lElectronic) {
        Console.WriteLine(lItem);
        Console.WriteLine();
        Console.WriteLine(lItem.Element("name").Value);
        Console.WriteLine(lItem.Element("price").Value);
        Console.WriteLine(lItem.Element("description").Value);
    }

    Console.ReadLine();
} //

example output:
Banana          1.99 Mexican delicious
Rice            0.79 the best you can get
Cornflakes      3.85 buy some milk
Milk            1.43 from happy cows
baked beans     1.35 very British

<electronic>
  <name>Kindle fire</name>
  <price>100</price>
  <description>Amazon loves you</description>
  <somethingElse>the perfect Xmas gift for your kids</somethingElse>
</electronic>

Kindle fire
100
Amazon loves you

Saving XML is also straight forward. I added a comment and attributes for demonstration purposes. The program generates and saves the XML file “Genesis.xml” on your desktop.

public static void SaveXml() {
    string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\";
    string lFile = lDesktopPath + "Genesis.xml";

    WmItem[] lMetals = { 
                            new WmItem("Lead", 1.0, "here we go"),
                            new WmItem("Silver", 2.0, "cutlery"),
                            new WmItem("Gold", 3.0, "wife's best friend"),
                            new WmItem("Platinum", 4.0, "posh") 
                        };
            
    XDocument lXDocument = new XDocument();
    lXDocument.Declaration = new XDeclaration("1.0", "utf-8", "yes");
    lXDocument.Add(new XComment("copyfight by Bastian M.K. Ohta"));
            
    XElement lLME = new XElement("London_Metal_Exchange", new XAttribute("attribute1", "buy here"), new XAttribute("AreYouSure", "yes"));
    lXDocument.Add(lLME);

    foreach (WmItem lMetal in lMetals) {
        XElement lGroup = new XElement("metal");
        lGroup.Add(new XElement("name", lMetal.name));
        lGroup.Add(new XElement("price", lMetal.price));
        lGroup.Add(new XElement("description", lMetal.description));
        lLME.Add(lGroup);
    }

    lXDocument.Save(lFile);

    //Console.ReadLine();
} //