Extending T4: <#@ xsd #> directive


XML strikes a nice balance between human and machine readability. On one hand, it is structured, making it easy to parse and manipulate programmatically. On the other hand, XML is text-based and easy enough for developers to write by hand, especially with IntelliSense support provided by Visual Studio. This makes XML a very attractive file format for storing models or metadata information. Many of the modern modeling tools built on the Visual Studio platform like LINQ to SQL and ADO.NET Entity Framework use XML to store their metadata.

There are several different ways to extract information stored in an XML file. XmlDocument supports the traditional approach of accessing XML DOM, XPathDocument provides a more targeted approach based on XPath queries and LINQ to XML provides a LINQ-flavored combination. While these APIs are flexible and powerful enough to handle virtually any XML, they are also weakly-typed and complex to use. If a particular XML file has a predefined schema, the most productive way to extract information from XML may be to create strongly-typed .NET classes and use XML serialization to convert XML documents into .NET objects.

The .NET framework SDK includes a tool called xsd.exe that can generate strongly-typed .NET classes based on a given XML schema. While it is certainly possible to use code generated by xsd.exe to develop a T4 code generation template, this requires an extra step, creates an extra file to maintain and doesn’t support some of the more complex schemas, such as the schema used by ADO.NET Entity Framework. To eliminate these limitations, T4 Toolbox now includes the <#@ xsd #> custom directive processor.

Syntax

<#@ xsd 
    processor="T4Toolbox.XsdProcessor"
    file="" #>

processor

The processor parameter specifies name of the custom processor T4 engine will use to process this directive. This parameter is required and must be set to "T4Toolbox.XsdProcessor".

file

The file parameter specifies the XML schema (.xsd) file which will be converted to strongly-typed .NET class definitions. This parameter can contain an absolute file path or a relative file path (relative to the location of the template file). File path can also contain environment variables, such as %ProgramFiles%, which will be replaced with actual values when the template is compiled.

Example

The following T4 code generation template produces a text file with information about all classes and their properties defined in a LINQ to SQL model called Northwind.dbml, located in the same folder with the template.

<#@ template language="C#" hostspecific="True" #>
<#@ output extension="txt" #>
<#@ xsd processor="T4Toolbox.XsdProcessor"
    file="%VS90COMNTOOLS%\..\..\Xml\Schemas\DbmlSchema.xsd" #>
<#
    string northwindDbml = Host.ResolvePath("Northwind.dbml");
    Database northwind = Database.Load(northwindDbml);
    foreach (Table table in northwind.Table)
    {
#>
Class <#= table.Type.Name #>
<#
        foreach (object item in table.Type.Items)
        {
            Column column = item as Column;
            if (column != null)
            {
#>
    Property <#= column.Name #>: <#= column.Type #>
<#
            }
        }
    }
#>

In this example, the xsd directive is used to generate .NET classes for XML schema defined in DbmlSchema.xsd installed in the Xml\Schemas directory of Visual Studio 2008. The code calls static Load method of the generated Database class to deserialize XML stored in Northwind.dbml file into .NET objects and then iterates through all tables and columns defined in this file.

Note that template directive sets hostspecific parameter to true which allows this template to call ResolvePath method of the Host to determine absolute path to the Northwind.dbml file.

Under the hood

XsdProcessor uses XML serialization APIs to load schema definition from the specified .xsd file and generate .NET classes nested inside of the GeneratedTextTransformation, making them available to use in template code. Let’s consider a simple, complete example.

Here is a schema file that defines a complex type called TestClass with a single element called TestProperty of type int.

<?xml version=1.0encoding=utf-8?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="TestClass" nillable="true" type="TestClass" />
  <xs:complexType name="TestClass">
    <xs:sequence>
      <xs:element name="TestProperty" type="xs:int"
                  minOccurs="1" maxOccurs="1" />
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Here is a sample XML file that conforms to this schema.

<?xml version="1.0" encoding="utf-8" ?>
<TestClass>
  <TestProperty>7</TestProperty>
</TestClass>

The following template uses the xsd directive to import this definition and access information from external file TestModel.xml using a strongly-typed testClass object.

<#@ template language="C#" debug="True" #>
<#@ xsd processor="T4Toolbox.XsdProcessor" file="TestSchema.xsd" #>
<#
    TestClass testClass = TestClass.Load(@"C:\Path\TestModel.xml");
    this.Write(testClass.TestProperty.ToString());
#>

Here is what the compiled template code looks like after some clean up and formatting to improve readability. Notice that GeneratedTextTransformation contains a nested class called TestClass. This class is marked with XML serialization attributes, which help XmlSerializer to convert XML to .NET objects. This class also includes a static Load method, which was used in the template to load a TestClass object from TestModel.xml.

namespace Microsoft.VisualStudio.TextTemplating6100B59E730EBC2ACF815817DE31653D
{
    using System;
    using System.IO;
    using System.Xml.Schema;
    using System.Xml.Serialization;
    using Microsoft.VisualStudio.TextTemplating;

    public class GeneratedTextTransformation : TextTransformation
    {
        public override string TransformText()
        {
            TestClass testClass = TestClass.Load(@"C:\Path\TestModel.xml");
            this.Write(testClass.TestProperty);
            return this.GenerationEnvironment.ToString();
        }

        [XmlRootAttribute(Namespace = "", IsNullable = true)]
        public partial class TestClass
        {

            [XmlElementAttribute(Form = XmlSchemaForm.Unqualified)]
            public int TestProperty;

            public static TestClass Load(string filePath)
            {
                if (filePath == null)
                    throw new ArgumentNullException("filePath");

                XmlSerializer serializer = new XmlSerializer(typeof(TestClass));
                FileStream stream = null;
                try
                {
                    stream = new FileStream(filePath, FileMode.Open,
                       FileAccess.Read, FileShare.Read);
                    return (TestClass)serializer.Deserialize(stream);
                }
                finally
                {
                    if (stream != null)
                        stream.Dispose();
                }
            }
        }
    }
}

Given the TestModel.xml file shown above, this template generates the following output.

7

Download

  • Click here to download T4 Toolbox, which includes the <#@ xsd #> directive processor.
  • Click here to download sample source code used in this article.

About T4

T4 (Text Template Transformation Toolkit) is a template-based code generation engine. It is available in Visual Studio 2008 and as a download in DSL and GAT toolkits for Visual Studio 2005. T4 engine allows you to use ASP.NET-like template syntax to generate C#, T-SQL, XML or any other text files.

Ready for more? Go back to the overview for more information and examples.

Article Updates

  • 3/21/2009. Name of the directive processor has changed from XsdProcessor to T4Toolbox.XsdProcessor in version 9.3.21.1 of the T4 Toolbox.

Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

I have been experimenting with Linq inside of the template, but I cannot get it to compile properly. I’ve tried to add the necessary assemblies but that was not help. I believe its when I try a lambda expression, like “a => a is Association” the “=> causes the template generator to freak out. Is there anything I can do to get around this?

Try setting the language parameter to “C#v3.5″ or “VBv3.5″ in the template directive.

Hi Oleg

Super article.

The part of adding the xsd was just what i needed to perform custom additions to what the LinqToSqlGenerator does.

Now i have an additional property on all my tableclasses - created in 5 minutes. Sweet.