Tag Archive for EPiServer

Quick Summary of Dojo Mixins in EPiServer

Scratching behind Dojo’s ears

There are some excellent blog posts about EPiServer’s implementation of Dojo Dijits, most of which have been collected on David Knipe’s equally excellent blog.

I’ve been scratching around Dojo a bit myself lately and found the above examples invaluable. I also made my own notes regarding the use of mixins as it wasn’t clear at first where everything was coming from. Essentially all I’ve done is describe the most common mixins used in an EPiServer dijit widget. Hopefully someone will find these in some way useful.

_base/declare

The essential mixin. Every widget you build will use this unless you’re up to something really quite unusually scary. It provides the base dojo functionality which further mixins and your own code can build on.

_WidgetBase

In most circumstances you will also use this mixin. This provides the more commonly used base widget functionality, and crucially also provides the lifecycle events you will probably want to subscribe to at some point. These are detailed in the linked reference guide, but of special note is postCreate. This is called after the widget has been rendered, making it a very useful time to introduce your own code.

_TemplatedMixin

You will probably want to use this too as it saves you from the complexity of implementing Dojo’s buildRendering method yourself. Markup for your widget can be supplied inline or via a file. For the latter option you will need to reference the dojo/text! plugin.

dojo/html

Not essential, but useful. While simple use-cases can be adequately catered for using innerHtml directly, this mixin abstracts some of the fiddlier stuff away to provide easier cross-browser compatibility.

dojo/parser

Used alongside dojo/html, this can be used to convert DOM nodes directly into dijits or widgets. Note that this declarative use isn’t recommended, mostly due to potential performance issues. Generally it’s best to leave it for Dojo to use in the background (it’s a requirement of _TemplatedMixin so the odds are you’ll be using it somewhere even if you don’t realise it.)

_ContentContextMixin

The first non-standard Dojo mixin you’re likely to use with EPiServer. This unsurprisingly provides access to some basic content information, largely encompassing what we’d expect to get from a ContentData object in the back-end code. It also provides some events which are useful for hooking into when the current context changes.

Scratch deeper

EPiServer provides a good selection of mixins to use in your widgets. It’s always worth checking through them before embarking on rolling your own functionality as it may be that the heavy lifting has already been done for you.

Using local blocks to refine EPiServer’s on-page editing experience

The on-page editing interface introduced with CMS 7 is, on the whole, quite nice.* It allows fast, intuitive editing of properties in most situations. However on busier pages it can all get a bit out of hand. The editor bounding boxes start to overlap and the screen begins to resemble that old Amiga demo that draws rectangles on the screen at (as then) mind-blowing speeds. There are also some properties which don’t lend themselves to on-page editing in isolation. Links for example, typically use two properties in tandem for the text and the URL. Fortunately there’s a way to tidy things up, and that is to use local blocks

Local blocks allow you to group properties together so that they can be edited together. As an example, let’s say we have a promotional box on our home page. It shows a title and a link over a background image and has the following properties:

  • Title
  • Image
  • Link URL
  • Link Text

If these are defined in the home page model, then on-page editing becomes tricky. If it’s enabled for the image, its bounding box will cover the title and link making them uneditable. And even without that, there isn’t a direct way editing the Link Url. We could just let the editors use the all properties view, but that’s not very friendly.

Instead, we can create a local block called, imaginatively called PromoBlock, add the properties there, then add the local block to the home page. Now we can do this in the home page’s view:

<div class="promo" @Html.EditAttributes(x => x.CurrentBlock.Promo)>
  <div style="background: url(@Model.Promo.Image)>
    ... other promo markup ...
  </div>
</div>

Setting EditAttributes using the PromoBlock as a property will result in an edit box being drawn around the whole promo. Clicking on the promo will pop out the right-hand edit panel with all four of our PromoBlock properties there, ready to edit.

That isn’t all we can do though. We can also create a display template for the PromoBlock. To do this, create a new view in your project’s DisplayTemplates folder. This will typically be found at Views\Shared\DisplayTemplates. Give the View a model with a type of PromoBlock, and move all our PromoBlock markup from the home page’s view, like so:

@model PromoBlock

<div class="promo">
  <div style="background: url(@Model.Image)>
    ... other promo markup ...
  </div>
</div>

Now we can reduce the promo block code in the home page to this:

@Html.PropertyFor(x => x.CurrentPage.PromoBlock)

Which is rather neat. Another advantage of this is that should the promo block be used elsewhere, there’s no need to duplicate the markup. Just add a local PromoBlock property to the page in question and use PropertyFor with it.

*Let’s gloss over the pain and anguish of Dojo for now.

Conditionally hiding properties from editors in CMS 7

Completely hiding properties from editors is simple enough – just add the [ScaffoldColumn(false)] attribute to the property. However there are times when I want to show the property in some situations, and hide it in others. A typical scenario is sharing a local block between several page types. For example, let’s say we have a local block called PromoBlock. PromoBlock has two properties:

  • Title
  • Image

It is used on two page types, LandingPage and ContentPage.

However it has become vital that the PromoBlock on LandingPage has an optional video. We could add the property VideoUrl to PromoBlock, but then it would be presented to editors on the ContentPage, and we don’t want a video there.

One option is to simply make another block for this purpose. In most situations this is the route I’d advise, but it isn’t always the most appropriate. My example is deliberately simplified, and in reality PromoBlock could be used on many pages and have complex behaviour. It could also already be in production and used on many pages, making its replacement a tedious editing task.

Another option is to programmatically hide the VideoUrl property on the ContentPage. In CMS 6 this was easily achieved using the EPiServer.UI.Edit.EditPanel.LoadedPage event. This is no longer available in CMS 7, so we need a different approach. That approach is to use an EditorDescriptor:

[EditorDescriptorRegistration(TargetType = typeof(Url))]
public class HidePromoVideoUrl : EditorDescriptor
{
  public override void ModifyMetadata(
    ExtendedMetadata metadata,
    IEnumerable&lt;Attribute&gt; attributes)
  {
    base.ModifyMetadata(metadata, attributes);
    if (metadata.PropertyName == "VideoUrl" && metadata.Parent.ContainerType == typeof(ContentPage))
    {
      metadata.ShowForEdit = false;
    }
  }
}

The main points of interest here are:

  • EditorDescriptorRegistation attribute needs its TargetType setting to the type of the property we are modifying. In this case it is a Url.
  • metaData.Parent.ContainerType gives the type of the page containing the local block which in turn contains the property we are modifying. metaData.ContainerType would give the type of the block itself, in this case PromoBlock.
  • Once we’ve determined we’re in the right place, hiding the property is a simple matter of setting metadata.ShowForEdit to false.

Restricting blocks in content areas

EPiServer 7.5 introduces the AllowedTypes attribute. This accepts an array of types, effectively making a whitelist of blocks that can be added to a ContentArea. An editor attempting to drag and drop a block not included in the type array will see the block turn grey and will not be able to place it. Here’s an example of its use:

Display(
         Name = "My Content Area",
         GroupName = SystemTabNames.Content,
         Order = 100)]
        [AllowedTypes(new[] { typeof(AllowedBlock), typeof(AlsoAllowedBlock) })]
        public virtual ContentArea MyContentArea { get; set; }

However there is a problem with this attribute. It only restricts block placement when dragging and dropping existing blocks in a content area. An editor can still create a new block directly on the content area, which is frankly a bit of a headache, as we can’t rely on the attribute to enforce block placement rules.

A crude workaround

I’ve worked around this issue by creating a custom validation attribute for content areas. This will prevent content from being saved if a content area contains a disallowed block. Here’s the code:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class AllowedBlocksAttribute : ValidationAttribute
    {
        private readonly Type[] _allowedBlocks;

        private List AllowedBlockTypeFullNames
        {
            get { return _allowedBlocks.Select(a =&gt; a.FullName).ToList(); }
        }

        public AllowedBlocksAttribute(Type[] allowedBlocks)
        {
            _allowedBlocks = allowedBlocks;
            ErrorMessage = "This content area can only accept the following block types: {0}";
        }

        public override string FormatErrorMessage(string name)
        {
            return string.Format(CultureInfo.CurrentCulture, ErrorMessage, FormattedAllowedBlockTypes);
        }

        public override bool IsValid(object value)
        {
            var contentArea = value as ContentArea;
            if (contentArea == null)
                return true;

            foreach (var item in contentArea.Items)
            {
                if (!AllowedBlockTypeFullNames.Contains(item.GetContent().GetOriginalType().FullName))
                    return false;
            }

            return true;
        }

        private string FormattedAllowedBlockTypes 
        {
            get
            {
                return string.Join(", ", _allowedBlocks.Select(s =&gt; s.ToString().Split('.').Last().ToCamelCase()));
            }
        }
    }

This functions like any other validation attribute. If validation fails while saving content, a notification is displayed in the notification area at the top right of the page. It complements the existing AllowedTypes attribute, so ideally it should be used wherever that attribute is placed, eg

Display(
         Name = "My Content Area",
         GroupName = SystemTabNames.Content,
         Order = 100)]
        [AllowedTypes(new[] { typeof(AllowedBlock), typeof(AlsoAllowedBlock) })]
        [AllowedBlocks(new[] { typeof(AllowedBlock), typeof(AlsoAllowedBlock) })]
        public virtual ContentArea MyContentArea { get; set; }

Suggested improvements

There’s some scope for improvement here. Firstly, having to add two attributes with the same array of allowed types is somewhat clunky. Any suggestions as to how I could combine this with the existing attribute are welcome.

Secondly, although it stops content being saved with unwanted blocks, it doesn’t prevent an editor from creating said blocks. So although it acts as a final gatekeeper, it can lead to a frustrating experience for editors. It would be better if there’s a way of preventing the editor from creating the block in the first place. Ideally the disallowed blocks would not be available in the list of blocks when creating one on the content area, but I haven’t figured out a way of doing that yet.

Finally, you may have noticed that I’m crudely constructing a block name for display in the error message from its type name. This is because I couldn’t work out how to get the block name from its type definition. Now, surely there’s a way of doing that so I’d be grateful if someone could point me in the right direction. It would also mean I could get rid of this wee beastie:

/// <summary>
        /// Splits a string on humps in a camel case word, eg camelCaseWord => camel Case Word
        /// </summary>
        /// <param name="input">The camel case string to split</param>
        /// <returns>The string, with a space between every incidence of a lower case letter and an upper case letter</returns>
        public static string ToCamelCase(this string input)
        {
            return System.Text.RegularExpressions.Regex.Replace(input, "(?<=[a-z])([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim();
        }

EPiServer CMS 7.5 Clean Database Setup

EPiServer have been making some great improvements to the setup of new sites lately. There’s now no need to wrestle with the Deployment Center. Using the latest EPiServer CMS Visual Studio Extension creating a shiny new CMS 7.6.3 project is a simple matter of choosing between and MVC and Web Forms and pressing a button. This will create a preconfigured site in Visual Studio, with the only remaining tasks being to create a site in IIS and attach the supplied database.

Supplied database?

That’s right, within the App_Data folder is an .mdf file comprising the site’s database. Naturally it isn’t included as part of the solution:

EPiServer default database file

Empty EPiServer database .mdf file. There’s also an .ldf file, collapsed from view under the .mdf here by Visual Studio.

The template installs (at the time of writing) the version 7.6.3 Nuget packages, but there aren’t any database changes between versions 7.5.394.2 and 7.6.3.0 that I’m aware of. Feel free to correct me if I’m wrong.

However there’s a catch. There isn’t always a catch, but sometimes it can certainly seem that way. In this case it’s that the supplied database was created in SQL Server 2012, and 2012 databases cannot be ported back to earlier versions. This was a problem for me as our infrastructure runs mostly on SQL Server 2008R2. I suspect this is the case for a lot of people.

This was frustrating as it was standing in the way of having a nice easy setup process for new projects. The only thing to do was to roll up my sleeves and figure out where that sample database came from. This involved some tedious digging through the Visual Studio Extension’s.vsix file (it’s just an archive) the details of which I’ll spare you in favour of the highlights

Hooray there’s a script

I was concerned when I started looking that I’d find the .mdf file plainly archived in the .vsix. That would have been a disappointing dead end. However there was no sign of it, so it was either being supplied by one of the included Nuget packages (which would have also been a dead end) or created from a SQL script. It was of course the latter, inside EPiServer.CMS.Core.7.6.3.nupkg, in the tools folder: EPiServer.Cms.Core.sql.

Hopefully, I thought, I would just be able to run this script on a new database and we’ll be off.

Boo there’s a catch

The catch this time is that the script doesn’t contain everything you need. The database it created resulted in the error Missing stored procedure “RetrieveNonblockingInstanceStateIds” when I tried to use it with the new project. This is related to Windows Workflow Foundation, which requires some objects creating in the database. As these aren’t part of EPiServer itself, they weren’t included in EPiServer.Cms.Core.sql. However fortunately they are available to us in %WINDIR%\Microsoft.NET\Framework\v4.0.30319\SQL\en. Simply the following two scripts (in order) against the EPiServer database:

  • SqlPersistenceService_Schema.sql
  • SqlPersistenceService_Logic.sql

So we’re done, right?

Yes, I do believe that’s all that’s required to get a fresh database created. Here’s a zip containing all three required scripts. Just remember to run SqlPersistenceService_Schema.sql before SqlPersistenceService_Logic.sql and all should be well.

If however all is not well, please let me know as (there’s that catch again!) it hasn’t been thoroughly tested yet, but it seems sound in principle.

EPiServer Access Rights Wrongness

Recently I had a bug raised on an EPiServer build which described an error message I’d never seen before. When attempting to assigned new group rights to a particular branch of the page tree, the admin user was faced with this error message:

After reflecting the code out I found the error message in a catch block around a database transaction. To understand what was happening, it’s necessary to have a bit of knowledge about how EPiServer manages its access rights.

What lies beneath

tblAccess

This table keeps track of user/group access rights. It contains records for each user and group stored against a page id. Crucially, the page id has a constraint on it – the page must exist in tblPage. This was the underlying cause of the error message – a non-existent page id was being inserted into the table. But how could that happen?

tblTree

This table describes the page hierarchy. It records the page id of a page against the page id of its children, along with an integer indicating the level of nesting. When access rights for a branch of the pagetree are updated, two things happen:

1) The current rights for the user/group are removed from tblAccess. The records are deleted using a set of records from tblTree as a master list of pages included in the branch.

2) New rights for the user/group are assigned. These are written to tblAccess, again using a set of page ids obtained from tblTree. However, there is no constraint on tblTree. As a result, the table can contain a page id for a non-existent page. The upshot of this is that when tblAccess is updated, it attempts to write an invalid page id, which fails, resulting in the error message above.

This should never happen

Although tblTree should ideally have a constraint on it, the above scenario should never happen. Left to its own devices EPiServer will take care of its table structure, so the situation decsribed here looks like the result of a manual database manipulation. Someone, at some point, has hand-deleted some pages. This illustrates the pitfalls of directly manipulating CMS data nicely – table constraints will have led the way to deleting all references to the deleted pages, but tblTree has been missed. This is a lesson we should all know already, but it never hurts to be reminded why the lesson exists.