Skip to content

Latest commit

 

History

History

Beef.CodeGen.Core

Beef.CodeGen.Core

This tooling console application assembly contains the Beef code generation capabilities.

Code generation is proposed as a means to greatly accelerate application development where standard coding patterns can be determined and the opportunity to automate exists.

Code generation is intended to bring the following benefits:

  • Acceleration of development;
  • Consistency of approach;
  • Simplification of implementation;
  • Reusability of layering and framework;
  • Evolution of approach over time;
  • Richness of capabilities with limited effort.

There are generally two types of code generation:

  • Gen-many - the ability to consistently generate a code artefact multiple times over its lifetime without unintended breaking side-effects. Considered non-maintainable by the likes of developers as contents may change at any time; however, can offer extensible hooks to enable custom code injection where applicable.
  • Gen-once - the ability to generate a code artefact once to effectively start the development process. Considered maintainable, and should not be re-generated as this would override custom coded changes.

Beef primarily leverages Gen-many as this offers the greatest long-term benefits.


Supported code-gen

Both entity-driven and database-driven are supported; similar to the following image.

CodeGen


Composition

The base code-generation tooling is enabled by OnRamp; the OnRamp code-generation tooling is composed of the following:

  1. Configuration - data used as the input for to drive the code-generation and the underlying templates.
  2. Templates - Handlebars templates that define a specific artefact's scripted content.
  3. Scripts - orchestrates one or more templates that are used to generate artefacts for a given configuration input.

Configuration

The code-gen is driven by a configuration data source, in this case YAML or JSON.

The two supported configurations are:


Templates

The Beef templates (as per OnRamp) are defined using Handlebars and its syntax, or more specifically Handlebars.Net.

The template files have been added as embedded resources within the project to enable runtime access. The Beef standard templates can be found here.


Scripts

To orchestrate the code generation, in terms of the Templates to be used, a YAML-based script-like file is used. The Beef standard scripts can be found here. The Script files have been added as embedded resources within the project to enable runtime access.


Entity-driven code-gen

The entity-driven gen-many code generation is enabled by an Entity configuration file that is responsible for defining the characteristics used by the code-gen tooling. The hierarchy is as follows:

CodeGeneration
└── Entity(s)
  └── Property(s)
  └── Const(s)
  └── Operation(s)
    └── Parameter(s)

Configuration details for each of the above are as follows:

The Entity configuration supported filenames are, in the order in which they are searched by the code generator:

  • entity.beef-5.yaml and entity.beef-5.json.

The Entity configuration is defined by a schema, YAML/JSON-based entity.beef.json. This schema should be used within the likes of Visual Studio when editing to enable real-time validation and basic intellisense capabilities.

There are two additional configuration files that share the same schema:

  • Reference Data - used to define (configure) the Beef-specific Reference Data. Supported filenames are in the order in which they are searched by the code generator:

    • refdata.beef-5.yaml and refdata.beef-5.json.
  • Data Model - used to define (configure) basic data model .NET classes typically used to represent internal/backend contracts that do not require the full funcionality of a Beef entity. Supported filenames are in the order in which they are searched by the code generator:

    • datamodel.beef-5.yaml and datamodel.beef-5.json.

Additionally, secondary configuration files *.entity.beef-5.yaml, *.refdata.beef-5.yaml, *.datamodel.beef-5.yaml can be added to the project (including within subfolders) that will be automatically merged into the corresponding primary entity.beef-5.yaml, refdata.beef-5.yaml, datamodel.beef-5.yaml files respectively. The secondary files only support a single root entities property/node that merges into the primary's equivalent. This allows the configuration to be broken up logically to minimize challenges related to overall file size and complexity, and minimize potential developer merge conflicts, etc.


Database-driven code-gen

The database-driven code generation is enabled by a Database configuration file that is responsible for defining the characteristics used by the code-gen tooling. The hierarcy is as follows:

CodeGeneration
└── Query(s)
  └── QueryJoin(s)
    └── QueryJoinOn(s)
  └── QueryWhere(s)
  └── QueryOrder(s)
└── Table(s)
  └── StoredProcedure(s)
    └── Parameter(s)
    └── Where(s)
    └── OrderBy(s)
    └── Execute(s)
  └── Relationship(s)

Configuration details for each of the above are as follows:

The Database configuration supported filenames are, in the order in which they are searched by the code generator:

  • database.beef-5.yaml and database.beef-5.json.

The Database configuration is defined by a schema, YAML/JSON-based database.beef-5.json. The schema should be used within the likes of Visual Studio when editing to enable real-time validation and basic intellisense capabilities.

Finally, this is not intended as an all purpose database schema generation capability. It is expected that the tables pre-exist within the database. The database schema/table catalog information is queried from the database directly during code generation, to be additive to the configuration, to minimise the need to replicate (duplicate) column configuration and require on-going synchronization.


Console application

The Beef.CodeGen.Core can be executed as a console application directly; however, the experience has been optimized so that a new console application can reference and inherit the capabilities.

Additionally, the Database related code-generation can be (preferred method) enabled using Beef.Database.Core. This will internally execute Beef.CodeGen.Core to perform the code-generation task as required.


Commands

The following commands are available for the console application (the enablement of each can be overridden within the program logic).

Command Description
Entity Performs code generation using the entity configuration and EntityWebApiCoreAgent.yaml script.
RefData Performs code generation using the refdata configuration and RefDataCoreCrud.yaml script.
DataModel Performs code generation using the data model configuration and DataModelOnly.yaml script.
Database Performs code generation using the database configuration and Database.yaml script.
All Performs all of the above (where each is supported as per set up).

Additionally, there are a number of command line options that can be used.

Beef.CodeGen.Core Code Generation Tool.

Usage: Beef.CodeGen.Core [options] <command>

Arguments:
  command                   Execution command type.
                            Allowed values are: Entity, Database, RefData, DataModel, All, Clean, Count, EndPoints.

Options:
  -?|-h|--help              Show help information.
  -s|--script               Script orchestration file/resource name.
  -c|--config               Configuration data file name.
  -o|--output               Output directory path.
  -a|--assembly             Assembly containing embedded resources (multiple can be specified in probing order).
  -p|--param                Parameter expressed as a 'Name=Value' pair (multiple can be specified).
  -cs|--connection-string   Database connection string.
  -cv|--connection-varname  Database connection string environment variable name.
  -enc|--expect-no-changes  Indicates to expect _no_ changes in the artefact output (e.g. error within build pipeline).
  -sim|--simulation         Indicates whether the code-generation is a simulation (i.e. does not create/update any artefacts).

Extended commands and argument(s):
  clean       Cleans (removes) all related directories named 'Generated'.
              - Use --param exclude=name[,name] to exclude named directory(s) from the clean.

  count       Counts and reports the number of files and lines (All and Generated) within all related directories.
              - Use --param exclude=name[,name] to exclude named directory(s) from the count.

  endpoints   Lists (audits) the code-generated endpoints and related configuration.

Program.cs

The Program.cs for the new console application should be updated similar to the following. The Company and AppName values are specified, as well as optionally indicating whether the Entity, RefData, DataModel and/or Database commands are supported.

public class Program
{
    static Task<int> Main(string[] args) => CodeGenConsole
        .Create("Company", "AppName")           // Create the Console setting Company and AppName.
        .Supports(entity: true, refData: true)  // Set which of the Commands are supported.
        .RunAsync(args);                        // Run the console.
}

To run the console application, simply specify the required command; e.g:

dotnet run entity      -- Default filename: entity.beef-5.yaml
dotnet run refdata     -- Default filename: refdata.beef-5.yaml
dotnet run datamodel   -- Default filename: datamodel.beef-5.yaml
dotnet run all         -- All of the above (that are supported)

-- Override the configuration filename.
dotnet run entity --configFile configfilename.xml

Personalization and/or overriding

As described above Beef has a set of pre-defined (out-of-the-box) Scripts and Templates. These do not have to be used, or additional can be added, where an alternate code-generation outcome is required.

To avoid the need to clone the solution and update, add the Templates and Scripts folders into the new console application and embed the required resources. The underlying Beef.CodeGen.Core will probe the embedded resources and use the overridden version where provided, falling back on the Beef version where not overridden.

One or more of the following options exist to enable personalization.

Option Description
Config There is currently no means to extend the underlying configuration .NET types directly. However, as all the configuration types inherit from ConfigBase the ExtraProperties hash table is populated with any additional configurations during the deserialization process. These values can then be referenced direcly within the Templates as required. To perform further changes to the configuration at runtime an IConfigEditor can be added and then referenced from within the corresponding Scripts file; it will then be invoked prior to the code generation enabling further changes to occur. The ConfigBase.CustomProperties hash table is further provided to enable additional properties to be set and referenced in a consistent manner.
Templates Add new Handlebars file, as an embedded resource, to the Templates folder (add where not pre-existing) within the project. Where overriding use the same name as that provided out-of-the-box; otherwise, ensure the Template is referenced by the Script.
Scripts Add new Scripts YAML file, as an embedded resource, to the Scripts folder (add where not pre-existing) within the project. Use the Inherits attribute where still wanting to execute the out-of-the-box code-generation.

Example

The Beef.Demo.Codegen provides an example (tests the capability) of the implementation.

Code Description
TestConfigEditor.cs This implements IConfigEditor and demonstrates ConfigBase.TryGetExtraProperty and ConfigBase.CustomProperties usage.
TestCodeGenerator.cs This inherits from CodeGeneratorBase<TRootConfig, TGenConfig> overriding the SelectGenConfig to select the configuration that will be used by the associated Template.
Test_cs.hbs This demonstrates how to reference both the ExtraProperties and CustomProperties using Handlebars syntax. This file must be added as an embedded resource.
TestScript.yaml This demonstrates the required configuration to wire-up the previous so that they are leveraged apprpropriately at runtime. This file must be added as an embedded resource.

Finally the Program.cs will need to be updated similar as follows to use the new Scripts resource.

return CodeGenConsoleWrapper
    .Create("Beef", "Demo")
    .Supports(entity: true, refData: true, dataModel: true)
    .EntityScript("TestScript.yaml")   // <- Overrides the Script name.
    .RunAsync(args);