How toDeveloper Guide

Developer Guide

Sana Commerce 8.3
Your provider

Create a Custom Content Type

In this chapter you can read how to:

  • create a custom page template;
  • make the custom page template editable in the Sana Commerce backoffice;
  • show the created custom page template on the frontend;
  • enable the preview functionality for the custom page template;
  • make the new page template linkable in the link manager.

Follow the steps below to create a custom page template, for example 'Articles'.

Set up the data model for the new page template

You should set up the data model for the new page template in order to store the page type in the database:

Step 1: Create a table in the database (for example 'Articles'). It will contain the next columns:

Column Required Type Description
Id Yes uniqueidentifier The unique identifier of the page item. (Primary Key)
Title No nvarchar(50) The title of the page item.
WebsiteId Yes nvarchar(50) The ID of the website the page item is linked to.
Description No ntext The description of the page item.
Url Yes nvarchar(400) The page URL on the frontend. This page will be used to show the content of the custom page template.
CreatedDate Yes Datetime The date when the page item was created.
ModifiedDate Yes Datetime The last modification date of the page item.
Fields No xml The XML fields collection which can store additional properties.


Create the 'Articles' Table in the Database

Step 2: Create an interface with the 'IArticle' name and add it to the 'Sana.Commerce.Sdk' project. Add the properties needed for this interface to match the database table:

public interface IArticle : IInternalPage, IEntity, IVersionedItem
    {
        string Description { get; set; }
    }

This interface should be inherited from the following interfaces:

Interface Description
IInternalPage Identifies that the entity is a page and you can create links to this page in the Sana Commerce backoffice as to the internal page. This page can also be a part of the webshop navigation. It can have translations to other languages, stored in the 'ItemTranslations' table in the database.
IEntity This is the base interface for all entity types in the Sana Commerce framework.
IVersionedItem Identifies that this page item can be tracked by its 'CreatedDate' and 'ModifiedDate' properties.

Step 3: Add the 'Article' class to the 'Sana.Commerce.Content' namespace of the 'Sana.Commerce.Sdk' project. Inherit this class from the 'ContentItemBase' class and implement the 'IArticle' interface:

public class Article : ContentItemBase, IArticle
    {
        public string Title { get; set; }
        [PropertyContentType(PropertyContentTypes.Html)]
        public string Description { get; set; }
    }

In the example above the 'PropertyContentType' attribute is applied to the 'Description' property, thus it will be treated as HTML content by Content Manager.

When any page item is loaded from the database by Content Manager it gets processed by the 'IContentProcessor' instance. The 'IContentProcessor' instance processes all properties of the page item which have the 'PropertyContentType' attribute set. Depending on what is passed to the attribute constructor, the appropriate content processor is called to process the value of the property. All content processors are located in the 'Sana.Commerce.Web.Business.Utilities.ContentProcessing' namespace of the Sana Commerce framework.

Step 4: Add NHibbernate mapping to the 'Content.hbm.xml' file in the 'DomainModel\Content' folder of the 'Sana.Commerce.Sdk' project:

<class name="Article" table="Articles" polymorphism="explicit">
    <id name="Id" type="Guid">
      <generator class="guid" />
    </id>
    <property name="Title" type="string" length="50" not-null="true" />
    <property name="Description" type="StringClob" not-null="true" />
    <property name="Url" type="string" length="400" not-null="true" />
    <property name="WebsiteId" type="string" length="50" not-null="true" />
    <property name="CreatedDate" type="DateTime" not-null="true" />
    <property name="ModifiedDate" type="DateTime" not-null="true" />
    <property name="Fields" type="Sana.Commerce.Data.Types.DictionaryType, Sana.Commerce" not-null="false"/>
</class>
Make the page template editable in the backoffice

The step below will allow you to make the custom page template editable in the Sana Commerce backoffice:

Step 1: Locate the 'DataModel\Metadata' folder in the 'Sana.Commerce.Backoffice' project.

Step 2: In this folder create the 'Article_Metadata' class inherited from the 'ContentItemBase_Metadata' class:

[ContentItem(1)]
    [DisplayNameLocalizable("Article")]
    [DisplayNamePluralLocalizable("Articles")]
    [Previewable("Article")]
    [AllowCopyEntity]
    [UniqueUrl(ErrorMessageResourceName = "UniqueUrlError", ErrorMessageResourceType = typeof(Resources.DataModel))]
    public class Article_Metadata : ContentItemBase_Metadata
    {
        [UIFilter(UIFilterType.Contains, 0)]
        [UserDisplayMode(10)]
        [PreviewRequired]
        [DisplayNameLocalizable("Title")]
        [RequiredAttribute(ErrorMessageResourceName = "RequiredAttribute_ValidationError", ErrorMessageResourceType = typeof(Resources.DataModel))]
        [Localizable(true)]
        public object Title { get; set; }
        [UserDisplayMode(UserDisplayModes.All & ~UserDisplayModes.List, 50)]
        [DisplayNameLocalizable("MainDescription")]
        [HtmlContentRequired(ErrorMessageResourceType = typeof(Resources.DataModel), ErrorMessageResourceName = "HtmlContentRequiredAttribute_ErrorMessageFormat"),
        Required(ErrorMessageResourceName = "RequiredAttribute_ValidationError", ErrorMessageResourceType = typeof(Resources.DataModel))]
        [PreviewRequired]
        [Localizable(true)]
        public object Description { get; set; }
    }

The attributes used in the metadata class:

Attributes Description
ContentItem Indicates whether the item is a content page item.
DisplayNameLocalizable Specifies the item display name in the backoffice. Localization is supported. To store the translations, the 'DataModel.resx' resource file is used.
DisplayNamePluralLocalizable Specifies the plural display name of the item in the backoffice. Localization is supported. To store the translations, the 'DataModel.resx' resource file is used.
Previewable Indicates that the entity can be opened in a preview mode from the backoffice. The name of the route, which is also described later in this chapter, should be specified in the constructor parameter.
AllowCopyEntity Indicates whether it is allowed to copy the entity in the backoffice.
UniqueUrl Validates URL uniqueness of the page item within the current webshop.
UIFilter Used to filter the content pages by the data field to which this attribute is applied, for example 'Title'.
UserDisplayMode Used to determine on which pages and in which order the data fields of the content page should be shown in the backoffice.
UIHint Specifies the template or user-defined control which Dynamic Data must use to show a data field in the backoffice.
PreviewRequired Indicates that the property must be passed to the preview form. Can be applied only when the entity has the 'Previewable' attribute set.

Step 3: Register the 'Article' class in the 'RegisterTypes()' method of the 'WebApplication' class in the 'Sana.Commerce.Sdk' project:

ObjectManager.RegisterType<IArticle, Article>();

Step 4: Register the 'Article_Metadata' class in the 'RegisterMetadata()' method of the backoffice 'Global.asax' file:

MetadataTypeFactory.Register<IArticle, Article_Metadata>();

Step 5: To add the link to the 'Articles' module item, modify the 'web.sitemap' file which is in the root of the 'Sana.Commerce.Backoffice' project.

For example if you want to add the 'Articles' module item to the 'Content' module in the backoffice, insert the XML given below into the 'Content' section of the 'web.sitemap' file (the 'siteMapNode' with a specific URL "~/Landing.aspx?section=Content"):

<siteMapNode url="~/Content/Articles/List.aspx" title="$resources:SiteMap,Articles" image="Img/open-dark.png" isNavigationItem="true">
        <siteMapNode url="~/Content/Articles/Edit.aspx" title="$resources:SiteMap,EditArticle" showinnavigation="false" />
        <siteMapNode url="~/Content/Articles/Insert.aspx" title="$resources:SiteMap,NewArticle" image="Img/new-dark.png" />
</siteMapNode>

Notice, that the sitemap uses the resource file to store the text values. Therefore, you also need to add three new records to the 'SiteMap.resx' resource file. Open it in Visual Studio and add three records with the keys 'Articles', 'EditArticle' and 'NewArticle':


Add Records to 'SiteMap.resx'

After you complete the above steps the new menu item 'Articles' will be added to the 'Content' module of the Sana Commerce backoffice.

Create the ASP.NET page template on the frontend

The below steps will allow you to show the content page template on the frontend:

Step 1: Create the '.aspx' template.

Open the 'Sana.Commerce.Startersite' project. Add the new page template to the 'Content' folder, for example 'article.aspx'. The page class can be inherited from the 'StarterSitePage' class, which is the base class for all pages in the webshop. But in our case it is better to inherit from the 'ContentItemPreviewPage' class. This class encapsulates logic common to all content pages and also provides the preview functionality for the backoffice as this class, in its turn, is inherited from the 'PreviewPage' class.

Step 2: Retrieve the data from the database.

Retrieve the content item data from the database (for example 'Article'):

To retrieve an article by its ID you can use the 'GetContentItem' generic method of the Content Manager.  This method will return article by ID for the current webshop using the current language:

CommerceFramework.Content.GetContentItem<IArticle>(guidId);

To retrieve the list of articles you can use the 'GetContentItems' generic method of the Content Manager.  This method will return all articles for the current webshop using the current language:

CommerceFramework.Content.GetContentItems<IArticle>(options);

In our case the 'ContentItemPreviewPage' class, which is used as a base class for our page template, contains already the methods that handle the retrieval of the content item. Thus, we need only to call the 'GetContentItem' method.

All needed logic to retrieve a content item is already implemented in the base class. Use the following code to retrieve the data item from the database in order to show it on the page:

var page = GetContentItem();

For example, if you have labels with the 'Title' and 'Description' IDs on the template you can use the following code to fill it with data:

protected void Page_Load(object sender, EventArgs e)
        {
            var page = GetContentItem();
            if (page != null)
            {
                Description.Text = page.Description;
                Title.Text = FormatFullPageTitle(page.Title);
            }
}

Notice, that we do not specify any ID to identify the content item we want to get. This is because the ID is resolved internally using the ASP.NET routing engine. In order to make it work you need to configure the URL rewriting routine which is explained in the steps below.

Enable URL rewriting for SEO

These steps allow to create a search engine friendly URL for the content item. It increases the presence of the created content item in the search engine index:

Step 1: In the database 'Routes' table create a route record for the new page template:


Route for the New Page Template

In the table below the example of the created record and the description of the columns to which the values should be added are given:

Column name Description Value Remarks
Id The route identifier. Article  
Name The route name. Article  
RewrittenUrl The pattern for the virtual URL. {*url} Here the "url" wildcard parameter is set. This means that any request will match this route entry.
PhysicalUrl The path to the physical resource on the server to which the virtual URL is mapped. ~/content/article.aspx The path to the created page template.
Type The type of the route. In our case this is a content page template route. Template  
IsLinkable The value should be set to 'True' if the link can be created for the current route. true  
OrderNo The order number of the route. All routes will be registered in the order which is defined by the 'OrderNo' field. 7  

Step 2: Create a new class in the 'Sana.Commrece.Sdk' project and name it 'ArticleRouteAddon':

public class ArticleRouteAddon : IAddon, IRoutingAddon
    {
        public void Initialize()
        {
            ObjectManager.GetInstance<FrontendRoutesRegistrator>().RegisterAddon(this);
        }
        public void RegisterBefore(RouteCollection routeCollection, RoutesRegistrationStep step)
        {
            if (step == RoutesRegistrationStep.ContentRoutes)
            {
                routeCollection.Add(ContentItemRoute<IArticle>.RouteName, new ContentItemRoute<IArticle>());
            }
        }
        public void RegisterAfter(RouteCollection routeCollection, RoutesRegistrationStep step)
        {
        }
    }

This class must implement the 'IAddon' and 'IRoutingAddon' interfaces:

Interface Description 
IAddon

Implementation of this interface allows developers to easily plug their custom add-ons into the Sana Commerce framework. All that is needed is to implement the 'Initialize' method. On application startup the application domain is searched for all instances of the 'IAddon' type and for every instance its 'Initialize' method is called. This way it is possible to create a separate add-on, compile it as a separate assembly and drop it to the applications 'bin' folders. On application startup this add-on will be picked up automatically and if it implements the 'IAddon' interface it will be initialized.

In our case in the 'Initialize' method we register the class itself in the 'FrontendRoutesRegistrator' instance with the 'RegisterAddon' method. The 'FrontendRoutesRegistrator' class is responsible for registering all routes for the frontend application.

IRoutingAddon

In order to be passed to the 'RegisterAddon' method of the 'FrontendRoutesRegistrator' instance, the instance must implement the 'IRoutingAddon' interface providing the 'RegisterBefore' and 'RegisterAfter' methods.

The 'FrontendRoutesRegistrator' instance registers the routes step-by-step. Steps are defined by the 'RoutesRegistrationStep' enumeration. For each step the 'FrontendRoutesRegistrator' calls the 'RegisterBefore' method of the 'IRoutingAddon' interface, then registers the routes for this step in 'RouteCollection' and after that calls the 'RegisterAfter' method of the 'IRoutingAddo' interface.

In our case we implement the 'RegisterBefore' method: if the current registration step is the 'RoutesRegistrationStep.ContentRoutes' step we add 'ContentItemRoute' for the 'IArticle' type to the 'RouteCollection'.

To summarize all the actions taken: we have added the add-on which registers itself on application startup and which registers automatically 'ContentItemRoute' for the 'IArticle' type in the 'RouteCollecition'. With the database record added to the 'Routes' table the search engine friendly URL is created for the content page.

Enable the preview functionality

These steps allow you to use the preview functionality for the custom content page. After performing these steps you will be able to see how the content page will look even before saving it:

Step 1: The functionality which enables the frontend page to be previewable from the backoffice is encapsulated in the 'PreviewPage' class. Thus, you can simply inherit the page class from the 'PreviewPage' class and it will be enough to make the page previewable. In our case we inherit from the 'ContentItemPreviewPage' class, which in its turn is inherited from the 'PreviewPage' class, as it contains functionality common to all content pages.

public partial class Article : ContentItemPreviewPage<IArticle>

Step 2: Add the 'Previewable' attribute to the metadata type which is defined for the article type. The route ID must be passed as a parameter. The route defined in the 'Previewable' attribute is used to build the preview URL. To each property of the 'Article' class, which must be accessible in the preview mode, the 'PreviewRequired' attribute for the corresponding property in the metadata type must be added.

The example of the metadata type defined for the 'Article' class:

[ContentItem(1)]
    [DisplayNameLocalizable("Article")]
    [DisplayNamePluralLocalizable("Articles")]
    [Previewable("Article")]
    [AllowCopyEntity]
    [UniqueUrl(ErrorMessageResourceName = "UniqueUrlError", ErrorMessageResourceType = typeof(Resources.DataModel))]
    public class Article_Metadata : ContentItemBase_Metadata
    {
        [UIFilter(UIFilterType.Contains, 0)]
        [UserDisplayMode(10)]
        [PreviewRequired]
        [DisplayNameLocalizable("Title")]
        [RequiredAttribute(ErrorMessageResourceName = "RequiredAttribute_ValidationError", ErrorMessageResourceType = typeof(Resources.DataModel))]
        [Localizable(true)]
        public object Title { get; set; }
        [UserDisplayMode(UserDisplayModes.All & ~UserDisplayModes.List, 50)]
        [DisplayNameLocalizable("MainDescription")]
        [HtmlContentRequired(ErrorMessageResourceType = typeof(Resources.DataModel), ErrorMessageResourceName = "HtmlContentRequiredAttribute_ErrorMessageFormat"),
        Required(ErrorMessageResourceName = "RequiredAttribute_ValidationError", ErrorMessageResourceType = typeof(Resources.DataModel))]
        [PreviewRequired]
        [Localizable(true)]
        public object Description { get; set; }
    }

Now the step is completed and you can use the preview functionality for the custom content page template.

Make the new page template linkable in the link manager

These steps allow to use the link manager to link the custom content template:

Step 1: Create the 'ArticlesLinkProvider' class in the 'Sana.Commerce.Sdk.Links.Providers' namespace and inherit it from the 'ContentLinkProviderBase<T>' abstract class. Implement all abstract properties and methods:

Name Description
TableName The name of the database table for our content items.
Type The identifier of the link provider type which is used internally by the framework.
Name The link provider name. It will be shown in the backoffice dropdown where the link type can be selected.
Editable Indicates whether the data in a pop-up for internal link selection in the backoffice can be edited.

For example the 'ArticlesLinkProvider' class can be defined this way:

public class ArticlesLinkProvider : ContentLinkProviderBase<IArticle>
    {
        public override string TableName { get { return "Articles"; } }
        public override string Type
        {
            get { return "Article"; }
        }
        public override string Name
        {
            get { return "Article"; }
        }
        public override bool Editable
        {
            get { return true; }
        }
    }

Step 2: Register the 'ArticleLinkProvider' class in the 'RegisterLinkManagerProviders' method of the 'Sana.Commerce.Sdk.WebApplication' class. Add the following line to the 'RegisterLinkManagerProviders' method:

linkManager.AddProvider(new ArticlesLinkProvider());

Now the step is completed and you can see the custom page template in the link manager dropdown in the Sana Commerce backoffice.

How toDeveloper Guide