Xml Schemas From Code / XML validation against Schema File And More

I don’t know about you lot but I work with XML files a bit, but I don’t have to mess around with XSD (xml schema) files that often. And it seems like every time I do I forget what I did last time. To this end I thought I would write this up somewhere, so I can refer back to it.

 

So what we will we cover here?

I will be covering these things:

  1. Create a XML file from C# objects
  2. Create a XSD from a XML file using C#
  3. Validate a XML file against a XSD schema file using C#

 

1. Create a XML file from C# objects

So lets say we have created the following objects in C# that we wish to serialize to XML.

public class OrderList
{
    public OrderList()
    {
        Orders = new List();
    }

    public List Orders { get; set; }
}

public class Order
{

    public OrderSummary OrderSummary { get; set; }
    public Customer Customer { get; set; }
}


public class Address
{
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string AddressLine3 { get; set; }
    public string City { get; set; }
    public string County { get; set; }
    public string PostCode { get; set; }
}

public class Customer
{
    public string Title { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }

}


public class OrderSummary
{
    public OrderSummary()
    {
        OrderLines = new List();
    }

    public List OrderLines { get; set; }
    public Address DeliveryAddress { get; set; }
    public DateTime DeliveryDate { get; set; }

}

public class OrderLine
{
    public decimal ItemQuanity { get; set; }
    public string ItemName { get; set; }
}

 

How do we then take that and save it to XML? Turns out this is very easy we can just use some code like this:

 

public static void CreateXmlFile(string filename)
{
    Address add = new Address()
    {
        AddressLine1 = "AddressLine1",
        AddressLine2 = "AddressLine2",
        AddressLine3 = "AddressLine3",
        City = "City",
        County = "County",
        PostCode = "PostCode"
    };

    Customer cust = new Customer()
    {
        Email = "Email",
        FirstName = "John",
        LastName = "Barnes",
        Phone = "13311",
        Title = "Mr"
    };


    OrderList orders = new OrderList();


    var orderSummary = new OrderSummary()
    {
        DeliveryAddress = add,
        DeliveryDate = DateTime.Now,
        OrderLines = new List()
        {
            new OrderLine() {ItemQuanity = 150, ItemName = "TestItem1" },
            new OrderLine() {ItemQuanity = 250, ItemName = "TestItem2" },
            new OrderLine() {ItemQuanity = 4, ItemName = "TestItem3" },
        },
    };


    //order1
    Order order1 = new Order();
    order1.Customer = cust;
    order1.OrderSummary = orderSummary;
    orders.Orders.Add(order1);


    //order2
    Order order2 = new Order();
    order2.Customer = cust;
    order2.OrderSummary = orderSummary;
    orders.Orders.Add(order1);

    XmlSerializer xmlSerializer = new XmlSerializer(typeof(OrderList));

    using (FileStream stream = File.OpenWrite(filename))
    {
        xmlSerializer.Serialize(stream, orders);
    }
}

 

That is enough to write a XML file to disk that matches your C# objects. Cool so far. Let’s continue

 

2. Create a XSD from a XML file using C#

Now there are many ways to do this. Here are some choices

  • Double click a valid XML in Visual Studio and use the Xml menu to create a schema
  • Use the xsd.exe command line tool which you would use something like xsd.exe SomeXmlFile.xml
  • Use C# to programmatically write out a XSD file that matches some object definition

 

In the past I would have use the XSD.exe command line to do this. But a strange this happens when I take the output of that and try and include it in Visual Studio. Visual Studio tries to create a strongly typed DataSet out of the XSD file. I don’t want this, I want it to remain as a XSD schema file. I think there is a way to stop this happening by altering the file produced by xsd.exe, but there are 2 other ways. For this I have chosen to use a programmatical approach, which is as follows:

 

public static void CreateSchemaFromXml(string fileName)
{

    //CREATE SCHEMA FROM XML

    XmlSerializer xmlSerializer = new XmlSerializer(typeof(OrderList));

    XmlSchemas schemas = new XmlSchemas();
    XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);

    XmlTypeMapping mapping = new XmlReflectionImporter()
        .ImportTypeMapping(typeof(OrderList));
    exporter.ExportTypeMapping(mapping);
    var schemasData = TrimSchema(schemas);

    using (FileStream stream = File.OpenWrite(fileName))
    {
        schemasData.First().Write(stream);
    }
}

private static List TrimSchema(XmlSchemas schemas)
{
    List schemasData = new List(
        schemas.Where(s => s.TargetNamespace != "http://www.w3.org/2001/XMLSchema" &&
        s.TargetNamespace != "http://microsoft.com/wsdl/types/"));

    return schemasData;
}

 

This will produce a valid XSD file on disk that you may then fiddle with by adding more restrictions say.  So now all we need to do is carry out some validation of XML file(s) against this XSD file.

 

3. Validate a XML file against a XSD schema file using C#

 

This is easily achieved using some test code. But before we look at that, this is what the project looks like in Visual Studio

 

image

 

So you can see that there is a folder with good and bad files that I wish to test against the XSD file. Here is the complete test case code:

 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Xml.Schema;
using NUnit.Framework;

namespace XmlTests
{
    [TestFixture]
    public class StaticXmlFileTests
    {

        
        //Baddies
        [TestCase(@"\Xml\BadAgainstSchema\OrdersBADExampleFileNoOrderSummary.xml", false)]
        [TestCase(@"\Xml\BadAgainstSchema\OrdersBADExampleFile_AddressLineTooLong.xml", false)]


        //Goodies
        [TestCase(@"\Xml\Good\OrdersGOODExampleFile_FullFeatureSet.xml", true)]
        [TestCase(@"\Xml\Good\OrdersGOODExampleFile_MultipleOrderLines.xml", true)]
        [TestCase(@"\Xml\Good\OrdersGOODExampleFile_MultipleOrders.xml", true)]
        [TestCase(@"\Xml\Good\OrdersGOODExampleFile_SingleOrder.xml", true)]
        [TestCase(@"\Xml\Good\OrdersGOODExampleFile_SingleOrderLine.xml", true)]
        public void TestFileProducesExpectedSchemaValidationResult(string filename, bool exepectedValidationResult)
        {


            var xmlFile = ObtainFullFilePath(filename);
            var xsdFile = ObtainFullFilePath(@"\Xml\OrdersExampleFile.xsd");

            //VALIDATE XML AGAINST SCHEMA C#
            var xdoc = XDocument.Load(xmlFile);
            var schemas = new XmlSchemaSet();
            using (FileStream stream = File.OpenRead(xsdFile))
            {
                schemas.Add(XmlSchema.Read(stream, (s, e) =>
                {
                    var x = e.Message;
                }));
            }

            bool isvalid = true;
            StringBuilder sb = new StringBuilder();
            try
            {
                xdoc.Validate(schemas, (s, e) => 
                    {
                        isvalid = false;
                        sb.AppendLine(string.Format("Line : {0}, Message : {1} ", 
                            e.Exception.LineNumber, e.Exception.Message));
                    });
            }
            catch (XmlSchemaValidationException)
            {
                isvalid = false;
            }

            var validationErrors = sb.ToString();
            Assert.AreEqual(exepectedValidationResult, isvalid);
            if (exepectedValidationResult)
            {
                Assert.AreEqual(string.Empty, validationErrors);
            }
            else
            {
                Assert.AreNotEqual(string.Empty, validationErrors);
            }

        }



        private string ObtainFullFilePath(string fileName)
        {
            var path = TestContext.CurrentContext.TestDirectory;
            return string.Format("{0}{1}", path, fileName);
        }
    }
}

 

And here is a screen shot of all the tests working as expected:

 

image

 

 

You can find a small project for this on github:  https://github.com/sachabarber/XmlTests

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: