XML Support in .NET

Jul 29, 12:00 am

Article Author: Dan Wahlin
.NET 3.5 Books

This article is part of Dan Wahlin’s ‘XML Support in ASP.NET’ Guru week

It could be argued that Extensible Markup Language (XML) is the lingua franca of data exchange, given all of the attention it has had in the past few years. Although there are many other viable alternatives, including Electronic Data Interchange (EDI), flat-files, and a variety of binary formats, XML offers an extensible format that allows data to be exchanged in a flexible yet human-readable manner. XML has much more going for it, aside from its flexible and easy to master syntax. It is also supported by a multitude of parsers (the "brains" that can read an XML document), has "built-in" validation, transformation, and querying capabilities, and can be used with virtually any programming language and platform. By using XML you can focus on working with data rather than on labor-intensive tasks such as writing custom file parsers, validation, or data transformation routines.

Leading development platforms such as Sun’s J2EE, or open source projects, such as PHP, support many of the features that XML provides. However, Microsoft’s .NET platform leads the pack as far as XML support and integration goes. Rather than being an afterthought, XML integration was built-into .NET from day 1, which is evident as you look through the robust set of XML classes that are native to the platform (as opposed to being add-ons).

This article is the first in a series that will provide an introductory look at XML and demonstrate different ways that the .NET platform enables developers to leverage the power of XML. In this initial article you’ll be provided with an overview of what XML is, what it is good for, and some of the different ways the .NET platform supports XML. The articles that follow will delve into development specifics and code and demonstrate tasks such as how to programmatically validate XML documents against DTDs and schemas, work with different parsing APIs, convert relational data to XML, and transform XML into other formats.

Let’s start by reviewing some of the XML fundamentals and a few of the different ways XML is useful in applications.

What’s a Well-Formed XML Document?

When working with XML you’ll often hear two terms that are extremely important to understand. The first is well-formed which means that a given XML document adheres to a set of rules defined in the W3C’s XML specification located at http://www.w3.org/TR/2000/REC-xml-20001006. A few of the main rules contained in the specification are shown below:

All tags (known as "elements" in XML) must be closed:


  • Correct: <name>John Doe</name>

  • Incorrect: <name>John Doe

All elements are case-sensitive:


  • Correct: <name>John Doe</name>

  • Incorrect: <name>John Doe</Name>

All attributes must be quoted:


  • Correct: <customer id="ab104" />

  • Incorrect: <customer id=ab104 />

Each XML document must contain a single root tag:


  • In HTML documents the root tag is <html>. This tag encapsulates all other tags in the document. Although you can name the root tag anything you want in your custom XML documents, you must wrap all other tags in the document with a single tag as shown below with the <customers> tag:



<customers>
    <customer>
          <name>John Doe</name>
    </customer>
</customers>

No attribute may appear more than once on a given element:

  • Correct: <customer id="ab104" />
  • Incorrect: <customer id="ab104" id="ab102" />

The other important term used in XML is valid. A valid XML document adheres to a set of definitions contained in either a Document Type Definition (DTD) or XML Schema. These documents specify what element names can appear in the XML document, what order the elements can appear in, attributes that elements can have, plus much more. It is possible for an XML document to be well-formed but not valid. Although a discussion of validating XML is beyond the scope of this article, you’ll be provided with a detailed look at validating XML documents against DTDs or schemas in tomorrow’s article.

Now that some of the basic terms have been explained, let’s discuss where XML is actually useful when creating .NET applications.

Where is XML Useful?

The pace technology moves these days is often staggering. Some technologies are proclaimed to be the next "big thing" but then quickly fade away due to either lack of vendor/developer support or a failure to deliver on specific promises. XML has certainly been marketed as being the next "big thing", but it can back up many of the claims and has proven to be a serious contender in the crowded world of technologies.

There have been many articles written about XML that explain how to use it in applications, however, few explain why you would want to use XML and where it is most applicable. "Where is XML actually useful?" is a frequent question I hear asked in training classes and on consulting projects. I like to answer this question by giving a simple example and by listing some of the ways XML can be put to good use in .NET applications. Let’s first look at an example that compares flat-files to XML documents.

Flat-files work well for data exchange between different companies and are still widely used, however, they only delimit data with characters (commas, pipes, etc.) or by assigning fixed-length fields. An example of a potential row of data in a comma-delimited flat-file is shown in Listing 1 below.


John, Doe, 195, 03/12/2000,13365849,03/22/2000, 4547, Jeff

Listing 1. A simple comma-delimited flat-file record



Although this row may have an associated header row that explains the different columns in each row, the data in an individual row isn’t actually described. This makes it difficult if you want to identify what a specific flat-file data item represents in a given row. This is especially true if many rows are involved. To programmatically access the data in this type of flat-file, you normally have to either write a custom parsing mechanism or use an existing product that maps the file to a data source.

There’s a problem with this approach. What if the program generating the data switches a few of the fields around in one or more rows? For example, if a date gets switched with a customer name the file is now corrupted and importing it into a database will likely fail. Flat-file fields are normally accessed by position (position 0 is John, position 1 is Doe, etc. in Listing 1) and therefore don’t handle switches in the data position gracefully. Also, how do you validate a flat-file to ensure that it contains the proper information and that the individual data fields are typed correctly? Unless you have a third-party product you normally have to write a custom validation mechanism, which takes both time and money.

Let’s now compare the flat-file approach to XML. XML is a meta-data language (it provides data about data) that offers a much more flexible approach to exchanging the data shown in Listing 1. For example, the data might look like the following when marked-up as XML:


    <?xml version="1.0"?>
    <orders>
         <order>
                <customer customerID="13365849">
                       <fname>John</fname> 
                       <lname>Doe</lname> 
                </customer> 
                <partNum>195</partNum> 
                <orderDate>03/12/2000</orderDate>
                <shipDate>03/22/2000</shipDate> 
                <orderNum>4547</orderNum> 
                <salesRep>Jeff</salesRep>
         </order>
    </orders>

Listing 2. Converting a flat-file record to XML



Although certainly more verbose than the flat-file version (it can be compressed quite small, however), each piece of data is now described by using elements and/or attributes. If the <fname> tag is switched with the <orderDate> tag the application can still handle the change gracefully and in a flexible manner because XML applications can access elements by both position and name. This is much like using SQL with a database, since fields in a table are normally accessed by name. As an added bonus, XML provides the benefits of having existing parsers, validation schemes, and transformation technologies so you don’t have to worry about more mundane tasks, such as parsing through the file to extract data.

Aside from upgrading data from flat-files to XML documents, XML has many other excellent ways it can be used. The following 5 topics provide a good glimpse into a few of these ways. A brief explanation of each topic (with links to examples) follows.

5 Uses for XML

1. Data Exchange


2. Web Services


3. Content Management


4. Web Integration


5. Configuration

Data Exchange between companies and individuals has been, and always will be, crucial in order for business processes to succeed. As shown earlier, by using XML data exchange can be made more flexible and doesn’t have to involve writing a custom parser or data validation routine in order to receive or consume data. You will find several examples of using .NET for data exchange at the following URLs:

http://www.xmlforasp.net/codeSection.aspx?csID=11


http://www.xmlforasp.net/codeSection.aspx?csID=12


http://www.xmlforasp.net/codeSection.aspx?csID=13


http://www.xmlforasp.net/codeSection.aspx?csID=37

Web Services are the "hot" topics these days since they offer a way for distributed systems to communicate in a language and platform neutral manner. Web Services involve sending XML messages called SOAP envelopes between systems. Please see Developing Web Services with ASP.NET – An Introduction and Creating web services using Visual Studio.Net for more information on this subject.

Content Management is another important topic as corporations strive to generate different document formats from a single set of data. Since XML’s main purpose is to describe data, it presents an excellent way to store document data since it can be transformed into many other formats from HTML to PDF to Excel through technologies such as Extensible Stylesheet Language Transformations (XSLT) and XSL/FO. Examples of using the .NET platform for content management can be found at the following URLs:

http://www.xmlforasp.net/codeSection.aspx?csID=14


http://www.xmlforasp.net/codeSection.aspx?csID=41

Web Integration is the process of tying XML data to a Website. Once you have a solid understanding of the different ways to work with XML (which will be provided in this and future articles), you can integrate news, weather, sports, market, or any other type of data into your Website. You can also use XML to create Vector Graphic graphs or other images using technologies such as Scalable Vector Graphics (SVG). Examples of using XML for Web integration can be found at the following URLs:

http://www.xmlforasp.net/codeSection.aspx?csID=9


http://www.xmlforasp.net/codeSection.aspx?csID=39

Finally, XML can play a key role in application configuration. This is already seen in the web.config file associated with ASP.NET applications. Although there are several other configuration storage formats, such as INI files that can be used, XML offers many time-saving features that make it a great mechanism for storing application configuration information. This could range from storing something as simple as email addresses to use when sending out feedback responses, to storing stored procedure parameter data.

By storing your application configuration data within an XML file you can make changes to the file without being forced to recompile the application. Plus, there’s the added bonus of not having to learn a new parsing API, such as for an INI parser. An example of using XML and .NET for application configuration can be found at the following URLs:

http://www.xmlforasp.net/codeSection.aspx?csID=10


http://www.xmlforasp.net/codeSection.aspx?csID=28

Now that you’ve seen some of the different ways XML can be used, let’s examine some of the ways the .NET platform supports XML.

XML Support in .NET

Earlier in the article I mentioned that the .NET platform has XML integrated directly into its core. Exactly what type of XML support does the .NET platform provide though? The following list answers this question:

Figure 1 shows an overview of where XML fits into the .NET platform:

Figure 1. XML is one piece of the .NET platform.



Much of the functionality listed above is found in the System.Xml namespace, which lives within the System.Xml.dll assembly. Other namespaces contain related XML classes as well such as System.Data, which contains the DataSet class, and System.Web.Services, which contains the Web Service related classes. Although much more detail will be provided in the articles that follow in this series, let’s take a quick look at some of the different support .NET offers for XML.

XML Parsing APIs

Within the System.Xml namespaces you’ll find several different XML reader and writer classes. Accessing the different classes is as easy as importing the namespace into your .NET programs. Two main APIs exist for parsing XML, including a forward-only, non-cached mechanism found in the XmlTextReader class and a Document Object Model (DOM) specific class named XmlDocument. While the DOM is used by many other development platforms, the API offered by the XmlTextReader is new and functions in a different manner from the typical Simple API for XML (SAX) API used by the majority of other platforms, as you’ll see a little later.

Listing 3 demonstrates how to read through an XML document using the XmlTextReader and write out the node names and values it finds to a browser.


<%@ Page Language="C#" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Text" %>
<script language="C#" runat="server"> private void Page_Load(Object sender, EventArgs e){ StringBuilder sb = new StringBuilder(); XmlTextReader reader = new XmlTextReader(Server.MapPath("listing1.xml")); while (reader.Read()) { sb.Append("<b>NodeType: </b>" + reader.NodeType + "<br />"); sb.Append("<b>Name: </b>" + reader.Name + "<br />"); sb.Append("<b>Value: </b>" + reader.Value + "<br /><p />"); } //Close our XmlTextReader reader.Close(); output.Text = sb.ToString(); }
</script>
<html> <body> <font size="5" color="#02027a"> Parsing XML with the XmlTextReader Class </font> <p /> <asp:label ID="output" Runat="server" /> </body>
</html>

Listing 3. Parsing XML with the XmlTextReader



The XmlTextReader converts an XML document into a stream of "XML tokens" that can be pulled as desired within the while loop – this technique is very memory efficient and fast. It is quite different from the push model exposed by traditional Simple API for XML (SAX) parsers found in the majority of other development platforms. The XmlTextReader provides a read-only API, which is quite useful when XML data must be parsed and imported into a database. It is also used in conjunction with a class called the XmlValidatingReader to validate XML documents against DTDs and schemas. More details on the XmlValidatingReader will be discussed in tomorrow’s article.

Aside from the read-only XmlTextReader class, XML documents can also be loaded into memory. Listing 4 demonstrates how to load XML into a DOM structure and then use XPath and different DOM classes to find and edit an attribute named customerID.


<%@ Page Language="C#" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Text" %>
<script language="C#" runat="server"> private void Page_Load(Object sender, EventArgs e){ XmlDocument doc = new XmlDocument(); doc.Load(Server.MapPath("listing2.xml")); //Convert the structure in memory to a string and //assign it to the start label Text property start.Text = doc.OuterXml; XmlElement root = doc.DocumentElement; //Use XPath to select a specific node XmlElement elem = (XmlElement)root.SelectSingleNode("order/customer"); if (elem != null) { //Set the attribute named customerID to "45" elem.SetAttribute("customerID","45"); } end.Text = doc.OuterXml; }
</script>
<html> <body> <form runat="server"> <font size="5" color="#02027a"> Parsing XML with the XmlDocument Class </font> <p /> <b>Initial XML:</b><br /> <asp:Textbox Runat="server" ID="start" Runat="server" TextMode="MultiLine" Columns="70" Rows="10" /> <p /> <b>Edited XML:</b><br /> <asp:Textbox Runat="server" ID="end" Runat="server" TextMode="MultiLine" Columns="70" Rows="10" /> </form> </body>
</html>

Listing 4. Parsing and editing XML with the XmlDocument class



As shown, the code in Listing 4 loads the XML document into memory using the XmlDocument class, locates the customer element using XPath, and then changes its attribute named customerID to a value of 45. The edited and unedited versions of the XML document are then written out to the browser so that the changes can be seen. Use of the DOM should be restricted due to its reliance on memory; however, it is generally appropriate when an XML document must be edited.

Aside from using the XmlTextReader and XmlDocument classes, you can also use a third type of API, referred to as XML Serialization, to parse and manipulate XML. XML serialization is the process of converting an existing object into an XML document while deserialization converts the XML document back into a useable object. Using serialization techniques, XML documents can be manipulated in a more object-oriented manner, which allows programmers to focus on programming instead of working with XML, since much of the editing/reading of the XML document is shielded from them and encapsulated within objects. Listing 5 shows an example of using XML serialization.


private void btnSerialize_Click(object sender, 
    System.EventArgs e) {
    Orders orders = new Orders();

//Fill Customer Details orders.OrderDetails.CustomerDetails.FirstName = "John"; orders.OrderDetails.CustomerDetails.LastName = "Doe"; orders.OrderDetails.CustomerDetails.CustomerID = 45; //Fill order details orders.OrderDetails.OrderDate = DateTime.Now; orders.OrderDetails.ShipDate = DateTime.Now.AddDays(2); orders.OrderDetails.OrderNum = 1; orders.OrderDetails.PartNum = 5; orders.OrderDetails.SalesRep = "Jeff"; //Serialize the Orders object (and sub objects) //to XML StreamWriter writer = null; XmlSerializer serializer; try { writer = File.CreateText(Server.MapPath("Orders.xml")); serializer = new XmlSerializer(typeof(Orders)); serializer.Serialize(writer,orders); } finally { if (writer != null) writer.Close(); } Response.Write("Orders Class serialized to Orders.xml file"); }

Listing 5. Serializing an object to XML.



This example creates an instance of a class named Orders, fills it with data, and serializes the object to XML using the XmlSerializer class found in the System.Xml.Serialization namespace. The serialized XML document will look similar to the one shown back in Listing 1 and can be deserialized back into an Orders object by using the XmlSerializer ‘s Deserialize() method as shown in Listing 6.


private void btnDeserialize_Click(object sender, 
    System.EventArgs e) {
    string filePath = Server.MapPath("Orders.xml");
    StreamReader reader = null;
    XmlSerializer serializer;
    Orders orders;
    try {
        reader = new StreamReader(filePath);
        serializer = new XmlSerializer(typeof(Orders));
        orders = (Orders)serializer.Deserialize(reader);
        Response.Write("CustomerID attribute = " + 
        orders.OrderDetails.CustomerDetails.CustomerID.ToString());
    }
    finally {
        reader.Close();
    }
}

Listing 6. Deserializing an XML document.



XML Support in ADO.NET

Although working directly with XML documents can offer many benefits, the ability to work with both relational and XML data in a seamless manner can be crucial to business applications. Fortunately, the .NET platform makes working with both XML and relational data views a snap through its ADO.NET data access technology.

At the core of ADO.NET is the DataSet class, which is located in the System.Data namespace. The DataSet class provides the ability to work with XML and relational data with little effort and makes moving XML into databases less work intensive. Underneath the covers, DataSets rely on a disconnected, message-like architecture that is based upon XML and XML schemas. This architecture allows data within DataSets to be passed through firewalls and exposed via Web Services.

Although this section cannot possible describe all of the XML support that ADO.NET has to offer, let’s examine a simple example of how XML and relational data can work together. Listing 7 demonstrates how to generate XML from relational data using the DataSet class’s GetXml() method. The Customers and Orders tables in the Northwind database are queried and a parent/child nesting is created using the DataRelation class.


public void Page_Load(Object Src, EventArgs E) { 
    string SqlconnStr = 
      "server=localhost;uid=sa;pwd=;database=Northwind";
    string customersSql = "SELECT * FROM Customers " + 
      "WHERE CustomerID = ‘ALFKI’";
    string ordersSql = "SELECT * FROM Orders " + 
      "WHERE CustomerID = ‘ALFKI’";

DataSet dsTables = new DataSet(); SqlConnection dataConn = new SqlConnection(SqlconnStr); SqlDataAdapter dsCmdCustomers = new SqlDataAdapter(customersSql,dataConn); SqlDataAdapter dsCmdOrders = new SqlDataAdapter(ordersSql,dataConn); //Fill DataSet dsCmdCustomers.Fill(dsTables,"Customers"); dsCmdOrders.Fill(dsTables,"Orders"); //Create relationships and nest XML output DataRelation rel = new DataRelation("CustomerOrders", dsTables.Tables["Customers"].Columns["CustomerId"], dsTables.Tables["Orders"].Columns["CustomerId"]); rel.Nested = true; dsTables.Relations.Add(rel); //Write out nested XML to textbox txtXml.Text = dsTables.GetXml(); }

Listing 7. Using ADO.NET classes, relational data can easily be converted to XML.



A portion of the XML generated from running Listing 7 is shown below:


<NewDataSet>
  <Customers>
    <CustomerID>ALFKI</CustomerID>
    <CompanyName>Alfreds Futterkiste</CompanyName>
    <ContactName>Maria Anders</ContactName>
    <ContactTitle>Sales Representative</ContactTitle>
    <Address>Obere Str. 57</Address>
    <City>Berlin</City>
    <PostalCode>12209</PostalCode>
    <Country>Germany</Country>
    <Phone>030-0074321</Phone>
    <Fax>030-0076545</Fax>
    <Orders>
      <OrderID>10643</OrderID>
      <CustomerID>ALFKI</CustomerID>
      <EmployeeID>6</EmployeeID>
      <OrderDate>1997-08-25T00:00:00.0000000-07:00</OrderDate>
      <RequiredDate>1997-09-22T00:00:00.0000000-07:00</RequiredDate>
      <ShippedDate>1997-09-02T00:00:00.0000000-07:00</ShippedDate>
      <ShipVia>1</ShipVia>
      <Freight>29.46</Freight>
      <ShipName>Alfreds Futterkiste</ShipName>
      <ShipAddress>Obere Str. 57</ShipAddress>
      <ShipCity>Berlin</ShipCity>
      <ShipPostalCode>12209</ShipPostalCode>
      <ShipCountry>Germany</ShipCountry>
    </Orders>
    <!— More orders would follow —>
  </Customers>
</NewDataSet>

In Thursday’s article I’ll explore some of the other XML-related features found in ADO.NET.

Web Services

Web Services allow distributed systems to communicate in a platform and language neutral manner. Although I won’t be covering Web Services in this article series, it’s important that they are mentioned since they play an important role in the .NET platform. Although many other platforms now support Web Services, most do so through add-on objects and don’t support technologies such as SOAP (Simple Object Access Protocol) and WSDL (Web Service Description Language) natively. .NET on the other-hand supports these and other related technologies directly, allowing you to leverage Web Services immediately by simply installing the .NET framework. Much of the Web Service functionality in .NET can be found in the System.Web.Services namespace.

Web Services work by sending XML messages between systems which allows Java, C#, PHP, Perl, and many other languages to exchange data and communicate without being restricted to more tightly-bound object models such as DCOM, CORBA, or Java RMI. Although many of the technologies used in Web Services are still being finalized, Web Services appear to have a bright future ahead of them and .NET is positioned well in this area of technology.

XSLT Support in .NET

Although the .NET platform contains many different type of controls that can be used to present data in a simple, yet extensible way, there are still many cases where XSLT may play a key role, especially when data needs to be presented in a browser. The .NET platform contains a namespace dedicated specifically to XSLT called System.Xml.Xsl that allows for XSLT transformations. The centerpiece of this namespace is the XslTransform class that can be used to load the XSLT stylesheet, pass parameters to the stylesheet (as necessary), and then transform the source XML document.

Listing 8 demonstrates one way XSLT can be used to transform data into a master/detail report. The code starts by loading relational data into a DataSet, which is then converted into XML using the XmlDataDocument class. The resulting DOM structure is then fed to the XslTransform class, which performs the XSLT transformation.


public class SqlConnect {
    public DataSet ReturnDataSet(string dbConnectString,
        string table1,string table2) {
        DataSet dsTables = new DataSet();
        SqlConnection dataConn = 
          new SqlConnection(dbConnectString);
        SqlDataAdapter dsCmdCustomers = 
          new SqlDataAdapter(table1,dataConn);
        SqlDataAdapter dsCmdOrders = 
          new SqlDataAdapter(table2,dataConn);

//Fill the DataSet with the two tables dsCmdCustomers.Fill(dsTables,"Customers"); dsCmdOrders.Fill(dsTables,"Orders"); return(dsTables); } }

public void Page_Load(Object Src, EventArgs E) { string SqlconnStr = "server=localhost;uid=sa;pwd=;database=Northwind"; string customerSql = "SELECT * FROM Customers WHERE " + "CustomerID LIKE ‘C%’" + " ORDER BY ContactName"; string ordersSql = "SELECT * FROM Orders WHERE " + "CustomerID LIKE ‘C%’"; StringBuilder sb = new StringBuilder(); StringWriter sw = new StringWriter(sb); XmlTextWriter writer = new XmlTextWriter(sw); //Open Sql Connection SqlConnect Sqlconn = new SqlConnect(); DataSet ds = Sqlconn.ReturnDataSet(SqlconnStr,customerSql, ordersSql); //Create a nested relationship between the tables DataRelation rel = new DataRelation("CustomerOrders", ds.Tables["Customers"].Columns["CustomerId"], ds.Tables["Orders"].Columns["CustomerId"]); rel.Nested = true; ds.Relations.Add(rel);

//Convert the relational data to XML and transform it //using XSLT XslTransform xsl = new XslTransform(); xsl.Load(Server.MapPath("customerOrders.xslt")); xsl.Transform(new XmlDataDocument(ds),null,writer); content.InnerHtml = sb.ToString(); }

Listing 8. Relational data can easily be transformed into other formats using ADO.NET objects along with XSLT



Figure 2 shows the output generated by running Listing 8:

Figure 2. Generating a master/detail report using ADO.NET and XSLT



Conclusion

XML provides an excellent mechanism for performing a multitude of tasks from data exchange to storing application configuration information. With the introduction of the .NET platform, developers have a powerful development environment that provides a robust set of classes for parsing, editing, and transforming XML documents.

Although this article has only skimmed the surface of the XML support available in .NET, you’ve seen several of the main XML classes that can be used to work with XML. Future articles in this series will provide a more in-depth look at how .NET supports XML. In the next article I’ll explore the features that XML schemas offer and demonstrate how to programmatically validate XML documents against DTDs and schemas.

Please rate this article using the form below. By telling us what you like and dislike about it we can tailor our content to meet your needs.






















Article Information
Author Dan Wahlin
Chief Technical Editor John R. Chapman
Project Manager Helen Cuthill
Reviewers Andy Krowczyk, Saurabh Nandu


If you have any questions or comments about this article, please contact the technical editor.









Professional ASP.NET 1.0 XML with C#
Professional ASP.NET 1.0 XML with C# is aimed at the experienced web developer who already has a grasp of ASP.NET and a basic familiarity with XML and related technologies. The book is written in C# and aims to augment the skill set of those seeking to progress their .NET experience.


As ASP.NET becomes more widely adopted there is an increasing need for content that goes beyond broad overviews of this exciting new technology. This book concentrates on describing how XML can be effectively used within ASP.NET applications. Coverage includes discussion of where and when to use XML, detailed discussion of the System.XML namespace, ADO.NET as it relates to ASP.NET, SQL Server 2000 and SQLXML managed classes, and XSLT. Furthermore the book specifically spends time highlighting new developments in XML related standards and technologies, and performance issues that the advanced ASP.NET developer should be aware of.


Founders at Work

Commenting is closed for this article.