Now that you have a language model, we'll
As you've seen previously, a set of .NET classes are
The object model that is created is strongly typed to the language you defined in the language designer, making it extremely straightforward to use. Consider the following example, which iterates around all of the pages defined in the Navigation model depicted in Figure 6-15 and outputs each page's
IList MELPages = this.Store.ElementDirectory.GetElements (DomainModel.Page.MetaClassGuid); foreach (DomainModel.Page MELPage in MELPages) { Debug.WriteLine( "Page: " + MELPage.Name ); if (MELPage.ChildPages.Count > 0) { Debug.WriteLine("Links to: "); foreach (DomainModel.Page MELChildPage in MELPage.ChildPages) { Debug.WriteLine(MELChildPage.Name); } Debug.WriteLine("\n"); } else { Debug.WriteLine("Links to nothing"); } }
Here's the output:
Page: Logon Links to: Logon Failed My Accounts View Statement Move Money Page: Logon Failed Links to: Logon Page: My Accounts Links to: Signoff Page: View Statement Links to: Signoff Page: Transfer Money Links to: Signoff Page: Signoff Links to nothing
The code sample uses the in-built ElementDirectory to find all of the Page elements in our domain model and then iterates through them. Note that the ChildPages collection is the left-hand role of the Transition relationship that we defined in the language model, which gives you access to a collection of references to other pages within the model.
The object model is extremely easy to navigate and is typically used by any extensions you build into your designer. Examples of such extensions include code generation or custom save and load routines for your designer; a custom save routine would persist the contents of a model to a proprietary file format and the corresponding custom load routine would then convert from that file format back into the language model to be
The DSL Tools provide a rich templating technology that enables you to generate output based on your domain model; and as discussed previously, this technology is used to create your designer and can be used by your own designer. It can also be used by your designer to generate your output of choice — source code and documentation are common examples.
In the scenario of your Navigation Designer, you want to generate ASP.NET Pages and their associated code-behind files for each of the pages in the language model. As placeholders, you could create
At the time of writing, the DSL Tools do not provide a way for a template to output multiple files during execution. In our scenario, we need to generate an .ASPX and ASPX.CS file for every Page defined in our navigation model, so we are unable to generate pages using the templates. This feature is planned in the final product. If you were to use the templating technology, you would define a template of a shell ASPX and ASPX.cs file and provide .NET code to iterate around the object model creating pages.
For the purposes of the book, we will implement the page generation manually using .NET code. The principles are in fact identical to how templates will work, but you have to provide a bit more plumbing.
You start by defining the template for the ASPX and ASPX.cs files by creating a new Web Site project and use the default pages generated as a base for your templates. Any page-specific information has to be
The ASPX and ASPX.cs templates are shown in the following code examples. You can see that tokens have been added at key places (highlighted in bold). Where the data is the same, we use the same token identifier, and any curly braces in the code need to be escaped with an extra curly
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="
{0}
.aspx.cs" Inherits="
{0}
" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1"
runat
="server"> <h1>
{1}
</h1> <div>
{2}
</div> </form> </body> </html>
Here is the corresponding ASPX.CS template. The number of using statements to import namespaces has been reduced for clarity:
TemplatePage.aspx.cs using System; public partial class
{0}
: System.Web.UI.Page {{
{1}
}}
Now that you have your templates, you need to write the code that will iterate through all of the pages in the domain model and generate the physical
The code shown retrieves a collection of all the Pages in your domain model and iterates through each one in
For each page, you then obtain any child Pages referenced by the Page and construct a button for each one, enabling the
You then replace any tokens in the two files with the page-specific information and then output the text for the pages to disk. Following its execution, you will see .ASPX and .ASPX.cs files generated:
const string ButtonDefinition = "<asp:Button ID=\"{0}\" runat=\"server\" Text=\"{0}\" OnClick=\"{0}_Click\" />"; const string ButtonCodeDefinition = "protected void {0}_Click(object sender, EventArgs e)\r\n{{\r\n{1}\r\n}}"; IList MELPages = this.Store.ElementDirectory.GetElements(DomainModel.Page.MetaClassGuid); foreach (DomainModel.Page MELPage in MELPages) { //Read the ASP.NET Page Templates into a string so we can customise. StreamReader ASPXTemplatePageReader = new StreamReader("TemplatePage.aspx"); string TemplateASPXPage = ASPXTemplatePageReader.ReadToEnd(); StreamReader CodeTemplatePageReader = new StreamReader("TemplatePage.aspx.cs"); string TemplateCodePage = CodeTemplatePageReader.ReadToEnd(); //We loop around any Child Pages and provide buttons on the ASP.NET Page and //Code Behind handlers to move to the child pages StringBuilder ASPXButtonDefinitions = new StringBuilder(); StringBuilder CodeButtonDefinitions = new StringBuilder(); foreach (DomainModel.Page MELChildPage in MELPage.ChildPages) { //Variable and type names cannot have spaces so we remove string CollapsedChildName = MELChildPage.
Name
.Replace(" ", ""); //Create a new ASP.NET Button ASPXButtonDefinitions.Append( String.Format(ButtonDefinition, CollapsedChildName) ); //Create the Code behind for the button string NavigationCode = String.Format( "Response.Redirect(\"{0}.aspx\");", CollapsedChildName ); CodeButtonDefinitions.Append(String.Format(ButtonCodeDefinition, CollapsedChildName, NavigationCode ) ); } //Variable and type
names
cannot have spaces so we remove string CollapsedName = MELPage.Name.Replace(" ", ""); //Our page templates have the areas that need //to change for each page replaced with tokens which we now fill in. string OutputASPXPage = String.Format(TemplateASPXPage, CollapsedName, MELPage.Name, ASPXButtonDefinitions.ToString() ); string OutputCodePage = String.Format(TemplateCodePage, CollapsedName, CodeButtonDefinitions.ToString() ); using (StreamWriter ASPXPageWriter = new StreamWriter( CollapsedName + ".aspx" ) ) { ASPXPageWriter.Write(OutputASPXPage); } using (StreamWriter ASPXPageWriter = new StreamWriter( CollapsedName + ".aspx.cs" ) ) { ASPXPageWriter.Write(OutputCodePage); } }
If you then copy these files into a new ASP.NET website and navigate to Logon.aspx, you should see the logon page as defined in the language model appear as shown in Figure 6-17.
Figure 6-17
Clicking one of the buttons will enable you to navigate to the page as defined in the language model. For example, clicking ViewStatement will take you to the View Statement page (shown in Figure 6-18), which only has a link to the Signoff page.
Figure 6-18
As you can see, generating these pages was extremely straightforward, and even a complex navigation model would require much less development effort. The templating support in the DSL Tools makes generation of these pages even easier.
If you wanted to produce an Avalon version of your site or move to another page technology, you could simply replace the templates and regenerate the pages. Using the domain model in this way effectively decouples you from the implementation technology.