Monday 8 February 2021

Basic Episerver Coding Best Practices

This is my first post on Episerver development best practices. In this one, I'll present the best practices that should be followed while working with Episerver - ASP.Net MVC framework. 
Here are some best practices you should always have in mind:

A. General Coding Conventions

Compare strings with “==” operator 

The standard C# coding practice advises that string comparison should be done with string.Equals() method, because “==” operator compares strings base on reference equality, and that could lead to unexpected behaviours. 
Use string.Equals() method. 

Remove Unused Using Statements

 Remove all unused using statements.

Simplify Names

Names should be simplified (for example change String -> string, Boolean -> bool, remove leading namespace if the namespace is already declared in using statement).
  • The naming convention for methods, variables, and parameters 
Check all class to make sure the naming follows C# convention. Method and class names should follow Pascal Case, variable and parameter names should follow the Camel Case. Consider changing them for better readability and maintainability. 

High Usage of String Concatenation

The high number of string concatenations generally leads to poor memory usage because the string is immutable. Every time a string is modified, a new instance is created in the memory and that will lead to too many call to memory allocation. To optimize memory usage on string operations, make use of StringBuilder class if there are more than a few concatenations needed in each code block. 

Move hardcoded strings to constants

Consider moving hardcoded strings to constant classes or resource files to avoid duplication and centralize their management. Strings to process the program flow should be moved to constant, while strings used to display messages should be moved to resource files to enable localization.

Hardcoded service’s URL and sensitive information 

There might be some places in the code which have hardcoded strings to store sensitive information like username, password, encryption key and the service’s URL. This information should be moved to the application settings file (web.config) instead. 

Move business Logic from Views 

In the principal domain, logic should go into the model or view model and application logic into the controller. Business logic in views is against MVC principal and it has serious performance implications. 

Empty files & classes 

 Empty files & classes (if any) should be removed for better maintainability. 

Unused classes & methods 

Unused classes & methods (if any) should be removed for better maintainability. 

Potential code quality issues 

Special attention needs to be given for code quality issue witch regards to following 
  •  Variables with possible ‘System.NullReferenceException’. 
  •  Uses empty general catch class suppresses any errors 
  •  Replace with 'String.IsNullOrEmpty' 
  •  String formatting method problems 

Unused properties

Remove unused properties form the blockTypes pageTypes e.t.c 

Move static methods to extension classes

Static methods which operate on some type of data and placed across the code files and the methods are not related to the business logic of their classes should be organized as extension methods instead. 

Use View State to store large information

Avoid using view state. Sometimes the website makes use of ASP.NET View State to store a certain amount of information, sometimes include large objects such as data table. Objects stored in View State are rendered to a hidden field in the HTML output of the web page, and this increases the size of the request. In case the View State gets too large, it could be blocked by proxy servers. 

Implement IDisposable

Use where required. 

JavaScript and CSS code are embedded in razor views (.cshtml) 

Some performance gains can be achieved by minifying the embedded JavaScript and CSS inside the razor views. 

B. Episerver Best Practices

Hide information not used frequently by editors 

To create pages of type “500 page”, “400 page” and so on is not the daily work for editors. It should be better to just show them to a smaller specific group that is responsible to set up the site. This can be done by restricting the access to create that page type to a certain group of editors or administrators.  

Obsolete EPiServer Class/Methods

In each major version, old obsolete methods are removed permanently to make sure that the API is kept clean and usable over time, so even if you can postpone fixing warning messages, it is good practice to make sure all warning messages are fixed before upgrading to a major version. 

Memory leak due to constructing new DataFactory class

DataFactory is a singleton class (which mean one instance for entire application), and it should not be constructed. In fact, EPiServer coding practice does not advice developer to use DataFactory directly, and the developer should use an injected instance of IContentRepository instead. 

EPiServer FindPagesWithCriteria problem

Due to its lack of developer usability and relatively slow performance it's a method many developers love to hate. Under the hood, FindPagesWithCriteria builds a bunch of SQL statements that query the database directly. There is No caching when you use this. When you use FindPagesWithCriteria, you need to be careful that you don't overuse it or better avoid using FindPageWithCriteria altogether.  

Enable ContentOutputCache

Caching can dramatically increase the performance of your website. [ContentOutputCache] you can set a much higher duration for your output cache. The cache will be invalidated as soon as an editor change the page and the visitor will see the updated information. ContentOutputCache attribute is commented out in Page controllers.

Use inline Blocks instead of custom properties

Its easier to manage and localize blocks instead of custom properties. Create inline Blocks e.g for CTA any settings AvailableInEditMode = false 

Overuse of ServiceLocator

The most preferable ways to do the dependency injection in Episerver is to use Constructor injection whenever possible
  
public class DummyPageController : PageControllerBase<DummyPage>
{
        private readonly IContentRepository _contentRepository;
        private readonly ContentAssetHelper _contentAssetHelper;
        
        public DummyPageController(
            ContentAssetHelper _contentAssetHelper, 
            IContentRepository contentRepository)
        {
            _urlResolver = _contentAssetHelper;
            _profileService = _contentRepository;
        }
        
        public ActionResult Index(AccountPage currentPage)
        {
             var startPage = _contentRepository.Get<StartPage>(ContentReference.StartPage);
            var assetsFolder = _contentAssetHelper.GetOrCreateAssetFolder(currentPage.ContentLink);
            return View();
        }
}        
  
Moving forward with the issue with ServiceLocator, to create objects with the service locator cost more than a normal creation with the “new” operator. So, performance gains can be archived if the ServiceLocator is just used once for a page view. 
Example
  
public class DummyPageController : PageControllerBase<DummyPage>
{
    public ActionResult Index(DummyPage currentPage)
{ var startPage = ServiceLocator.Current.GetInstance<IContentRepository>().Get<StartPage>(ContentReference.Stat… var dummySites = ServiceLocator.Current.GetInstance<IContentRepository>().GetChildren<ContentPage>(startPage… … foreach (var site in dummySites) { var pages = ServiceLocator.Current.GetInstance<IContentRepository>().GetChildren<SamplePage>(site.C…

The Problems with Service Locator

Using the snippet of code above is all very good but, in a project, you might have multiple files that need to call IContentRepository. Having this code duplicated also means you will need to write the same tedious set-up code in your unit tests everywhere. 

EPiServer also provides an alternative way of injecting dependencies into your code, via property injection and constructor injection. Injected<> uses property injection via the IoC container to give you access to your dependencies.
An example of how to use Injected<> can be seen below:
  
public class DummyExample
{
	internal Injected<IInterface> interface;
	public DummyExample()
	{
		var implmentation = interface.Service;
	}
}
  
Injected is useful when you need to implement from things like ISelectionFactory or an InitializationModule. If you try to use constructor injection in these types of files, MVC will complain about not having a parameterless constructor. 

EPiServer supports constructor injection for controllers. It is possible to inject dependent objects in your EPiServer controllers if you have configured it properly - created MVC dependency resolver. 

Try to minimize the construction of objects when using the ServiceLocator or better use Injected or constructer injection. 

Implement EPiServer Schedule Jobs Stop Signal 

Stop signal in EPiServer Schedule Jobs should be periodically checked (in a loop for example) so that the execution can be terminated a short time after if the stop signal has been received. 
Periodically check the stop signal in the longest-running code block (the loop). Stop the execution if the stop signal has been received.

Overuse of PageTypes

Overuse of PageTypes in the system, resulting in code duplication. The ideas of using block types in EPiServer to reduce code duplication and rely on fewer page types. If multiple pages serve the same layout but different content, there should be one-page type and different blocks should be created to serve different content.
Use abstract class if there is a need for same looking multiple PageTypes.

Unused PageTypes

It is important to keep the CMS clean and it is recommended to clean up the system from the unused page and block types. 

EPiServer Translation 

EPiServer platform provides the flexibility of translating the hard-coded strings to multiple languages with resource XML files. Currently, the content type names, property names, descriptions and label texts on the pages are hardcoded with fixed strings. The values should be taken from the resource XML files instead, as this would better support a global website with multiple languages. 

Overuse of Block Controllers

Simple blocks without processing logic inside their controllers should not have dedicated controllers as this will degrade the performance. 

Avoid dynamic properties


Register UIDescriptor 

where you disable on-page edit and preview views

For settings kind of pages, ensure that it will not have a template


Use Episerver’s Object Cache instead of .NET’s built-in cache


For property (field) names in code, use standard .NET PascalCase. 

Make sure to set a friendly Display Name and Description

Set default values for Content Types’ Properties (if known)


For media properties, use the ContentReference type and the appropriate UIHint


Use the PropertyFor method to render properties (fields) in ContentType views


Use Container Pages for folder nodes, without presentation (if required) 

use IContainerPage

C. EPiServer Best Practices for Cloud Development & Deployment

Replace log4net with EPiServer.Logging 

Episerver DXP redirects all Episerver logs into .NET Diagnostic Trace if the code uses the Episerver logging abstraction EPiServer.Logging.LogManager. As the application is using log4net and deploying to DXP, replace log4net with EPiServer.Logging.

Reference:  


Keep a look at system requirements

Some of the EPiServer Add-ons, features or components are not compatible with Microsoft Azure, and it is recommended to keep a look at the system requirements of EPiServer Azure deployment to select appropriate add-ons, components or to choose a suitable approach of development.

 Take a look at EPiServer Azure (DXP) system requirements, remove incompatible add-ons, features and components. The system requirements are available at this reference link: http://world.episerver.com/digital-experience-cloud-service/requirements/ 

Avoid code that depends on specific on-premise resources 

 Code that was developed to run on a cloud website should not have any specific dependency on the on-premise resources, such as SMTP server, writing to local files and folders. Session state should be avoided, and if it is impossible to avoid the use of session state, Azure Session Affinity or other optimized session state providers for Azure should be considered.

Try to avoid any code that depends on specific on-premise resources, consider alternative options for them in Azure environment (for example, emails can be sent from SendGrid service, any logic which writes to the local files/folders should now use Azure Blob Storage instead) 

Security 

Read the following documentation for the level of security EPiServer provides https://world.episerver.com/documentation/developer-guides/CMS/security/
 
 It is recommended some level of protection should be there in the website against click-jacking. 

 Install and configure the NuGet package called NWebsec (https://www.nuget.org/packages/NWebsec)

About the Author

Adnan Zameer, Lead Developer at Optimizley UK, is a certified Microsoft professional, specializing in web app architecture. His expertise includes Optimizley CMS and Azure, showcasing proficiency in crafting robust and efficient solutions.

0 comments :

Post a Comment