Menu

Document-Centric .NET

May 12, 2004

Eric Gropp

The principle "program to an interface, not an implementation" helps control the complexity and enhance the flexibility of systems. XML interfaces are a natural extension of this principle that bring a number of new benefits in terms of flexibility, reusability, simplified code, and readiness for enterprise environments.

We will walk through a sample application to see how the .NET framework classes can work together in an application centered around an XML interface. This XML interface will be the hub of present and future requirements. Our application will produce a bar graph from a variety of data sources. Though this example is relatively trivial, similar approaches can solve larger and more complex problems.

The Schema

Our bar graph application will revolve around a schema written in the W3C XML Schema language. Specifying this schema will be one of the first and most important design steps. Stability is key and all the better if you can use a standardized schema for your application. However, keep in mind that there are W3C XML Schema features that are not directly compatible with .NET's XML-to-database and XML-to-object mapping tools.

Flow of our example document centric application

For this application we'll use a custom schema: BarGraph.xsd. An instance document conforming to this schema looks like the following:

<BarGraph xmlns="http://example.org/bargraph">

  <Bar>

    <Title>Portland</Title>

    <Value>32</Value>

  </Bar>

  <Bar>

    <Title>Berlin</Title>

    <Value>20</Value>

  </Bar>

  <Bar>

    <Title>Pune</Title>

    <Value>16</Value>

  </Bar>

</BarGraph>

Taming the Winds: Input into our Application

We'll start by gathering input from a variety of sources. The following three examples will each produce the same result: an XmlReader containing bar graph XML. XmlReader is an abstract class that plays a similar role to other platforms' use of SAX, in that it can be used to pass the contents of a XML text file, a DOM tree, or a high-throughput dynamic data source.

GetXml() #1: REST web service

The most familiar benefit of XML interfaces is that they allow applications to span machine and platform boundaries. Techniques that are well described asREST can be an easy way to pull XML from a network resource.

  • We will use the XmlTextReader subclass of XmlReader, which can be used to read data from files or remote REST URLs.

    public XmlReader GetXml() {
    
        string url = "http://somehost/graph.asmx?id=cities";
    
        XmlReader graphReader = new XmlTextReader(url);
    
        return graphReader;
    
    }
    
    	

GetXml() #2: Database

Another data source might be a database. Microsoft has placed XML at the center of its ADO.NET data access library with the DataSet, which is simultaneously a disconnected set of relational data and a XML document.

  • The first step to get the XML is to create a DataAdapter, which is a bridge between the database and the DataSet. Here we construct a SqlDataAdapter, a SQL Server specific DataAdapter, with a connection string and a SQL statement.

    public XmlReader GetXml() {
    
        string sql = "SELECT * FROM [Category Sales for 1997]";
    
        string conn = "Data Source=(local);Database=Northwind;" +
    
                      "Integrated Security=SSPI;"; 
    
        SqlDataAdapter da = new SqlDataAdapter(sql,conn);
    
    	
  • Next we create a new empty DataSet and fill it with data using the DataAdapter's Fill method.

    
        DataSet ds = new DataSet();
    
        da.Fill(ds);
    
    	
  • The default schema of the dataset does not match bar graph XML. We could use XSLT to transform it, but in some cases we can do this more efficiently by directly modifying the DataSet.

    
        ds.DataSetName = "BarGraph";
    
        ds.Namespace = "http://example.org/bargraph";
    
        ds.Tables[0].TableName="Bar";
    
        ds.Tables[0].Columns[0].ColumnName="Title";
    
        ds.Tables[0].Columns[1].ColumnName="Value";
    
    	
  • There are a number of ways to obtain the DataSet's XML, including the WriteXml and GetXml methods. Here we will use the XmlDataDocument class to expose the DataSet as a DOM object, and then create an XmlNodeReader, which is a XmlReader that reads the contents of a DOM object.

    
        XmlDocument xmlDoc = new XmlDataDocument(ds);
    
        XmlReader graphReader = new XmlNodeReader(xmlDoc);
    
        return graphReader;
    
        }
    
    	

GetXml() #3: User Form

The previous example showed how a DataSet object can be used as a bridge between a database and XML. The DataSet can also be used as a bridge between controls in a Windows form and XML. The complete source code for this example is at form2graph.zip.

  • In our form's constructor, we'll create a DataSet and give it a structure by reading in our BarGraph schema using the ReadXmlSchema method.

    
    private DataSet ds;
    
    private DataGrid grid;
    
    
    
    public BarGraphForm() {
    
    	
    
        InitializeComponent();
    
    
    
        // Create a dataset based on the schema
    
        ds = new DataSet();
    
        ds.ReadXmlSchema("BarGraph.xsd");
    
    	
  • Then we'll bind the DataSet to a DataGrid control in our form. The DataGrid will automatically create columns that conform to the schema in the DataSet.

    
        // Bind the dataset to a DataGrid control
    
        grid.SetDataBinding(ds,"Bar");
    
        }
    
    	
  • Now when the form is displayed, the DataGrid will conform to the schema, and the user can add and edit data.

    A DataGrid bound to the Bar Graph XML schema

  • Once the user is finished, we will use the WriteXml method to extract the XML from the dataset.

    
    public XmlReader GetXml() {
    
        Stream streamXml = new MemoryStream();
    
        ds.WriteXml(streamXml);
    
        streamXml.Position = 0;
    
        XmlReader graphReader = new XmlTextReader(streamXml);
    
        return graphReader;
    
        }
    
    	

Application Output

Each of the following examples will perform some action using an XmlReader containing bar graph XML. Naturally the choice of our input source is unimportant, since all the sources produce XmlReaders conforming to the same schema.

OutputAction() #1: An SVG Image

Another familiar benefit of XML interfaces is that an SVG or XHTML presentation is often only a transformation away.

  • To perform the XSLT transform we load our XmlReader into a XPathDocument,

    
    public void OutputAction(XmlReader graphReader) {
    
        XPathDocument graphDoc = new XPathDocument(graphReader);
    
    	
  • set up the XslTransform object,

    
        XslTransform xslt = new XslTransform();
    
        xslt.Load("svgGraph.xsl");
    
    	
  • and write the output to a file. I am using the older .NET 1.0 syntax; In .NET 1.1's the Transform method has changed slightly.

    
        Stream outStream = new FileStream("graph.svg",FileMode.Create);
    
        xslt.Transform(graphDoc, null, outStream);
    
        outStream.Close();
    
        }	
    
    	

OutputAction() #2: A PNG Bitmap

If some sort of human analysis is required, it's more appropriate to work with a graph than XML. This is how we'll convert our bar graph XML into a PNG image file. The complete source code for this example is in graph2png.zip.

  • The OutputAction method will simply call the static XmlToPng method with the XmlReader. This will write the image onto an output stream.

    
    public void OutputAction(XmlReader graphReader) {
    
    
    
        Stream outStream = new FileStream("graph.png",FileMode.Create);
    
        BarGraphImage.XmlToPng(graphReader,outStream);
    
        outStream.Close();
    
        
    
        }
    
    	

    A PNG image generated from Bar Graph XML

  • The static XmlToPng method creates the image by deserializing the XML into an object graph. We'll use the XmlSerializer class to create an instance of the custom BarGraphImage class and then call its WriteImage method to create the PNG image.

    
    public static void XmlToPng(XmlReader reader, Stream output) {
    
    
    
        // Create a XmlSerializer that will create an 
    
        // object from the bar graph
    
        string ns = "http://example.org/bargraph";
    
        XmlSerializer serializer = new XmlSerializer(typeof(BarGraphImage),ns);
    
    		
    
        // Create a new instance of the BarGraph class from the XML
    
        BarGraphImage graphImage = (BarGraphImage)serializer.Deserialize(reader);
    
    		
    
        // Write The Image
    
        graphImage.WriteImage(output,ImageFormat.Png);
    
    
    
        }
    
    	
  • To map the Bar Graph XML to our class, we use attributes (the stuff in the square brackets) to provide hints. For example, the BarGraphImage class will correspond to our top level <BarGraph> element. A useful feature of XmlSerializer is that it will automatically size and populate arrays, so we can leave out helper methods like AddBar(). This can save you a lot of code, especially when instantiating large and deep object graphs.

    [XmlRoot("BarGraph")]
    
    public class BarGraphImage {
    
    
    
        public BarGraphImage() {}
    
    
    
        private Bar[] _bars;
    
    	
    
        [XmlElement("Bar")]
    
        public Bar[] Bars {
    
            get {return _bars;}
    
            set {_bars = value;}
    
            }
    
    
    
        public void WriteImage(Stream output, ImageFormat format) {
    
            ....
    
            }
    
    
    
        public static void XmlToPng(XmlReader reader, Stream output) {
    
            ....
    
            }
    
        }
    
    
    
    public class Bar {
    
    
    
        public Bar() {}
    
    		
    
        public string Title;
    
        public double Value;
    
    
    
        }
    
    	

OutputAction() #3: A Network XML Consumer

Perhaps our application is just part of a distributed pipeline or needs to pass the bar graph XML into some sort of content management store. Here we'll pass the bar graph XML on to a protected REST webservice.

  • Create a web request, and supply the Windows credentials in which the application is running,

    
    public void OutputActions(XmlReader graphReader) {
    
    
    
        string url = "http://somehost/pid/graphRepository.ashx";
    
    
    
        WebRequest uploader = WebRequest.Create(url);
    
        uploader.Method="POST";
    
    	
  • Supply the appropriate credentials,

    
        uploader.Credentials = CredentialCache.DefaultCredentials;
    
    	
  • And use the WriteNode() method of XmlWriter to write the contents of the XmlReader to the request stream.

    
        Stream upStream = uploader.GetRequestStream();
    
    
    
        XmlWriter upWriter = new XmlTextWriter(upStream,Encoding.UTF8);
    
        upWriter.WriteNode(graphReader,true);
    
        upWriter.Close();
    
    		
    
        upStream.Close();
    
    
    
        uploader.GetResponse();
    
    
    
        }
    
    

Conclusion

This architecture is not for everybody. For some, the approach will place unwelcome constraints on W3C XML Schema and class design. Also throughput and security considerations may make it inappropriate for some applications.

However, for applications that live in dynamic environments, you achieve true loose coupling of components with all the flexibility of XML. An XmlReader producing and consuming components of your application can be readily swapped out, improved, or reused by other applications or pipelines. It also reduces the amount of wiring required to tie components together.

More tools are on the way for XML-centric applications in the upcoming versions of .NET and Windows, named Whidbey and Longhorn respectively. .Net brings XQuery Support and the new XmlReader and XmlWriter implementations ObjectReader and ObjectWriter, providing additional support for serializing and deserializing objects. Longhorn's XAML, which uses XML to define windows UI elements, can make interface elements a transformation away from your data or schema XML.