Sharing properties between content types, and action methods between controllers

It is a good idea to provide derived types from the Episerver content types PageData, BlockData and MediaData to share common properties among all the pages on the site. We should also consider to use that also on the controllers.

Considering the class diagram:

We can start to implement the Base[Classes] and Standard and Start page as:

using EPiServer.Core;
using EpiserverAdventures.Core.Constants;
using System.ComponentModel.DataAnnotations;

namespace EpiserverAdventures.Core.ContentTypes
{
    public abstract class BasePageData : PageData
    {
        [Display(Name = "Include in SEO Sitemap", Description = "Include on SEO sitemap.xml", GroupName = PageTabs.Navigation, Order = 1000)]
        public virtual bool IncludeInSitemap { get; set; }
    }
}
using EPiServer.Core;

namespace EpiserverAdventures.Core.ContentTypes
{
    public abstract class BaseBlockData : BlockData
    {

    }
}
using EPiServer.Core;

namespace EpiserverAdventures.Core.ContentTypes
{
    public abstract class BaseMediaData : MediaData
    {

    }
}

Therefore, we can start to create a standard page and define the start page for the site:

using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using EpiserverAdventures.Core.Constants;
using System.ComponentModel.DataAnnotations;
using EpiserverAdventures.Core.ContentTypes;

namespace EpiserverAdventures.Core.Pages
{
    [ContentType(DisplayName = "Standard Page", GUID = "1D1A9C91-3F47-4ACE-90E2-06CBDFABC81A", Description = "Non specific page. Base page for content.", GroupName = PagesGroups.Content)]
    public class StandardPage : BasePageData
    {
        [CultureSpecific]
        [Display(Name = "Blocks", GroupName = SystemTabNames.Content, Order = 1000)]
        public virtual ContentArea Blocks { get; set; }
    }
}
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using EpiserverAdventures.Core.Constants;

namespace EpiserverAdventures.Core.Pages.StartPage
{
    [ContentType(DisplayName = "Start Page", GUID = "BD710683-6F9D-4DA5-A2DC-5CFEF74723A8", Description = "Site start page", GroupName = PagesGroups.Content)]
    public class StartPage : StandardPage
    {
    }
}

I have decided to create a separated project from the episerver web project as:

Using the BasePageData model, every page will have the property “IncludeInSitemap” and active by default on page creation.

Update an existing page property

Using methods from IContentLoader or IContentRepository to get existing data, it will be returned as read-only version. If you try to update and save it, you will get a ReadOnly exception, although the model has the public set available

Since the API assume that most of the time are read operations, than it shares the instance object on multiple threads. This reduces the amount of short-lived objects and reduce the memory need for the website.

If you want to change programmatic the property, you need to access to a writable clone of the current instance object. Then you can change it and request to save it. The property IsReadOnly gives information about if is a read-only or cloned instance.

The following code, update a property named “MyProperty” with a new text and publish the page with the new content.

var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();

var contentReference = new ContentReference(23123);

var readOnlyPage = contentRepository.Get<ContentPage>(contentReference);

var clonedPageInstance = readOnlyPage.CreateWritableClone<ContentPage>();

clonedPageInstance.MyProperty = "The new value";

contentRepository.Save(clonedPageInstance, SaveAction.Publish, AccessLevel.NoAccess);

The SaveAction is an enum type of save to perform on the page data object

Member nameMember summary
NoneDo not save data.
SaveSave a page, leaving it in a checked out state.
CheckInSave and check in page, creating a new version only if necessary.
PublishPublish page, creating a new version only if necessary.
RejectReject a checked-in page.
ForceNewVersionFlag that is used to force the creation of a new version.
ForceCurrentVersionSave and check in page, always updating the current version
SkipValidationDoes not validate the data against IValidationService
DelayedPublishSave and check in page, creating a new version only if necessary and sets the content as delayed publish.
ActionMaskMask to clear Force… settings from SaveAction
GetOriginalTypeGets the Type of the current object, ensuring that the eventual type that could be generated by a proxy interceptor is ignored.

The AccessLevel determine the minimum access level that the current user must have to save the content.

Member nameMember summary
NoAccessNo access to an item
ReadRead access to an item
CreateCreate access for an item, i e create new items below this item
EditChange / create new versions of this item
DeleteDelete this item
PublishPublish/unpublish items and versions of an item
AdministerSet access rights for an item
FullAccessFull access for an item
UndefinedAccess level not defined.
GetOriginalTypeGets the Type of the current object, ensuring that the eventual type that could be generated by a proxy interceptor is ignored.

Add Episerver site from scratch

Settings:

Open Visual Studio and create a new Episerver project.

Select Empty Project

After press OK, you will have a new solution with a web project on it.

The connection string to a database is set to a localDb instance. Lets change to a SQL Express.

Before:

<connectionStrings>
    <add name="EPiServerDB" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|EPiServerDB_d6814c74.mdf;Initial Catalog=EPiServerDB_d6814c74;Connection Timeout=60;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
  </connectionStrings>

After:

  <connectionStrings>
    <add name="EPiServerDB" connectionString="Server=.\\SQLExpress;Database=EpiserverAdventures;Trusted_Connection=True;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />
  </connectionStrings>

Add EPiserver Forms to the project. Run on the Package Manager console:

Install-Package EPiServer.Forms -ProjectName EpiserverAdventures

Lets update all the need packages of the project.

Update-Package EPiServer.CMS -ProjectName EpiserverAdventures -ToHighestMinor

After getting the Episerver.Forms need to authentication and updated all the packaged, we can create the database on the SQLExpress that we had configured before.

Initialize-EPiDatabase

The database is populated with schema for episerver.

Update the database with latest schema need.

Update-EPiDatabase

Since we dont want to use Windows authentication to login, we need to change the provider and create a new sql user as administrator for our episerver account.

Add an entry to <appSettings>, as shown in the following markup:

<add key="EpiserverAdventures:RegisterAdmin" value="true" />

Find the element <membership> and set the defaultProvider to SqlServerMembershipProvider, as shown in the following markup:

<membership defaultProvider="SqlServerMembershipProvider" ...

Find the element <roleManager> and set the defaultProvider to SqlServerRoleProvider, as shown in the following markup:

<roleManager enabled="true" defaultProvider="SqlServerRoleProvider" ...

Add a new IInitializableModule module to the project:

using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using System.Configuration;
using System.Web.Security;
namespace AlloyTraining.Business.Initialization
{
    [InitializableModule]
    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class RegisterAdminInitializationModule : IInitializableModule
    {
        private const string roleName = "Administrators";
        private const string userName = "Admin";
        private const string password = "Pa$$w0rd";
        private const string email = "admin@EpiserverAdventures.com";
        public void Initialize(InitializationEngine context)
        {
            string enabledString =
                ConfigurationManager.AppSettings["EpiserverAdventures:RegisterAdmin"];
            bool enabled;
            if (bool.TryParse(enabledString, out enabled))
            {
                if (enabled)
                {
                    #region Use ASP.NET Membership classes to create the role and user
                    // if the role does not exist, create it
                    if (!Roles.RoleExists(roleName))
                    {
                        Roles.CreateRole(roleName);
                    }
                    // if the user already exists, delete it
                    MembershipUser user = Membership.GetUser(userName);
                    if (user != null)
                    {
                        Membership.DeleteUser(userName);
                    }
                    // create the user with password and add it to role
                    Membership.CreateUser(userName, password, email);
                    Roles.AddUserToRole(userName, roleName);
                    #endregion
                }
            }
        }
        public void Uninitialize(InitializationEngine context) { }
    }
}

Start the site, enter /EPiServer/CMS/, and confirm that you can log in as a CMS admin with the following credentials:

Username: admin

Password: Pa$$w0rd

Close the browser.

Modify or remove the entry in <appSettings> to disable the registration of Admin, as shown in the following markup:

<add key="alloy:RegisterAdmin" value="false" />