buildingthingswith.net

Comparison of code-generation tools

OpenAPI Generator

OpenAPI Usage

Given, some valid OpenAPI spec the following can be done.

generate server stub from open-api spec

docker run --rm -v %CD%\local:/local openapitools/openapi-generator-cli generate -i /local/petstore.yaml -g aspnetcore -o /local/out/aspnetcore

output mustache templates

docker run --rm -v %CD%\local:/local openapitools/openapi-generator-cli author template -g aspnetcore -o /local/template/aspnetcore

view list of mustache templates here:

  1. all templates
  2. aspnetcore

create custom templates

docker run --rm -v %CD%\local:/local openapitools/openapi-generator-cli generate -i /local/petstore.yaml -g aspnetcore -o /local/out/aspnetcore -c /local/config.yaml

create custom generator

docker run --rm -v %CD%\local:/local openapitools/openapi-generator-cli meta -o /local/generators/my-aspnetcore -n my-aspnetcore -p com.my.aspnetcore

customize open-api template. below is an example of the controller.mustache template


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

using System.Threading.Tasks;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;

using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.SwaggerGen;


using Newtonsoft.Json;

using .Attributes;
using ;

namespace 
{ 
    /// <summary>
    /// 
    /// </summary>
    [Description("")]
    [ApiController]
    public  class Controller : ControllerBase
    { 
        /// <summary>
        /// 
        /// </summary>
        /// <remarks></remarks>
        /// <param name=""> (deprecated)</param>
        /// <response code=""></response>
        []
        [Route("}}")]


        [Authorize(Policy = "")]


        [Authorize(Roles = ",")]


        
        [Consumes()]
        
        [ValidateModelState]
        [SwaggerOperation("")]
        [SwaggerResponse(statusCode: , type: typeof(), description: "")]
        [ProducesResponseType(statusCode: , type: typeof())]
        
        [Obsolete]
        
        public  async Task<IActionResult> (, );
        
        {
            
            var  = Request.Cookies[""];
            



            //TODO: Uncomment the next line to return response  or use other options such as return this.NotFound(), return this.BadRequest(..), ...
            // return StatusCode(, default());


            //TODO: Uncomment the next line to return response  or use other options such as return this.NotFound(), return this.BadRequest(..), ...
            // return StatusCode();



            string exampleJson = null;
            
            exampleJson = "}";
            
            
            
            //TODO: Change the data returned
            return Task.FromResult<IActionResult>(new ObjectResult(example));
            throw new NotImplementedException();
        }
        
        
    }

}

OpenAPI Findings

Pros:

Cons:

Yeoman generator

Yeoman Usage

  1. install node with chocolatey
  2. install yo:
npm install -g yo
yo --version
# install a generator
npm install -g <generator-name>

# e.g. 
npm install -g generator-webapp

# scaffold new project using installed generator
yo <generator-name>

# e.g. 
yo webapp

# use sub-generators, e.g. add a controller
yo <generator-name>:<sub-generator-name>

# troubleshoot & debug
yo doctor
yo --generators
yo --help
yo --version
# create folder to hold generator, name is NB: generator-name, e.g. generator-sample
cd generator-name

# create package.json in folder
echo '' > package.json or npm init
npm install --save yeoman-generator

# create folders
# ├───package.json
# └───generators/
#     ├───app/
#     │   └───index.js
#     └───router/
#         └───index.js

npm link

yo <generator-name> # e.g. yo sample [doesn't need 'generator' prefixing it]

The available priorities are (in running order):

initializing - Your initialization methods (checking current project state, getting configs, etc) prompting - Where you prompt users for options (where you’d call this.prompt()) configuring - Saving configurations and configure the project (creating .editorconfig files and other metadata files) default - If the method name doesn’t match a priority, it will be pushed to this group. writing - Where you write the generator specific files (routes, controllers, etc) conflicts - Where conflicts are handled (used internally) install - Where installations are run (npm, bower) end - Called last, cleanup, say good bye, etc

Yeoman Sample

namespace <%= namespace %>.Controllers
{
    [Route("api")]
    [ApiController]
    public class AccountController : ControllerBase
    {

        <%_ if (authenticationType === 'jwt') { _%>
        private readonly ILogger<AccountController> _log;
        <%_ if (cqrsEnabled) { _%>
        private readonly IMediator _mediator;
        <%_ } else { _%>
        private readonly IMapper _userMapper;
        private readonly IMailService _mailService;
        private readonly UserManager<User> _userManager;
        private readonly IUserService _userService;
        <%_ } _%>

        <%_ if (cqrsEnabled) { _%>
        public AccountController(ILogger<AccountController> log, IMediator mediator)
        {
            _log = log;
            _mediator = mediator;
        }
        <%_ } else { _%>
        public AccountController(ILogger<AccountController> log, UserManager<User> userManager, IUserService userService,
            IMapper userMapper, IMailService mailService)
        {
            _log = log;
            _userMapper = userMapper;
            _userManager = userManager;
            _userService = userService;
            _mailService = mailService;
        }
        <%_ } _%>

        [HttpPost("register")]
        [ValidateModel]
        public async Task<IActionResult> RegisterAccount([FromBody] ManagedUserDto managedUserDto)
        {
            if (!CheckPasswordLength(managedUserDto.Password)) throw new InvalidPasswordException();
            <%_ if (cqrsEnabled) { _%>
            var user = await _mediator.Send(new AccountCreateCommand { ManagedUserDto = managedUserDto });
            <%_ } else { _%>
            var user = await _userService.RegisterUser(_userMapper.Map<User>(managedUserDto), managedUserDto.Password);
            await _mailService.SendActivationEmail(user);
            <%_ } _%>
            return CreatedAtAction(nameof(GetAccount), user);
        }

        /// etc...
        }
    }    
}    

Yeoman Findings

Pros:

Cons:

T4 templates

T4 (Text Template Transformation Toolkit) is a Microsoft templating engine that mixes literal text with C# control code to generate any text file at design time or build time. It is built into Visual Studio and .NET tooling, so it needs no extra runtime, but the template syntax and editor support are dated.

Roslyn API

Roslyn is the .NET compiler platform, exposing syntax trees and semantic models that let you parse, analyse and generate C# and VB code programmatically. Unlike text templating it produces code from a typed object model (or as source generators at compile time), at the cost of more code and a steeper learning curve.

OpenAPI Docs

Yeoman Docs

T4 Docs

Roslyn Docs

Home