Allow templates to easily orchestrate the invocation of other templates by name/id, making multi-project and multi-item templates easier to keep up to date, and factoring out subsets of currently-giant templates into maintainable chunks.
- A JS API with a corresponding .NET Backend
- A Library project, a console project, and a test project
- An Aspire AppHost with a .NET Backend
The template engine already has a known set of supported types - we should add a new one: compound
templates. These
templates would have additional syntax in the form of a subtemplates
array which describes the set of child templates
the parent compound template would orchestrate. The subtemplates
array would consist of items that:
- uniquely identify the child template (for allowing sharing of symbols)
- specify the child template to invoke (either by shortname alone or by a combination of PackageName/shortName for specificity)
- specify how the template engine should invoke the child template, namely:
- key input parameters like name, output, etc
- allowing specifying concrete values for the symbols of the child template
In addition, the parent template's symbols
would be able to re-use specific symbols from the child templates,
referencing them by identifiers and encouraging re-use and delegation.
An example of a compound template in the new system might look something like this:
{
"name": "My compound template",
"shortName": "compound-template",
"type": "compound",
"subtemplates": [
{
// the id is how we refer to this subtemplate in the rest of this file
"id": "console-app",
"template": "console", // this can be a shortname, or for great specificity a Package::shortname disambiguation syntax can be used
"name": "ref:parent:console-app-name", // name and output path can reference the parent's name and output path, specific parent symbols, or be hard-coded strings
"condition": "{favoriteAnimal} == 'Chicken'"
"symbols": [
"langVersion": "ref:parent:langVersion", // symbols can be set to hard-coded values or reference symbols defined on the parent (which themselves can come from children)
"framework": "ref:parent:framework"
]
},
{
"id": "library",
"template": "classlib"
}
],
"symbols": {
"langVersion": "ref:console-app:langVersion", // the parent template can pull out specific symbols and reuse them
"framework": "ref:console-app:langVersion",
"favoriteAnimal": {
"type": "parameter",
"description": "What's your favorite barn animal?",
"datatype": "choice",
"choices": [
{ "choice": "Pig" },
{ "choice": "Cow" },
{ "choice": "Chicken" }
]
},
}
}
Child templates need to be locateable to be installed or invoked. They can be referenced by one of two ways:
"<shortName>"
- if the shortName is unique and the shortName is in the package search cache, then it's enough to specify the shortName to identify the template to invoke"<PackageId>[@<PackageVersion>]:<shortName>"
- this can be used to explicitly specify the nuget package containing the template, optionally its version, and the name of the template in the package. The version is optional, if not specifiedinstall
will grab the latest version of the template.
Note
Only one level of template nesting will be supported. Specifically only templates of type
project
or item
can be used as subtemplates
.
We propose a new syntax for referencing symbols from another template: ref:<target>:<symbolName>
. This allows a 'parent'
template to point to symbol definitions in the child templates, and child template mappings to reference symbols from the parent.
You can see an example of what this referencing might look like in the example above.
As all other templates can, compound templates can define their own symbols. These symbols can be used by the parent template's content during rendering, or be passed to child templates as shown above.
For the .NET CLI, installing a compound template will consist of two phases:
- installing the initial template
- installing all subtemplates referenced in the parent template
This should be treated as an atomic action - the user should be presented with a yes/no dialog and a list of the child templates that will be installed after the compound template is downloaded, and that list should direct the user to details pages/etc where they can learn more about each template before accepting or denying the install request. If the request is accepted, all child templates are installed. If the request is denied, the parent template is uninstalled. If any errors occur during installation, all installed templates are uninstalled.
The compound template is the entrypoint of the invocation operation. The symbols of the compound template will be interrogated and used to bind any user inputs as symbols are for other templates today - the primary change being that the definitions for those symbols may be resolved from child templates.
Invocation of a compound template will proceed as follows:
- parent symbols are resolved
- each child template will be invoked in turn:
- child template symbols will be defined, taking into account any inputs that come from the parent template's mappings
- this includes name/output symbols
- child template content is rendered
- child template post-actions are run
- child template symbols will be defined, taking into account any inputs that come from the parent template's mappings
- parent template content is rendered (since it may rely on that of the child templates)
- parent template postactions are run (since they may rely on that of the child templates)
Some questions: