Setup
Each Client Builder module (e.g., WebApiModule
, TranslationsModule
) inherits from the base ScaffoldModule
. A module’s job is to describe:
- What folders to create
- What files to generate
- What data to inject into templates
The heart of every module is the SetupAsync
method. This method runs when the Client Builder is executed (typically triggered from the /_cb
UI).
What does SetupAsync
do?
SetupAsync
defines the exact files and folders the module should scaffold. Inside SetupAsync
, you:
- Fetch any metadata your files need (e.g., controllers, classes, enums, translations).
- Define one or more folders using
AddFolder
. - Define one or more files using
AddFile
and connect each file to a template. - Supply context data for the templates — this is what makes your files dynamic.
Example 1: WebApiModule
What it does:
Fetches all MVC controllers and actions.
Extracts related classes and enums.
Generates:
services.ts
for calling the APIenums.ts
for enum definitionstypes.ts
for strongly-typed DTOs and contracts
How it works:
public override async Task SetupAsync()
{
// Gather metadata using extractors
var controllerActions = mvcDescriptionExtractor.FetchControllerActions();
var classes = mvcDescriptionExtractor.FetchActionsClasses(controllerActions).ToList();
var enums = descriptionExtractor.ExtractUniqueEnumsFromClasses(classes).ToList();
// Add a folder where files will be created
var rootFolder = Path.Combine("ClientApp", "src");
this.AddFolder(new ScaffoldModuleFolder
{
Name = "api",
RelativePath = rootFolder,
});
// Add each file, attach its template, and pass the context data
this.AddFile(new ScaffoldModuleFile
{
Name = "services.ts",
RelativePath = Path.Combine(rootFolder, "api"),
Template = new HandlebarsFileTemplate("..."),
ContextData = new { classes, serviceContexts },
});
this.AddFile(new ScaffoldModuleFile
{
Name = "enums.ts",
RelativePath = Path.Combine(rootFolder, "api"),
Template = new HandlebarsFileTemplate("..."),
ContextData = new { enums, enumsHelpers },
});
this.AddFile(new ScaffoldModuleFile
{
Name = "types.ts",
RelativePath = Path.Combine(rootFolder, "api"),
Template = new HandlebarsFileTemplate("..."),
ContextData = new { enums, classes },
});
}
Result: When you trigger the module, these .ts
files are rendered using the Handlebars templates, filled with real data about your API.
Example 2: TranslationsModule
What it does:
- Loads all translations from a
.resx
resource file. - Exports them to a single
translations.json
in your client app’sconfig
folder.
How it works:
public override Task SetupAsync()
{
// Load all translations from resource files
var translations = new Dictionary<string, string>();
var resourceSet = T.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
foreach (var entry in resourceSet)
{
var dictionaryEntry = (DictionaryEntry)entry;
translations[dictionaryEntry.Key.ToString()] = dictionaryEntry.Value?.ToString();
}
// Add the JSON file
this.AddFile(new ScaffoldModuleFile
{
Name = "translations.json",
RelativePath = Path.Combine("ClientApp", "src", "config"),
Template = new JsonFileTemplate(),
ContextData = translations,
});
return Task.CompletedTask;
}
Result: When you trigger this module, a fresh translations.json
with all your keys/values appears in your front-end project.
Key building blocks
Method | What it does |
---|---|
AddFolder | Declares a folder where generated files will live. |
AddFile | Declares a file and connects it to a template + data context. |
Template | Defines how the file will be rendered (Handlebars, JSON, etc.). |
ContextData | The data model passed to the template for rendering dynamic content. |
Typical pattern for your own modules
Every new module you create will usually follow this pattern:
public override async Task SetupAsync()
{
// Gather metadata
var data = GetSomeData();
// Add folders if needed
this.AddFolder(new ScaffoldModuleFolder { Name = "...", RelativePath = "..." });
// Add files and link them to templates
this.AddFile(new ScaffoldModuleFile
{
Name = "...",
RelativePath = "...",
Template = new MyTemplate(),
ContextData = new { ... },
});
}