Improved code generation with Kentico Cloud
Technical | Kentico
Drazen Janjicek

By Drazen Janjicek | Principal Consultant 20.05.2017.

Improved code generation with Kentico Cloud

The Kentico Cloud team has made a very useful contribution to our daily work with Kentico Cloud. As we all know, working with Kentico (Cloud) API’s requires developers to target specific types or fields when querying or filtering content.

With Kentico Cloud, we can use the open sourced Cloud Generators Net project which is available on GitHub to generate strongly-typed classes in order to reflect the content types we have defined in Kentico Cloud. Each generated class contains the fields we have defined as public properties and the Kentico Delivery API is able to work with those classes. Pretty awesome isn’t it? Well, at a first glimpse it is, but it allows room for improvement.

Developers can use the generated classes for casting common object types returned by the Delivery API and work with the available property model. With the targeted MVC model, this seems a valuable addition. However, before we get to these casted objects, we often have to tell the Delivery API which kind of types we are targeting and what fields to filter on in the first place. And here comes the drawback, we cannot use any of the given strongly-typed possibilities.

The Cloud Generators Net project does not contain anything that would provide us developers with code names we usually have to work with. They still remain “magic strings” across the solution. In order to fix this, we have to extend the original codebase with a few lines of code.

Get the codebase

First, let’s clone the code base and create a local repository. You can use your favorite tooling to do this, I usually stick to what Visual Studio offers me. I don’t need more than that.

The repository we want to clone is located at https://github.com/Kentico/cloud-generators-net.git.

Once the repository has been created locally, let’s take a look… It’s nothing more than eight classes which do all the magic for you, and one of them is the console application we target whenever we (re-)generate the code classes.

These three classes require our attention:

  • ClassDefinition
  • CodeGenerator
  • ClassCodeGenerator

If we want to have strongly-typed code names available when we have to modify all of the listed classes.

ClassDefinition

This class basically provides the means to create the strongly-typed class and properties given by the targeted content type defined in Kentico Cloud. If we want to stick with the pattern that’s already in there, then we have to add three things.

1. Add a public property of type List<ContentElement> and instantiate it immediately. Let’s call it PropertyCodenameConstants

public List<ContentElement> PropertyCodenameConstants { get; } = new List<ContentElement>();

2. Add a private method with the return type of bool to verify the existence of the property code name to be added

private bool PropertyCodenameConstantIsAlreadyPresent(ContentElement element)
{
   return PropertyCodenameConstants.Exists(e => e.Codename == element.Codename);
}

3. Add a public method which will add the provided ContentElement object to the collection of the supposed property code name constants

public void AddPropertyCodenameConstant(ContentElement element) 
{
    if (PropertyCodenameConstantIsAlreadyPresent(element)) 
    {
        throw new InvalidOperationException($"Property with code name '{element.Codename}' is already included. Can't add two members with the same code name.");
    }

    PropertyCodenameConstants.Add(element);
}

CodeGenerator

This class is responsible for retrieving the generated code and writing it to the file system. Here, we need to extend the private GetClassCodeGenerator method with the following line of code:

classDefinition.AddPropertyCodenameConstant(element);

try 
{
    var property = Property.FromContentType (element.Codename, element.Type);
    classDefinition.AddPropertyCodenameConstant (element);
    classDefinition.AddProperty (property);
}

ClassCodeGenerator

This class requires the last code changes. In here, the actual code syntax is being created and we need to add support for our strongly-typed code names. In the GenerateCode method we want to add the following code below the property generation logic:

var properties = ClassDefinition.Properties.Select(element => …);

var classCodenameConstant = SyntaxFactory.FieldDeclaration(
        SyntaxFactory.VariableDeclaration(
            SyntaxFactory.ParseTypeName("string"),
            SyntaxFactory.SeparatedList(new[] {
                SyntaxFactory.VariableDeclarator(
                    SyntaxFactory.Identifier("Codename"),
                    null,
                    SyntaxFactory.EqualsValueClause(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(ClassDefinition.Codename)))
                )
            })
        )
    )
    .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
    .AddModifiers(SyntaxFactory.Token(SyntaxKind.ConstKeyword));

var propertyCodenameConstants = ClassDefinition.PropertyCodenameConstants.Select(element =>
    SyntaxFactory.FieldDeclaration(
        SyntaxFactory.VariableDeclaration(
            SyntaxFactory.ParseTypeName("string"),
            SyntaxFactory.SeparatedList(new[] {
                SyntaxFactory.VariableDeclarator(
                    SyntaxFactory.Identifier($"{TextHelpers.GetValidPascalCaseIdentifierName(element.Name)}Codename"),
                    null,
                    SyntaxFactory.EqualsValueClause(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(element.Codename)))
                )
            })
        )
    )
    .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
    .AddModifiers(SyntaxFactory.Token(SyntaxKind.ConstKeyword))
).ToArray();

So, first we are creating a public constant of type System.String for the code name of the content type itself. We already have access to it through the System property generated out of the box. But, it requires more typing, so we create a shortcut to it. The second piece of code generates public constants of type System.String for each property with the naming convention <FieldName>Codename.

So far so good…

Once we have extended the code to do what we want, we need to find a way to use it. To be honest, I was not able to create a new executable and neither to use this changed code directly when executing the existing CloudModelGenerator.exe file.

So I modified the local Cloud Generators Net solution a little bit further.

Change the output path of the compiled assemblies

Open the properties of the CloudModelGenerator project and navigate to the Build tab. In the Output path field, enter the location of the original folder where the CloudModelGenerator assemblies are located.

Step two, navigate to the Debug tab and add the following snippet into the Application arguments field.

-p "<KENTICOCLOUDPROJECTID>" -n "<NAMESPACE>" -o "<OUTPUTDIRECTORY>" –t

Now, if you execute the project (e.g. hit F5), it will generate code classes for your Kentico Cloud project in the targeted web application project. And you will be able to use strongly-typed code names…

Example of the generated constant members

Now, whenever you change a content type in Kentico Cloud and re-generate the code classes, you will get a broken build if any change has had a negative impact on your code base. So before you deploy to any target environment, you will be notified by your compiler.

That’s pretty much it…

Note: 
Of course this solution is not generic enough to cover multiple Kentico Cloud projects at the same time, due to the application arguments set in the CloudModelGenerator project. I hope this idea gets into the original project in a more feasible way. I tried (and still fail) to submit this idea as a pull request and I hope this blog post will get the right attention for it…

Update:
I managed to submit a pull-request which has been approved! So get the latest build and enjoy without making the changes mentioned above manually! :-)

Drazen Janjicek

By Drazen Janjicek | Principal Consultant 20.05.2017.