Zaius was destined to Optimizely

Optimizely(EPiServer)’s CDP journey that started from EPiServer Profile Store, destined to Optimizely Data Platform(Zaius). With the acquisition of Zaius, Optimizely (EPiServer) have a true and one of most sophisticated CDP into their product family. Partners and customers already having licenses of Visitor Intelligence should contact EPiServer/Optimizely support to know more about migrations plans.

Visitor Intelligence was a good product from many aspects but has some issues. Such as, segmentation is a key functionality of any CDP to look at customers as a group based on their demographical and behavioral data collected from different touchpoints. With Visitor Intelligence real-time event-based segmentation was not possible due to the 24-hour caching of data in the segments. Nearly real-time (5 minutes delay) segmentations were possible only on Profile, which means, any data that requires instant decision has to go into Profile object, making the profile object in itself a data store. Generally, even based segments caching is ok for many scenarios, but personalization based on real-time ‘events based’ segmentation wasn’t possible. E.g. for a case like “Personalise my banner if the user is visiting page X”. Within visitor intelligence we need to create a segment using KQL query on data available in ‘epiPageView’. A user visiting the next day was able to see that banner.

ODP isn’t just for segmentation, personalization, or data centralization, that gives a 360 view of customer journeys by bringing all the Optimizely and external data of customers together, from all the journeys and touchpoints to build a strong foundation for analytics and segmentation, by segmentation I mean real-time segmentation.

https://www.zaius.com/
https://world.episerver.com/documentation/developer-guides/profile-store/profile-store-api/segments/#caching
https://world.episerver.com/forum/developer-forum/episerver-personalization/thread-container/2021/5/realtime-personalisation-based-on-events/

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 schema.org vocabulary, but for definitive, google search behaviors, rely on documentation provided on developers.google.com rather schema.org.
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 schama.org.  
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 schema.net 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.

Installations
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

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.



tsconfig.json, 
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>
}

<app-root></app-root>

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
SELECT
  tct.Name, 
  tct.ModelType, 
  COUNT(tc.pkID) AS PageCount
FROM
  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.%’
GROUP BY
  tct.Name, tct.ModelType
ORDER BY
 PageCount desc

Check table size
EXEC sp_spaceused ‘tblBigTable’

No contents have been added for following content types
SELECT
  tct.Name, 
  tct.ModelType
FROM
  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.%’
GROUP BY
  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, 

                  tblContentType.ModelType
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
SELECT 
    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
FROM 
    sys.tables t
INNER JOIN      
    sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN 
    sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN 
    sys.allocation_units a ON p.partition_id = a.container_id
LEFT OUTER JOIN 
    sys.schemas s ON t.schema_id = s.schema_id
WHERE 
    t.NAME NOT LIKE ‘dt%’ 
    AND t.is_ms_shipped = 0
    AND i.OBJECT_ID > 255 
GROUP BY 
    t.Name, s.Name, p.Rows
ORDER BY 

    t.Name

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’) 
DROP PROCEDURE ShowHierarchy 
go 
CREATE PROC dbo.ShowHierarchy ( @Root int ) AS BEGIN 
SET NOCOUNT ON 
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) 
WHILE @PageID IS NOT NULL 
BEGIN 
EXEC dbo.ShowHierarchy @PageID 
SET @PageID = (SELECT MIN( pkID ) FROM dbo.tblContent 
WHERE fkParentID = @Root AND pkID > @PageID) 
END 
END 
go 
ShowHierarchy 103–[Replace with HOME PAGE ID]

go

Output
—-Home
——–PagePlaceHolder1
————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;  

GO 

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.
Example:
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;
                    }
                }
            }
        }
    }
}
Usage:
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
References:

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 https://gist.github.com/khurramkhang/e8d4b9ef093fe30610be986efa352744

Limitations:

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