If you find yourself writing ‘similar’ and ‘repetitive’ code, you might want to look into custom code generation.
Code generator tools generate their code based on some kind of meta-data. LightSwitch is partly a code-generator tool itself: what you do as a developer in the Screen Designer, Query Designer or Entity Designer, is stored in LightSwitch Markup Language (lsml files, the meta-data that describes your application), and used to generate code for parts of your application at compile time, and to interpret other parts at compile time.
Because your LightSwitch application already has this meta-data-driven approach, it’s extremely easy to do additional code generation yourself!
I started by downloading some tools, Tangible T4 editor came highly recommended for code coloring and “File>New Item” templates. After installation and VS reboot, I added a new file and from the Tangible section in the Add New File dialog, selected “Advanced” template.
This generates a sample tt file (the T4 template) which interprets a piece of xml to generate a .cs file (any file is supported). In the tt file, just swap out the hardcoded xml to load the lsml files you need (the path to your lsml files might be different!):
XNamespace schema = "http://schemas.microsoft.com/LightSwitch/2010/xaml/model"; string serviceLsml = System.IO.File.ReadAllText( System.IO.Path.GetDirectoryName(this.Host.TemplateFile) + "\\..\\TESS.DataWarehouse\\TESS.DataWarehouse.Server\\Properties\\Service.lsml"); System.Xml.Linq.XDocument XmlDoc = System.Xml.Linq.XDocument.Parse(serviceLsml);
Then just use your XDocument wizardry to extract the information you need…
foreach(var entityContainer in XmlDoc.Root.Descendants (schema + "EntityContainer")) { #> // Data source: <#= entityContainer.Attribute("Name").Value #> <#
Practical example: although it was tempting to try to generate a WinForms application based on the client lsml, we found it more rewarding to use this in our reporting solution instead. We have this one LightSwitch application which exposes a couple of views (BI sans BS). Reports are ran from the server as well, using the ServerApplicationContext as a data source. However, when an end-user wants to design a new, or alter an existing ‘report definition’, he/she can’t “access” the ServerApplicationContext in the end-user report designer. As a workaround, we use T4 to generate a stand-alone class library with a simplified layout of our LightSwitch ServerApplicationContext, but which spits out random dummy data when accessed. This class library is then distributed alongside with an end-user report designer, so that they can design the reports. When a report is uploaded, we simply swap out the ‘Dummy’ data source for one that accesses the ServerApplicationContext instead. (Mega-Props to Paul Van Bladel for switching on the light here 😉 )
Here’s a fragment of the full T4 template, more likely to be used as an example than anything else. As you can see, it generates a partial class, so that I can have some non-generated methods as well (like: CreateDummyData< T >).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<#@ template debug="true" hostSpecific="true" #> | |
<#@ output extension=".cs" #> | |
<#@ Assembly Name="System" #> | |
<#@ Assembly Name="System.Core.dll" #> | |
<#@ Assembly Name="System.Xml.dll" #> | |
<#@ Assembly Name="System.Xml.Linq.dll" #> | |
<#@ Assembly Name="System.Windows.Forms.dll" #> | |
<#@ import namespace="System" #> | |
<#@ import namespace="System.IO" #> | |
<#@ import namespace="System.Diagnostics" #> | |
<#@ import namespace="System.Linq" #> | |
<#@ import namespace="System.Xml.Linq" #> | |
<#@ import namespace="System.Collections" #> | |
<#@ import namespace="System.Collections.Generic" #> | |
// Generated class, do not modify the class but the .tt file instead!! | |
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Linq; | |
namespace LightSwitchApplication{ | |
[DataObject] | |
public partial class ReportSource{ | |
<# | |
XNamespace schema = "http://schemas.microsoft.com/LightSwitch/2010/xaml/model"; | |
string serviceLsml = System.IO.File.ReadAllText( | |
System.IO.Path.GetDirectoryName(this.Host.TemplateFile) + "\\..\\TESS.DataWarehouse\\TESS.DataWarehouse.Server\\Properties\\Service.lsml"); | |
System.Xml.Linq.XDocument XmlDoc = System.Xml.Linq.XDocument.Parse(serviceLsml); | |
foreach(var entityContainer in XmlDoc.Root.Descendants (schema + "EntityContainer")) | |
{ | |
#> | |
// Data source: <#= entityContainer.Attribute("Name").Value #> | |
<# | |
foreach(var entitySet in entityContainer.Descendants( schema + "EntitySet" )) | |
{ | |
if(entitySet.Attribute("Name").Value == "Reports" || | |
entitySet.Attribute("Name").Value == "ReportDefinitions") | |
continue; | |
#> [DataObjectMethod(DataObjectMethodType.Select)] | |
public IEnumerable< <#=entitySet.Attribute("EntityType").Value #> > <#= entitySet.Attribute("Name").Value #> () | |
{ | |
return GenerateDummyData< <#=entitySet.Attribute("EntityType").Value #> >(); | |
} | |
<# | |
} | |
#> | |
<# | |
} | |
#> | |
} | |
<# | |
foreach(var entityType in XmlDoc.Root.Descendants ( schema + "EntityType")) | |
{ | |
if(entityType.Attribute("Name").Value == "Report" || | |
entityType.Attribute("Name").Value == "ReportDefinition") | |
continue; | |
#> | |
public partial class <#= entityType.Attribute("Name").Value #> { | |
<# | |
foreach(var property in entityType.Descendants(schema + "KeyProperty")){ createProperty(property, schema);} | |
foreach(var property in entityType.Descendants(schema + "EntityProperty")){ createProperty(property, schema);} | |
#> | |
} | |
<# | |
} | |
#> | |
} | |
<#+ | |
void createProperty(XElement input, XNamespace schema) { | |
if(input.Descendants(schema + "Hidden").Any()) | |
return; | |
#> | |
public <#+ createPropertyType(input, schema); #> <#= input.Attribute("Name").Value #> {get;set;} | |
<#+ | |
} | |
#> | |
<#+ | |
void createPropertyType(XElement input, XNamespace schema) { | |
var propertyType = input.Attribute("PropertyType").Value.Substring(1); | |
if(propertyType.Equals("String?")) { #><#= "string" #><#+ } | |
else {#><#= propertyType #><#+} | |
} | |
#> |
The amount of LightSwitch and T4 resources online right now is … Well, just this post… Two: this post, and Paul Van Bladel’s material. If you ever decide to wander on this lucrative T4+LightSwitch combination, please do share your LightSwitch love so that I and others can learn.
Keep rocking LS!
Jan
DockShell is shell extension that also leverages T4 code generation to spit out Types based on user defined model.
John, my templating powers are no match for yours 😀 http://www.youtube.com/watch?v=8kzeCvhsjI8 Love DockShell!
Hi Jan,
Cool and thanks for mentioning my name 🙂
Readers interested in T4 in LightSwitch can also check a simple example here: http://blog.pragmaswitch.com/?p=915 .
It’s a T4 for generating strongly typed constants for choice lists values.
Tadaaam 😉
Pingback: Microsoft Fakes Framework - The Daily Six Pack: January 7, 2014
Pingback: LightSwitch Community & Content Rollup- December 2013 - Beth Massi - Sharing the goodness - Site Home - MSDN Blogs
Pingback: LightSwitch 社区和内容汇总-2013年12月份 - Beth Massi的中文博客 - Site Home - MSDN Blogs