Customising the Generated Code
As discussed in Creating Domain Models, LightSpeed models are ultimately just code, and the visual designer works by generating that code for you. LightSpeed offers several ways to customise and extend this generation process.
Code generation in LightSpeed uses a set of NVelocity templates, written in VTL (Velocity Template Language) and found in the installation directory under Tools > Designer > Templates. You can edit these templates to change the way that the designer generates LightSpeed code.
However, it’s recommended that you don’t edit the templates at the installation location for two reasons. First, whenever you upgrade LightSpeed, it will reinstall its templates over the top of yours. Second, if you’re working with other developers, or across multiple machines, you need to make sure the custom templates get copied to each machine.
To solve these problems, LightSpeed allows you to keep your templates with the project. By doing this, you eliminate the risk of the templates being overwritten during install, and you can treat them as part of the project rather than a machine setting — for example putting them in source control so that when another developer gets the project sources they get the current templates as well.
To do this, copy the template files from the installation location to a suitable project-specific location. Be sure to choose the template appropriate for your project language (under the C# or VB directory in the installation location).
Then go into Solution Explorer, select the project node and look at the Properties grid. If the project contains a LightSpeed model (a .lsmodel file), you’ll see an extra entry here called LightSpeed Template File. (You may need to open the .lsmodel file to make the extra entry appear.) Edit this to point to your copy of the “main” template (typically Base.vm):
From this point on, whenever the model changes, the code will be regenerated using your copies of the templates.
There are a couple of maintenance considerations for custom templates:
1. Visual Studio won’t automatically regenerate code when you change the template. Changes to the templates will only take effect next time you edit your model. If you don’t actually need to do anything to the model, just move something and move it back again – that will be enough to trigger regeneration.
2. We occasionally ship updates to the default templates, to reflect new features or options, or to fix bugs. If these updates are relevant to you, you’ll want to fold them into your custom templates. It’s therefore a good idea to keep a copy of the “Mindscape version our custom templates are based on” around. That way, when we update the templates, you can use a diff and merge tool to find the changes between the Mindscape versions and merge them into your files (or to merge your diffs from the baseline onto the new baseline).
The designer allows you to specify entity and property options that are relevant to LightSpeed. It’s sometimes useful to be able to specify other options which aren’t relevant to LightSpeed but which logically belong on the entity or property, and to emit those options through a custom template.
For example, suppose you have a user interface framework which uses DisplayNameAttribute to render the name of each property. You could specify DisplayNameAttribute manually, using the Custom Attributes collection, but this could become inconvenient if you have a lot of properties. You might wish instead to add a Display Name setting to entity properties, so that you could readily edit it in the grid, and to generate DisplayNameAttribute using that Display Name setting.
You can do this in the LightSpeed designer using extension properties. The default code generation templates ignore extension properties, but custom templates can use them to generate whatever code is required.
To define an extension property, open the LightSpeed Model Explorer, right-click the root model node, and choose Add New Extension Property Definition. You can specify the following settings for your extension property:
· Name: The name by which templates refer to the extension property.
· Extends: The kind of model elements to which the extension applies.
· Data Type Name: The CLR type name of the type of data that can be stored in the extension property. This must be fully qualified, e.g. System.Int32 or System.String. If the type is not defined in mscorlib.dll or System.dll, then the name must be assembly-qualified, or the assembly must be included in the Design Time Assemblies collection.
· Category, Display Name and Description: Additional options for how the extension property is displayed in the Visual Studio Properties grid.
Once you have defined an extension property, it appears in the properties grid for every element of the kinds you specified in Extends, and you can enter values for it as if it were a built-in property.
To use an extension property in a template, there are three methods which you can call from the template:
· HasExtendedProperty(name): Returns true if the user set a value for the named extension property on the element at hand, or false if the user did not set a value (or the extension property does not apply to this kind of element).
· GetExtendedPropertyValue(name): Returns the value set by the user for the named extension property on the element at hand, and throws an exception if the user did not set a value. You should always call HasExtendedProperty before calling this method.
· GetExtendedPropertyValue(name, defaultTo): Returns the value set by the user for the named extension property on the element at hand, or the defaultTo value if the user did not set a value (or the extension property does not apply to this kind of element). You can safely call this without calling HasExtendedProperty.
The value returned from GetExtendedPropertyValue is the actual value of the property. This may not be suitable for emitting into the generated code directly. For example, if GetExtendedPropertyValue returns a string, and you want to emit that string into a DisplayNameAttribute declaration, you will usually want to quote that string. Or if it returns an enum value, you will usually want to emit it qualified with the enum type name.
[DisplayName(Full Name)] // would be an error
To convert a literal value to a code fragment for that literal value, call $Translator.TranslatePrimitive from your custom template.
Generating DisplayNameAttribute from the DisplayName extension property
Using TranslatePrimitive ensures that values are converted to literals in a way which is correct for both the value and the target programming language.
(Of course, if the extension property is intended to be used in a VTL expression such as a #if test, or if it represented a member name such as a property in a strongly-typed resource class, then you will not want to quote it. This is one of the reasons why GetExtendedPropertyValue returns a value rather than a code fragment.)
You can also use Visual Studio’s T4 templates with a LightSpeed model. This technique is useful when you want to generate separate files from the model, rather than tweaking or extending the entity code as you would if you were customising the built‑in templates. For example, you could write a T4 template to generate WCF service interfaces or ASP.NET MVC controllers, perhaps shaped by your own extension properties.
To create a T4 template which works with a LightSpeed model, create a .tt file in your project and add the following declarations at the top:
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
You must also add the paths to the designer assemblies to your project’s Reference Paths collection (Project > Properties > Reference Paths). Alternatively, you can specify the path in the assembly directives. The designer assemblies are not redistributable and you should not add them to your project. T4 needs them to process the template but you do not need them at run time.
You must then specify the file extension for the generated file, using the output directive:
<#@ output extension=".txt" #>
Finally you must specify the LightSpeed model from which to generate code, using the LightSpeedModel directive. The processor attribute is always LightSpeedModelDirectiveProcessor; the requires attribute should specify fileName='lsmodel_file'. The following directive hooks the T4 template up to a LightSpeed model named Sample.lsmodel:
// Line breaks added for clarity
You can now write T4 code as normal. The LightSpeed model is available through the this.Model reference.
The designer object model is not documented so you will need to ask in the LightSpeed forum or use the Visual Studio Object Browser to determine the programmatic names of metamodel classes and properties (though they usually correspond to the display names shown in the toolbox and property grid).