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.
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.