Kentico MVC Done the BizStream Way

By Ian TeGrootenhuis On October 07, 2019

Kentico MVC Done the BizStream Way
At BizStream we do things a little bit differently with the well-known Model, View, and Controller (MVC). We do use a Model and View, but we don’t use controllers to handle our basic pages. In addition to the Model and Views, we use Populaters and ViewModels. The Populaters is where we would put our “Controller” logic and it populates the ViewModels. We pass our ViewModels to the Views to render the populated data. In this blog post, I’ll walk through how the developers at BizStream use the Kentico MVC platform. 

Automate the Page Creation Process 

Creating the four new files and registering the Populater in LightInject to create a single page can be a bit time-consuming. So we wanted to make a simple to use node package manager (npm) tool that would automate that process and remove the chance for human error. The tool pulls the page type code to be used in the Model. Then the tool populates the ViewModel with the properties from the page type fields. The Populater and View are generated with some of our boilerplate code that is used across most files. 

Creating the Model

The npm tool pulls in the page type code via a powershell script. With the following script we are able to pull the page type code no matter the Kentico version; instead of having separate scripts for each version. 
 
# How to run:
#    powershell -ExecutionPolicy ByPass -File gen-page-type-code.ps1
#    powershell -ExecutionPolicy ByPass -File gen-page-type-code.ps1 -namespace "{Model namespace}" -class "{PageType.CodeName}" -webroot "{webroot}"

# Configure path to the root of your web project

param (
   [Parameter(Mandatory=$true)][string]$namespace  = $( Read-Host "Input namespace, please" ),
   [Parameter(Mandatory=$true)][string]$class      = $( Read-Host "Input class/pagetype, please" ),
   [Parameter(Mandatory=$true)][string]$webroot    = $( Read-Host "Input webroot, please" )
)

# force a slash at the end
$webRoot = $webRoot.Trimend('\') +'\'

$bin = $webRoot + "bin\"

# Load settings from web.config
[System.AppDomain]::CurrentDomain.SetData("APP_CONFIG_FILE", $webRoot + "web.config")

# Add DLL resolution path
[System.AppDomain]::CurrentDomain.AppendPrivatePath($bin);

Add-Type -Path ($bin + "CMS.Base.dll")

# get system version 8.2 or 10.0
$version = [CMS.Base.CMSVersion]::MainVersion
#$version

$devNull = "fake spot to dump warnings"

If ($version -ne 8.2) {
   $references = @(("CMS.MacroEngine.dll"),("CMS.WebAnalytics.dll")) | Foreach-Object{ $bin + $_ }
   $devNull = Add-Type -Path $references
}

# Load CMSDependencies
$cmsDependencies = $webRoot +"CMSDependencies\"
$packagesFolder = $webRoot +"..\packages\"
If ( Test-Path $cmsDependencies -PathType Container ) {
   $devNull = Get-ChildItem -recurse "$cmsDependencies"| Where-Object {($_.Extension -EQ ".dll")} | ForEach-Object { $AssemblyName=$_.FullName; Try {Add-Type -Path $AssemblyName} Catch{ "Failed to load assembly: " + $AssemblyName + " " + $_.Exception.LoaderExceptions}}
} ElseIf ( Test-Path $packagesFolder -PathType Container ) {
   $valuesToLookFor = "Castle.|Mono.Cecil|System.Web.Http"
   $devNull = Get-ChildItem -recurse "$packagesFolder"| Where-Object {($_.Extension -EQ ".dll")} | Where-Object { $_.FullName -Match $valuesToLookFor } | ForEach-Object { $AssemblyName=$_.FullName; Try {Add-Type -Path $AssemblyName} Catch{ "Failed to load assembly: " + $AssemblyName + " " + $_.Exception.LoaderExceptions}}
}

# create a list of assemblies references, then add them to the app domain
$references = @(("CMS.Base.dll"),("CMS.Core.dll"),("CMS.MacroEngine.dll"),("CMS.FormEngine.dll"),("CMS.DataEngine.dll"),("CMS.Membership.dll")) | Foreach-Object{ $bin + $_ }
$devNull = Add-Type -Path $references

$codeGenSource = @"
                   if ( ContentItemCodeGenerator.Internal.CanGenerateItemClass( classInfo ) )
                   {
                       code = CMS.FormEngine.ContentItemCodeGenerator.Internal.GenerateItemClass( classInfo );
                   }
"@

If ($version -eq 8.2) {
   $codeGenSource = @"
                       string formDefinition = classInfo.ClassFormDefinition;
                       var fi = new FormInfo(formDefinition);
                       code = FormInfoClassGenerator.GetDocumentType(className, fi);
"@
}

# Inline source
$source = @"
using System;
using CMS.Base;
using CMS.FormEngine;
using CMS.Membership;

namespace PageTypeGeneratorUtility
{
   public class CodeGenerator
   {
       public static string GenerateClassCode(string namespaceName, string className, string webRoot)
       {
           webRoot = webRoot.TrimEnd( '\\' );
           CMS.Base.SystemContext.WebApplicationPhysicalPath = webRoot;

           var user = UserInfoProvider.GetUserInfo("administrator");

           string code = "";

           // Sets the context of the user
           using( new CMSActionContext( user ) ) //{ LogSynchronization = false, LogWebFarmTasks = false } )
           {
               var classInfo = CMS.DataEngine.DataClassInfoProvider.GetDataClassInfo( className ); // "{PageType.CodeName}"

               classInfo.ClassCodeGenerationSettingsInfo.NameSpace = namespaceName; // "{Module NameSpace}"

$codeGenSource
           }

           return code;
       }
   }
}
"@

# $source # print source

# # Load the class to the PowerShell's AppDomain
Add-Type -TypeDefinition $source -ReferencedAssemblies $references -IgnoreWarnings

# Initialize application
Try {
   "Application initialized:" + [CMS.DataEngine.CMSApplication]::Init()
} Catch {
   # I am getting a DocumentEngine Dependency error, but it is not preventing us from running the code we need
   # so just catch it, and do not throw an error
   # "Quietly Error"
}

$code = [PageTypeGeneratorUtility.CodeGenerator]::GenerateClassCode($namespace, $class, $webroot)

#$code.Substring(0, 400) # show top X characters
$code

Creating the ViewModel, Populater, and View

When creating the ViewModel, we reference the page type code returned above and we grab the fields to create ViewModel's properties. The developer would trash the properties that we don’t need and keep the ones we do. With the Populaters and Views, the tool drops in some standard code.

With the Populaters and Views, the tool drops in some standard code

Efficiency Output

With the npm tool, we are cutting down on development time for building pages on our MVC projects. Our developers do the heavy lifting of creating the page type in Kentico, Then they let the tool generate the needed files; which only takes a minute or two. After the files are generated they would need to make some modifications to the code ( if any are needed), and they are done. This tool has helped us speed up our development, and I hope it encourages you to see the patterns that can be automated in your day- to- day development. 
 
 
 

Share This Post:

Twitter Pinterest Facebook Google+
Click here to read more Kentico posts
Start a Project with Us

About the author

Ian was raised in Allendale, Michigan and has lived there his whole life. He loves the location; being a few minutes from the Lake Michigan and downtown Grand Rapids in a small town is perfect. From an early age he fell in love with video games and wanted to develop them. In pursuit of following his dream he found Bizstream Academy, and signed up to gain some coding experience. Ian loved the Academy so much his passion changed to web development. In his spare time Ian still plays video games.

View other posts by Ian

Subscribe to Updates

Stay up to date on what BizStream is doing and keep in the loop on the latest with Kentico.