Optimizely Feature Implementation Guide Process
- Page Owner: Steph Sommers
- Last Reviewed: 2025-09-09
Blend.Optimizely.Templates
The templates should act as a starting point for Optimizely projects. The Optimizely templates is a dotnet new template that can be installed via the Blend nuget server.
- To install the tool see the "Install template from package" section of the README.
- To create a project using the template see "Common installation for opti-12" section of the README.
GENERAL CODING STANDARDS
BUSINESS FOLDER
The Business folder is intended for business logic. Things like extension methods, services, tag helpers, initialization, scheduled jobs, ect. Blend organizes the Business folder by using a combination of standard MVC and feature-based approaches. Below are the common folders Blend will use in most projects with descriptions on what kinds of files go in each. Outside of these, folders will be created by feature, for example Search or Sitemap.
Common Folders
- Attributes
Code that inherits from
Attributeand generally applies code to a property within a model Ex: Templates Attributes - EditorDescriptors Generally modifies the way a property on a Page/Block behaves in the CMS. Ex: DateOnly
- Extensions https://bespin.blendinteractive.com/processes/development-process/best-practices-and-guidance/extension-methods-guidance/
- Initialization Code that is run and/or registered when the web app starts Ex: Templates Initialization
- Personalization
Any code related to changing output based on the user (
VisitorGroupCriterion, A/B testing, ect) - Rendering Code that turns data into html or serves up data specifically for a view. Ex: Templates Rendering
- ScheduledJobs
Code that is intended to run on a scheduled basis. Blend's most common use case is to bulk updates to content. If scheduled jobs are only intended to run once (clean up content or migrate content), it should be documented as such in the scheduled job code at the top of the file. Additionally a ticket should be created and scheduled to remove that code when it is no longer needed. Scheduled jobs must inherit from
ScheduledJobBaseorBlendJobBase(ifBlend.Optimizelyis installed) and should always include a GUID. Ex: Templates SiteMapGeneratorScheduledJob - Schema Code related to outputting schema.org JSON-LD (classes, extension methods, ect)
- SelectionFactories
Multi-use selection factories or selection factories implementing
ISelectionFactory. Blend prefers enum selection factories whenever possible, because they are easier to add/remove/update in the future. There some use cases forISelectionFactorysuch as when we need to dynamically generate a dropdown based on another data source. Ex: Templates SelectionFactories Also see: SELECTION FACTORIES - Services Generally services that are used by a controller as a way to reduce the logic in a controller.
- TagHelpers
Code that reduces logic in views and provide a consistent way of rendering common html components. Must inherit from
TagHelper. Ex: AnchorHeper - Validators
Editor validation to help the editors enter content. Inherits from
IValidate<T>Ex: PromoBlockValidator
Feature Folders
These folders are going to be project dependent, but in general, if a feature includes more than one file in multiple Business folders it deserves its own feature folder. Ex: Search
Selection Factories
- Single use enum selection factories should live in the page/block it is intended for. Ex: ChangeFrequency
- Muli-use selection factories should live in the
Business/SelectionFactoriesfolder. - When a selection factory is used to map an enum value to a css modifier class:
Ex: SelectionExtensions
- Add an extension method to
Business/Extensions/SelectionExtensions.cs - When possible use the switch expression matching to map values to css class values.
- Add an extension method to
MODELS
Common folders
- Blocks
Components that editor can drop into content areas. Inherits from
BlockData - Media
Media content type for files uploaded to the CMS (jpg, png, gif, svg, pdf, ect). Inherits from
MediaData - Pages
Pages that editors can created in the CMS. Inherits from
PageData - ProperyBlocks
Blocks (inherits from
BlockData) that are created to only be used as a property on another content type.AvailableInEditModeon theContentTypeattribute will befalsefor these blocks. Ex: Templates PropertyBlocks - ProperyList Classes that are used when creating a PropertyList property on a content model Ex: Templates PropertyList
Abstract Classes
Abstract classes in the context of Optimizely content models allow us to create a common set of properties that we want to appear on multiple content types. It also provides a common base type that we can use across the site for rendering common components, or searching for content across multiple types (and more). Here are some things to keep in mind when using (or choosing to create) abstract classes:
- Abstract class should be prefixed with "Base"
Ex:
BasePage,BaseTextPage,BaseBlock - Block and Page types should always inherit from an abstract class. Typically all projects will have
BasePageandBaseBlock. Try to avoid inheriting directly from another content type that will appear in the CMS. If the tech plan indicates that a content type will have the same properties as another content type, its probably worth creating a new abstract class.- Ex:
BioPageis aTextPagewith a few more properties. Create aBaseTextPageabstract class.
- Ex:
What about other model-like things?
- Interfaces
Put in the highest folder that the interface impacts. If an interface is a part of a feature that already has folder in the business folder, the interface should go in the feature folder.
Examples:
IHavePageHeaderis an interface for page types so it would go inModels/PagesfolderINavItemcould be a block or page so it would go in the rootModelsfolderIExcludeFromEPiServerFindis related to the Search feature inBusiness/Searchso it should go there Ex: Templates Search
- ViewModels
Put in a
ViewModelssubfolder within the folder the view model is related to. For example:ArticleListPageViewModelwould go inModels/Pages/ViewModelsVideoBlockViewModelwould go inModels/Blocks/ViewModels
VIEWS
Common folders
- Blocks Views for models in the Blocks folder
- Media Views for models in the Media folder
- Pages Views for models in the Pages folder
- Shared Partial views and layout files used across the site. Partial views are typically prefixed with an underscore (ex: _Header.cshtml or _Pagination.cshtml). Layout files typically go in a "Layouts" subfolder.
General Considerations
- Keep most logic in controllers, extension methods, or services where possible.
- Ex: Loading articles on the listing page
- Logic that directly dictates the markup should be in the view
- Ex: If there's an image output the markup for the image
COMMON DEPENDENCIES
The list of dependencies are ones that have been approved and are common in our projects. If you find a package that you think would be useful bring it to the Dev L-10 and we'll determine if we want to add to this list.
- A2Z.OrphanedProperties This Optimizely CMS plugin enables Administrators to identify and bulk delete any orphaned or missing properties (missing from code).
- Blend.Datastore (works w/ or w/o Dapper) This is a minimalist SQL migration system and access pattern. It provides rudimentary database migration capabilities, and a relatively simple pattern for executing queries and transactions against a database.
- Dapper Provides a simple and efficient API for invoking SQL, with support for both synchronous and asynchronous data access, and allows both buffered and non-buffered queries.
- DBLocalization Database driven localization that provides an editor interface so translations can be managed by editors
- EPiServer.PdfPreview Adds a preview of uploaded PDF files in edit mode
- Eshn.ContentTypeUsage This add-on adds a custom tool in EPiServer's Admin menu where user can search for instances of any content types along with their references.
- HtmlAgilityPack Library for parsing, selecting, manipulating, traversing, and writing html
- Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation For development only! Updates views real time in debug mode so you don't have to rebuild solution when there are only view changes.
- NodaTime Alternative date and time library.
- RestSharp RestSharp is a lightweight HTTP API client library. It's a wrapper around HttpClient, not a full-fledged client on its own.
- Stott.Optimizely.RobotsHandler This is an admin extension for Optimizely CMS 12+ for managing robots.txt on a per site and host definition basis. NOTE: Not upgrading past 3.x
- Wangkanai.Detection Library for screen detection. Commonly used for Optimizely Display Channel
- Yarp YARP is a library to help create reverse proxy servers that are high-performance, production-ready, and highly customizable.
FEATURES
CANONICAL URLS
A canonical URL is a way to tell a search engine that different URLs on a site are actually the same content. By specifying a single URL to represent both pages, you avoid problems with duplicate content. Below are some concrete examples of why a canonical URL is important.
Simple Address
In Optimizely page URLs are built off the page tree structure, but editors can specify a "Simple address" which allows a page to also be accessed by a shorter URL (think marketing campaigns).
Example
This page would be accessible from both URLs, but we'd set the canonical URL on both pages to "https://www.example.com/some/super/long/path/to/my-campaign" so that google and other bots recognize these as the same page.
Query Params
URLs can have query params and we need to decide if these params need to be included in the canonical URL or not. So how do we determine which params we need?
Valid Params
What we're calling "valid" params are params that change the content on the page and are unique from other values of that param. The best example here is pagination. Whenever we paginate results, users have to click through all of the pages in order to see all of the results. As such, we need to tell google and other bots that there is more content beyond page one, so each page would get its own canonical URL.
Example
Both URLs have different and unique content so each would get its own canonical URL.
Invalid Params
What we're calling "invalid" params are any params that if they exist in the URL we don't want a bot to crawl that page because the content on that page is already being indexed at a different URL and this page content is not the same as any other URL. The best examples of invalid params are category filters or search terms. Both examples could lead to near infinite URL combinations, but more importantly all of this content would already be indexed by our previous example (page param).
Example
While both of the above URLs are on page one, the second URL is filtering on a category. These pages will likely have drastically different content and as such we can't really say they are the same page. Since the content would likely already be indexed by the first URL having a canonical URL, we would add a <meta name="robots" content="noindex,nofollow" /> to the second URL.
Using Blend.Templates Canonical URL
If you are using the templates (as of 7/15/2024), you really only need to worry about CanonicalQueryParams and InvalidCanonicalQueryParams. Below explains how to use those properties in your project along with the underlying infrastructure so that you understand what's happening in the background.
Models/Pages/BasePage.csEx: BasePageCanonicalQueryParamsA list of query params keys as strings that should be include in a canonical URL. By default this will be an empty list. If a page type has query params, you'd override thisBasePageproperty on that page type model and add your param keys. Ex: SearchPageInvalidCanonicalQueryParamsA list of query params keys as strings that indicate a NOINDEX, NOFOLLOW meta tag. By default this will be an empty list. If a page type has query params, you'd override thisBasePageproperty on that page type model and add your param keys.
Ex: SearchPage
Business/Extension/CanonicalExtensions.csThis is where we determine what the canonical URL is. Ex: Templates CanonicalExtensions- If there's an invalid params return an empty string
- If a page shortcut is setup to another page on the site, pull that url
- Grab all valid params and values using the
CanonicalQueryParams - Sort params by key
- Return full URL with query params (if applicable)
Models/Pages/ViewModels/PageViewModel.csAdded a stringCanonicalUrlproperty so that we have a Canonical URL in the view for every page Ex: Templates PageViewModelBusiness/Rendering/PageContextActionFilter.csPopulatesCanonicalUrlon the view model using theGetCanonicalUrlextension method (inCanonicalExtensions.cs) Ex: Templates PageContextActionFilterViews/Shared/_HeadMetadata.cshtmlOutput canonical URL or tell bots not to follow Ex: Templates _HeadMetadata- If the the canonical URL has a value output it:
<link rel="canonical" href="@Model.CanonicalUrl" /> - Otherwise, don't follow the page:
<meta name="robots" content="noindex,nofollow" />
- If the the canonical URL has a value output it:
CUSTOM REPORTS IN CMS 12
Blend built an admin add-on for custom reports with styles in CHCO and Lurie CMS 12 projects. Please reference those project for any custom report needs or reference the Optimizely Templates
Reports structure