Security Matters

No matter what is the size of your website, your project is handling sensitive information or it’s just a feedback form, Cyber Security matters for all.

You are a novice or an expert, a developer, QA, or a solution architect, in your team. A team should have basic knowledge of cyber security.

In the project life cycle, security risks must be evaluated and decided by the solution architect or project lead before the start of the project. This can vary depending on the infrastructure, where the application will be running. We can divide security into different levels that require consideration.

Application Security

Hosting, gateway & network Protection

Voice, Access Security & Security Management.

Application security comes from the delivery team that is developing the API/website. How sensitive and serious is your team about security depends on the culture of the team. Some teams have a specific role for security experts within the team or a different department, but security starts from the developer. Security is not something that comes later after the site is ready for release or has been released. The team should take security into account right from their planning. In Agile methodology, the developer’s focus remains on solving the problem at hand and meeting the acceptance criteria of the story, and as a result, security aspects are easily ignored if those are not part of A/C.

We have different roles within a team. A developer develops something, but a member of QA can still find the bugs. It is because they are looking at the problem from a different perspective. The same goes for hackers they look at our applications with a different perspective and if someone is not looking within the same perspective as the hackers do, then the developer’s code becomes vulnerable. Developers must be aware of possible vulnerabilities and threats. OWASP helps and has provided a lot of material for possible vulnerabilities.

“Every developer in the team should have knowledge of at least OWASP Top 10 vulnerabilities and how those can be prevented.”

No one expects that everyone in the team is a security expert but stories that require serious security considerations should be reviewed by some senior wearing security expert hat.

CWE provides the list of the top 25 software weaknesses that can be considered also during the review. It will be ideal if the team is familiar with “Top 25 Most Dangerous Software Weaknesses”.

The team needs to familiarize also with GDPR, related aspects, those will also require consideration during planning. 

Dynamic Application Security Testing (DAST) tools are available that can be integrated into the CI/CD pipeline to audit code on a regular basis.

Just with small extra care and attitude, we can build more secure applications. Here is a security checklist for your EPiServer website.

How your site appears in google search results

Search engines now look deep into site contents and try to understand more about your site e.g. Google uses structure data to understand more about the content on your page. By providing structured data we not only can get the benefit of special features like rich snippets but also our site will be eligible to appear in graphic search results. By providing structured data you can outstand your site presence in the google search results and in result increase in clickthrough.

While searching keyword ‘eCommerce’ (from the UK, 5th of May 2020) I get the following results.

These are the highlights of the page

  1. Featured snippet
  2. Related Images
  3. Article
  4. Books
  5. People also searched for
  6. FAQ
    and the rest of the search results.
Google supports structured data in the following formats JSON-LD (recommended, use this format whenever possible), Microdata, and RDFa. Google also uses vocabulary, but for definitive, google search behaviors, rely on documentation provided on rather
There can be several items in your site for which structure data can be provided, e.g. article, breadcrumb, carousel, event, FAQ, logo, organization, or product. You can find examples of JSON-LD structured data code snippets from google here. A full list of vocabulary definition files can be downloaded from  
Implementation of structured data could be very simple to complex depending on your site and the way pages and blocks are structured in the EPiServer site, general structure data guidelines are available here. A reference implementation based on the package can be found in EPiServer foundation project. (Credits: Paul Gruffydd)

Add Angular component in your EPiServer site

Angular is a platform and framework for building client applications in HTML and TypeScript. Angular is written in TypeScript. It implements core and optional functionality as a set of TypeScript libraries that you import into your apps. It is easy to add Angular Components in our EpiServer MVC website with the following steps.

Prerequisite: Install the latest version of Node JS  before proceeding.

Install Angular CLI – ( npm install -g @angular/cli ) The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, and maintain Angular applications. You can use the tool directly in a command shell, or indirectly through an interactive UI such as Angular Console.

Create New Angular Component
Once prerequisites are installed, open the node js command prompt and browse to your alloy/quicksilver/EPiServer site root location. Create a new Angular application by running this command ng new firstEPiApp –minimal , Following below options will be asked, my preferences were as following

This will create a folder with name firstEPiApp under your project. To ensure every thing is setup correctly you can navigate to firstEPiApp and build the app, using command ng build, You should see this kind of output.

Move Folders and Files
New Angular App is created under a folder firstEPiApp. We will require to move a few folders and files as shown below, back into the root folder of projects to work further.

Src folder – Contains all source files requires to build component.
package.json – Contains the list of npm packages needed to develop the component.
angular.json – Contains the configuration settings for the Angular component. Angular-CLI requires this file to work smoothly.
tsconfig.json – Configuration file to compile TypeScript files into JavaScript.
node_modules – Contains all of the downloaded node modules.

After copying the file we can include in our project.

Adjust Configurations
As we have altered the location the way Angular CLI generates App, we will need to adjust a few configuration settings in tsconfig.json and angular.json

provides workspace-wide and project-specific configuration defaults for build and development tools provided by the Angular CLI. Path values given in the configuration are relative to the root workspace folder. We will need to adjust outputPath as required, the new path is scripts/libs, I created libs folder manually.

The presence of a tsconfig.json file in a directory indicates that the directory is the root of a TypeScript project. The tsconfig.json file specifies the root files and the compiler options required to compile the project. We will need to include config entry and set the src folder and adjust outDir.

Use in your View
After this adjustment at the command prompt run ng Build command again to make sure we haven’t broken anything. Now we are ready to use this component in our project

In _Root.cshtml I added following section @RenderSection(“scripts”, required: false)
 before the close of the body tag, to add angular scripts in razor views
As I am loading my Angular component on startup page, therefore I added following below code in StartPage/Index.cshtml

@section Scripts {
    <script type="text/javascript" src="~/Scripts/libs/runtime.js"></script>
    <script type="text/javascript" src="~/Scripts/libs/polyfills.js"></script>
    <script type="text/javascript" src="~/Scripts/libs/styles.js"></script>
    <script type="text/javascript" src="~/Scripts/libs/vendor.js"></script>
    <script type="text/javascript" src="~/Scripts/libs/main.js"></script>


and that’s  all

EPiServer CMS 11 Useful SQL Queries – 2

Here is a set of few queries that we have been using in different investigations

  • Get usages of EPiServer contents including pages and blocks
  • Check Table size
  • No contents have been added for following content types
  • Looking into Activity Logs
  • Unmapped Property List

Get usages of EPiServer contents including pages and blocks
  COUNT(tc.pkID) AS PageCount
  tblContent AS tc RIGHT OUTER JOIN
  tblContentType AS tct ON tc.fkContentTypeID = tct.pkID
Where tct.ModelType is not null and tct.ModelType not like ‘EPiServer.%’
  tct.Name, tct.ModelType
 PageCount desc

Check table size
EXEC sp_spaceused ‘tblBigTable’

No contents have been added for following content types
  tblContent AS tc RIGHT OUTER JOIN
  tblContentType AS tct ON tc.fkContentTypeID = tct.pkID
Where tct.ModelType is not null and tct.ModelType not like ‘EPiServer.%’
  tct.Name, tct.ModelType

Having COUNT(tc.pkID) = 0

Looking into Activity Logs
exec netActvitiyLogList @from=’2018-02-01′, @to=’2019-02-14′, @maxRows=10

Unmapped Property List

SELECT tblContentProperty.LinkGuid AS GuidID, tblContentProperty.fkLanguageBranchID AS LanguageBranchID, tblPageDefinition.Name AS PropertyName, tblPageDefinition.fkPageTypeID AS PageTypeID, tblContentType.Name, 

FROM     tblContentProperty INNER JOIN
                  tblPageDefinition ON tblContentProperty.fkPropertyDefinitionID = tblPageDefinition.pkID INNER JOIN
                  tblContent ON tblContentProperty.fkContentID = tblContent.pkID INNER JOIN
                  tblContentType ON tblContent.fkContentTypeID = tblContentType.pkID
WHERE  (tblContentProperty.LinkGuid IS NOT NULL) AND (tblContentProperty.ContentLink IS NULL)

EPiServer CMS 11 Useful SQL Queries – 1

Here is a set of few queries that we have been using in different investigations

  • Check How Big is your Database
  • Get Data for Each Property and from Each Content Type
  • Complete Tree Structure of your web site
  • Check EPiServer DB Version

Check How Big is Database
    t.NAME AS TableName,
    s.Name AS SchemaName,
    p.rows AS RowCounts,
    SUM(a.total_pages) * 8 AS TotalSpaceKB, 
    CAST(ROUND(((SUM(a.total_pages) * 8) / 1024.00), 2) AS NUMERIC(36, 2)) AS TotalSpaceMB,
    SUM(a.used_pages) * 8 AS UsedSpaceKB, 
    CAST(ROUND(((SUM(a.used_pages) * 8) / 1024.00), 2) AS NUMERIC(36, 2)) AS UsedSpaceMB, 
    (SUM(a.total_pages) – SUM(a.used_pages)) * 8 AS UnusedSpaceKB,
    CAST(ROUND(((SUM(a.total_pages) – SUM(a.used_pages)) * 8) / 1024.00, 2) AS NUMERIC(36, 2)) AS UnusedSpaceMB
    sys.tables t
    sys.indexes i ON t.OBJECT_ID = i.object_id
    sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
    sys.allocation_units a ON p.partition_id = a.container_id
    sys.schemas s ON t.schema_id = s.schema_id
    t.NAME NOT LIKE ‘dt%’ 
    AND t.is_ms_shipped = 0
    AND i.OBJECT_ID > 255 
    t.Name, s.Name, p.Rows


Get Data for Each Property and from Each Content Type
SELECT tblContentType.Name AS TypeName, tblContentLanguage.Name AS ContentName, tblContentLanguage.URLSegment, tblPropertyDefinition.Name As PropertyName, CASE WHEN tblContentProperty.Number IS NULL AND tblContentProperty.FloatNumber IS NULL AND 
                  tblContentProperty.ContentType IS NULL AND tblContentProperty.ContentLink IS NULL AND tblContentProperty.Date IS NULL AND tblContentProperty.String IS NULL AND tblContentProperty.LongString IS NULL 
                  THEN ‘Boolean: ‘ + CAST(tblContentProperty.Boolean AS varchar(40)) WHEN tblContentProperty.Number IS NOT NULL THEN ‘Number: ‘ + CAST(tblContentProperty.Number AS varchar(40)) 
                  WHEN tblContentProperty.FloatNumber IS NOT NULL THEN ‘FloatNumber: ‘ + CAST(tblContentProperty.FloatNumber AS varchar(40)) WHEN tblContentProperty.ContentType IS NOT NULL 
                  THEN ‘PageType: ‘ + CAST(tblContentProperty.ContentType AS varchar(40)) WHEN tblContentProperty.ContentLink IS NOT NULL THEN ‘PageLink: ‘ + CAST(tblContentProperty.ContentLink AS varchar(40)) 
                  WHEN tblContentProperty.Date IS NOT NULL THEN ‘Date: ‘ + CAST(tblContentProperty.Date AS varchar(40)) WHEN tblContentProperty.ContentType IS NOT NULL THEN ‘String: ‘ + CAST(tblContentProperty.String AS varchar(40)) 
                  WHEN tblContentProperty.LongString IS NOT NULL THEN ‘LongString: ‘ + CAST(tblContentProperty.LongString AS varchar(40)) ELSE CAST(‘Error Determining Value!’ AS varchar(40)) END AS ‘Property Value’
FROM     tblContent INNER JOIN
                  tblContentLanguage ON tblContent.pkID = tblContentLanguage.fkContentID INNER JOIN
                  tblContentProperty ON tblContent.pkID = tblContentProperty.fkContentID INNER JOIN
                  tblPropertyDefinition ON tblContentProperty.fkPropertyDefinitionID = tblPropertyDefinition.pkID LEFT OUTER JOIN
                  tblContentType ON tblPropertyDefinition.fkContentTypeID = tblContentType.pkID AND tblContent.fkContentTypeID = tblContentType.pkID

Complete Tree Structure of your web site
IF EXISTS (SELECT name FROM sysobjects 
WHERE name = ‘ShowHierarchy’ AND type = ‘P’) 
CREATE PROC dbo.ShowHierarchy ( @Root int ) AS BEGIN 
DECLARE @PageID int, @PageName varchar(30) 
SET @PageName = (SELECT tblContentLanguage.Name FROM dbo.tblContent inner join tblContentLanguage on tblContent.pkID = tblContentLanguage.fkContentID WHERE pkID = @Root) 
PRINT REPLICATE( ‘-‘, @@NESTLEVEL * 4) + @PageName 
SET @PageID = (SELECT MIN( pkID ) FROM dbo.tblContent WHERE fkParentID = @Root) 
EXEC dbo.ShowHierarchy @PageID 
SET @PageID = (SELECT MIN( pkID ) FROM dbo.tblContent 
WHERE fkParentID = @Root AND pkID > @PageID) 
ShowHierarchy 103–[Replace with HOME PAGE ID]


————page 1
————page 2
—————-page 21
—————-page 22
——————–page 221
————————page 2211
————————page 2212
————————page 2213
——————–page 222
——————–page 223
—————-page 23

Check EPiServer DB Version

DECLARE @db_status int;  
EXEC @db_status = dbo.sp_DatabaseVersion;  
SELECT ‘DB Status’ = @db_status;  


Get Specific Elements Blocks from EPiServer Forms

A small extension to get the specific form elements block with all properties rather relying on  FriendlyNameInfo with the limited set of properties.
Custom form element block:
public class HiddenExternalValueElementBlock : HiddenElementBlockBase
    [Display(Name = "Enable if its a special campaign")]
    public virtual bool MySpecialCampaign { getset; }

    [Display(Name = "Propperty 1")]
    public virtual bool Property1 { getset; }

    [Display(Name = "Property 2")]
    public virtual bool Property2 { getset; }


Extension Methods:

using EPiServer.Core;
using EPiServer.Forms.Core;
using EPiServer.Forms.Core.Models;
using EPiServer.Forms.Helpers.Internal;
using EPiServer.Forms.Implementation.Elements;
using System.Collections.Generic;
using System.Linq;

namespace PixieDigital.EpiServer.Extensions
    public static class FormExtensions
        public static IEnumerable<T> GetSpecificFormElements<T>(this FormIdentity formIdentity, bool filteredItemsOnly = truewhere T : ElementBlockBase
            return GetSpecificFormElements<T>(formIdentity.GetFormBlock(), filteredItemsOnly);

        public static IEnumerable<T> GetSpecificFormElements<T>(this FormContainerBlock formContainerBlock, bool filteredItemsOnly = truewhere T : ElementBlockBase
            if (formContainerBlock != null && formContainerBlock.ElementsArea != null && formContainerBlock.ElementsArea.Items != null && formContainerBlock.ElementsArea.Items.Count() != 0)
                var formElements = filteredItemsOnly ? formContainerBlock.ElementsArea.FilteredItems : formContainerBlock.ElementsArea.Items;

                foreach (var item in formElements)
                    T element = item.ContentLink.GetContent((formContainerBlock as ILocale).Language.Name) as T;
                    if (element != null)
                        yield return element;
Guid formGuid = "xxx-xx-xxxx";
var formIdentity = new FormIdentity(formGuid, "en");
var hiddenElement = formIdentity.GetSpecificFormElements<HiddenExternalValueElementBlock>();
//Use it for further processing
var formId = hiddenElement.FirstOrDefault().Content.ContentLink.GetElementName();
var definedElementName = hiddenElement.FirstOrDefault().Content.Name;
var mySpecialCampaign = hiddenElement.FirstOrDefault().MySpecialCampaign;                        

Azure based architecture for serving EPiServer CMS As Content Hub

This architecture uses the Azure API Management to provide access to content from EPiServer for different channels.
  • Contents are exposed via headless API and other Custom written APIs.
  • Editors and admins will have secure access to EPIServer CMS.
  • API Management is used to publish APIs to external, partner and channels, securely and at scale.
  • Application Insights is used to detect issues, diagnose crashes and track usages.
  • AAD – Azure Active Directory is used for secure, enterprise-grade authentication.
  • Traffic Manager is used to determining which web app is geographically best placed to handle each request, and will be used to obtain zero downtime. 
  • A CDN – content delivery network serves static content, such as images, script, and CSS for different channels.
  • Azure SQL DB/Redis Cache/Azure Blob Storage to server data about the site in a high performance and highly scalable way

Serialize IContent to use in Angular like front technologies

(A self note) A generic service to convert IContent into an Expando Object, ExpandoObject  can be converted into JSON to use in Angular/React components.
Find the code gist


  • not supporting multilingual
  • not preparing friendly urls

Few best practises while working with orders

  • Validate the status of line item (at required stage in sale flow) to make sure the product is active, within the valid date range, and that the catalog entry is available in the current market.
  • Validate the status of line item (at required stage in sale flow) to make sure business does not lose money.
  • It is important to add coupons to the IOrderForm before running apply discounts(cart.ApplyDiscount method)
  • Avoid saving carts unnecessarily and repeatedly.
  • You should avoid casting back and forth between concrete Order classes (OrderGroup, OrderForm, etc.) and the new abstraction interfaces (IOrderGroup, IOrderForm, etc.).
    For example, from the time you load the cart or purchase order until you save it, only use one API. While this is not always possible, doing it whenever possible helps avoid hidden problems when casting.
  • Do not use Math.Round() for rounding. The following methods return a value with the correct number of decimals, which is determined by the input currency.
    – Currency.Round()
    – Currency.Percentage()
    – Money.Round()
    After rounding, use the rounded value for all further totals. e.g
    var billingCurrency = order.Currency;
    foreach (var item in order.GetAllLineItems())
    var costWithoutDiscount = billingCurrency.Round(item.PlacedPrice * item.Quantity);
      item.Properties["costWithoutDiscount"] = costWithoutDiscount;
  • To get an order‘s correctly-rounded tax amount, do not use the tax amount of individual items. Instead, follow this example.
    var billingCurrency = order.Currency;
    var saleTaxesAmount = taxes
    .Where(x => x.TaxType == TaxType.SalesTax)
    .Sum(x => billingCurrency.Percentage(itemPriceWithoutTax, x.Percentage));
  • SerializableCarts provides a fast and efficient way to load, update, and save cart information. All components and custom json converters are internal, which means that the classes support the Episerver infrastructure, and are not intended to be used directly from your code.

Schedule Jobs revised

Schedule jobs has been revised in version 10.8. we have a new property Restartable on the ScheduledPlugIn attribute, that can be defined as following

[ScheduledPlugIn(Restartable = true)]

Jobs having property “Restartable=true” will make sure it can be re-run to completion in case of crashes before next schedule time. There are few considerations for developers before using this.

  • The job should also be implemented in such a way that it can be started repeatedly. 
  • If the job processes data, it should be able to continue where it was aborted. 
  • It is also recommended to implement a stoppable job, but be aware that the Stop method will only be called for controlled shutdowns which can be picked up by ASP.NET, and not for uncontrolled shutdowns such as an IIS crash or other external changes.
  • In the unlikely event the job is repeatedly cancelled, or the job it