Introducing Code Generation - Performing Real-World Code Generation
(Page 11 of 13 )
By now, you know what basic code generation looks like and how three different mechanisms that accomplish code generation work. You may already be leaning toward one of these approaches. You’ve also seen the five steps making code generation a conscious process and the five non-negotiable principles that back up these steps. This combination builds the environment where you can commit to code generation for your application development, remembering that code-generated applications also have significant amounts of handcrafted code.
To get a deeper understanding of how code generation works and how to implement each of the three mechanisms, you need to see them function in the context of a real-world class. Maybe “real-world” is a stretch for this chapter. This is a simple class, but it demonstrates how you’ll deal with data containers with properties and private fields for each column in the table. You’ll see this type of class enhanced a bit in Chapter 3 and fully developed in Chapter 8.
Creating a Simple Class The simplest real-world example is a class with private fields exposed by public properties, as shown in Listing 1-2.
Listing 1-2. A Target Program for Creating a Simple Class
Option Strict On Option Explicit On Imports System
Public Class Customers
#Region "Class level declarations"
Private m_CustomerID As System. String
Private m_CompanyName As System. String
Private m_ContactName As System. String
Private m_ContactTitle As System. String
Private m_Address As System. String
Private m_City As System. String
Private m_Region As System. String
Private m_PostalCode As System. String
Private m_Country As System. String
Private m_Phone As System. String
Private m_Fax As System. String
#End Region
#Region "Public Methods and Properties" Public Property CustomerID() As System. String Get
Return m_CustomerI D End Get Set(ByVal Value As System. String)
m_CustomerID = Value End Set End Property
Public Property CompanyName() As System. Strin g Get
Return m_CompanyNam e End Get Set(ByVal Value As System. String)
m_CompanyName = Value End Set End Property
I’ll skip the rest of the properties because they follow a similar pattern.
To determine the metadata needed to create this target output, you want to identify all the variable information. The variable information in Listing 1-2 is marked in bold. That’s the class name and the name and type of each field and property. You also want to identify repeating patterns. There are just two repeating patterns in this code: one for the private fields and one for the properties.
*********************
TIP Print your sample class, and mark the changeable information with a highlighter. Then use your highlighter to bracket repeating patterns within the class. This will serve as a reference for creating your templates.
Collecting the XML Metadata Before looking at the template, it’s useful to know what the metadata looks like.9 For the example in Listing 1-3, I just typed the metadata into the .NET XML editor. In addition to the metadata about the Customers table, it also includes metadata about the Orders table. This will let you see how a metadata file containing a full set of data can create a single class file using a subset of the metadata. You can generate similar classes for anything else for which you supply metadata. Listing 1-3 shows the metadata for the Customers and Orders tables.
*********************
9. You’ll discover in Chapter 2 that this is quite an understatement.
*********************
TIP The second time you create code from a template (using a second set of metadata) often exposes changeable information you overlooked.
Listing 1-3. XML Metadata Input for Generating the Simple Class
<?xml version="1.0" encoding="utf-8" ?> <DataSet Name="Northwind">
<TABLE Name="Customers"> <Column Name="CustomerID" Type="String" /> <Column Name="CompanyName" Type="String" /> <Column Name="ContactName" Type="String" /> <Column Name="ContactTitle" Type="String" /> <Column Name="Address" Type="String" /> <Column Name="City" Type="String" /> <Column Name="Region" Type="String" /> <Column Name="PostalCode" Type="String" /> <Column Name="Country" Type="String" /> <Column Name="Phone" Type="String" /> <Column Name="Fax" Type="String" />
</Table>
<Table Name="Orders"> <Column Name="OrderID" Type="Int32" /> <Column Name="CustomerID" Type="String" /> <Column Name="EmployeeID" Type="Int32" /> <Column Name="OrderDate" Type="DateTime" /> <Column Name="RequiredDate" Type="DateTime" /> <Column Name="ShippedDate" Type="DateTime" /> <Column Name="ShipVia" Type="Int32" /> <Column Name="Freight" Type="Decimal" /> <Column Name="ShipName" Type="String" /> <Column Name="ShipAddress" Type="String" /> <Column Name="ShipCity" Type="String" /> <Column Name="ShipRegion" Type="String" /> <Column Name="ShipPostalCode" Type="String" /> <Column Name="ShipCountry" Type="String" />
</Table> </DataSet>
*********************
TIP Using the System version of each data type allows generation of both C# and VB .NET code from the same XML metadata file.
All XML must contain a single root element. The name of the root element in this XML document is DataSet. The root element contains two Table elements, and each Table element contains a series of Column elements that contain the name and type of each column.
Generating a Simple Class via Brute Force Initiating code generation via brute force is easy. You open the XML metadata file and pass this to the GenerateOutput method of the ClassViaBruteForce class. To make it more interesting, the example creates both the Customers and Orders class files from the same GenerateOutput method:
Private Shared Sub GenerateClassViaBruteForce(ByVal outputDir As String)
' Open Metadata file
Dim xmlMetaData As New Xml.XmlDocument
Dim tOrdersableName As String = "Customers"
ClassViaBruteForce.GenerateOutput( _
IO.Path.Combine(outputDir, "ClassCustomersViaBruteForce.vb"), _
xmlMetaData, "Customers") ClassViaBruteForce.GenerateOutput( _ IO.Path.Combine(outputDir, "ClassOrdersViaBruteForce.vb"), _ xmlMetaData, "Orders")
End Sub
The ClassViaBruteForce.GenerateOutput method is similar to the earlier “Hello World” brute-force sample. The differences are that it uses IndentedTextWriter and that it uses a nonempty XML document for input. The IndentedTextWriter makes for a few more lines of code but is a less of a pain to keep the code lined up.
*********************
NOTE The CodeDOM namespace contains the IndentedTextWriter that simplifies whitespace management. I’m not actually doing CodeDOM generation in this sample, just borrowing a class from its namespace.
This brute-force template generates classes like the one shown in Listing 1-2:
' Class Summary: Generates a simple class based on XML metadata Public Class ClassViaBruteForce
#Region "Public Methods and Properties"
Public Shared Sub GenerateOutput( _ ByVal outputFile As String, _ ByVal xmlMetaData As Xml.XmlDocument, _ ByVal tableName As String)
Dim writer As New CodeDom.Compiler.IndentedTextWriter( _
New IO.StreamWriter(outputFile)) Dim node As Xml.XmlNode Dim nodeList As Xml.XmlNodeList
An XMLNodeList is a collection of XMLNodes. The SelectNodes method of the XMLDocument creates the nodeList, which contains the Column elements. SelectNodes takes an XPath expression specifying the tableName (Appendix A has more about XPath):
nodeList = xmlMetaData.SelectNodes( _ "/DataSet/Table[@Name=’" & tableName & "']/Column")
The XPath expression in this line says, “Get a collection of nodes that are Column elements under a parent Table element having a Name attribute that matches tableName, with the Table element also a child of the root DataSet element.” The nodeList is used later in the method.
WriteLine statements output code to the IndentedTextWriter in the same way they output to other streams. The first output is nearly static, with only the tablename changing:
writer.WriteLine("Option Strict On") writer.WriteLine("Option Explicit On") writer.WriteLine("") writer.WriteLine("Imports System") writer.WriteLine("") writer.WriteLine("' Class Summary: Simple output class") writer.WriteLine("") writer.WriteLine("Public Class " & tableName) writer.WriteLine("") writer.WriteLine("#Region " & Chr(34) & "Class level declarations" & Chr(34))
Incrementing the indent level by one increases the indent for further output by four spaces (the default indent string is four spaces). The first loop outputs the private fields corresponding to each column in the node list:
writer.Indent += 1 For Each node In nodeLIst writer.WriteLine("Private m_" & node.Attributes("Name").Value & _ " As " & node.Attributes("Type").Value)
Next writer.Indent -= 1 writer.WriteLine("#End Region") writer.WriteLine("")
Decrementing the IndentedTextWriter removes one level of indent from further output. The remainder of the template outputs the property procedure in another xsl:for-each loop:
writer.WriteLine("#Region " & Chr(34) & "Public Methods and Properties" & _
Chr(34)) writer.Indent += 1 For Each node In nodeLIst
writer.WriteLine("Public Property " & node.Attributes("Name").Value & _
"() As " & node.Attributes("Type").Value) writer.Indent += 1 writer.WriteLine("Get") writer.Indent += 1 writer.WriteLine("Return m_" & node.Attributes("Name").Value) writer.Indent -= 1 writer.WriteLine("End Get")
writer.WriteLine("Set(ByVal Value As " & node.Attributes("Type").Value & _
")") writer.Indent += 1 writer.WriteLine("m_" & node.Attributes("Name").Value & " = Value") writer.Indent -= 1 writer.WriteLine("End Set") writer.Indent -= 1 writer.WriteLine("End Property") writer.WriteLine("")
Next writer.Indent -= 1 writer.WriteLine("#End Region") writer.WriteLine("") writer.WriteLine("End Class")
writer.Flush() writer.Close()
End Sub #End Region
End Class
That’s it. Maybe it’s a tad ugly, but it’s marvelously effective in generating code output. It outputs the class shown in Listing 1-2. You could create a similar class for any table in any database or any business object, if you supply the metadata. The next chapter shows how to automate the metadata creation step.
This chapter is from Code Generation in Microsoft .NET by Kathleen Dollard (Apress, 2004, ISBN: 1590591372). Check it out at your favorite bookstore today.
Buy this book now. |
Next: Generating a Simple Class via CodeDOM >>
More .NET Articles
More By Apress Publishing