Category Archives: EPiServer 7.5

Display Help text in All Properties Editing

To see a property’s help text in EPiServer 7, the editor needs to hover the property label.
Helptext visible by hover
This is not always an intuitive behavior for the editor but I’ve found a way to it by making the help text visible next to the property.

Create css file

Create a .css file in /ClientResources/Styles, I call mine “editorhelp.css”.

Add the following styles : UPDATED FOR EPISERVER 9 and 10 (verified on 10.3.1)

.Sleek .dijitTabPaneWrapper .epi-form-container__section__row label[title]:after {
    content: attr(title);
    display: block;
    font-size: 0.8em;
    font-style: italic;
    margin: 0.3em 0 0 0;
}

Register your .css file

Add the following to module.config

<module>
	<clientResources>
		<add name="epi-cms.widgets.base"
				 path="Styles/editorhelp.css"
				 resourceType="Style"/>
	</clientResources>
</module>

Tadaa!

Go to edit mode and your Help text should be visible.
Help text displayed

Note that EPiServer is very good on caching this. You might need to clear your cache and restart your website.

Tagged

Extend your functionality by using Blocks

I’ve found that you can create a small toolbox for the editor to change the behavior of your functionality by using Blocks, not as models but as pluggable functions. It’s quite similar to visual programming where you play around with boxes and arrows but in this case we create Blocks and drag them either to a ContentReference or a ContentArea.

Functionality Blocks

The idea came from a project where multiple web sites are using their own instance of the same EPiServer project. Each website had different requirements of filters and I needed something generic where a super user could setup and change the functionality.

Cut to the code

First I created an interface that I will put on each BlockType:

    public interface IDoSomethingWithPageLists : IContentData
    {
        IEnumerable<PageData> DoSomething(IEnumerable<PageData> pages);
    }

In my first implementation, say you’re listing lots of pages and the editor wants to reverse the order, this is a really silly example though.

    [ContentType(DisplayName = "Reverse pages", GUID = "1f58e116-6576-4737-8d2c-246010b5b302")]
    public class ReverseBlock : BlockData, IDoSomethingWithPageLists
    {
        [Display(
            Name = "Property Name",
            GroupName = SystemTabNames.Content,
            Order = 1)]
        public virtual string PropertyName { get; set; }

        public IEnumerable<PageData> DoSomething(IEnumerable<PageData> pages)
        {
            return pages.Reverse();
        }
    }

That was quite easy, you can also give the editor the possibility to give details or variables to the logic. Such as filtering on that a property requires a value.

    [ContentType(DisplayName = "Property must contain value", GUID = "25ed56de-0d0b-43bb-8beb-8a8442e1bdd1")]
    public class PropertyMustContainValueBlock : BlockData, IDoSomethingWithPageLists
    {
        [Display(
            Name = "Property Name",
            GroupName = SystemTabNames.Content,
            Order = 1)]
        public virtual string PropertyName { get; set; }

        public IEnumerable<PageData> DoSomething(IEnumerable<PageData> pages)
        {
            return pages.Where(p => p[this.PropertyName] != null);
        }
    }

And just for the case of repeating the possibility of letting the editor decide.

    [ContentType(DisplayName = "Take every #", GUID = "7b9f13a6-22fe-4c0f-a804-ff81879fa986")]
    public class TakeEveryBlock : BlockData, IDoSomethingWithPageLists
    {
        [Display(
            Name = "Take Every",
            GroupName = SystemTabNames.Content,
            Order = 1)]
        public virtual int TakeEvery { get; set; }

        public IEnumerable<PageData> DoSomething(IEnumerable<PageData> pages)
        {
            for (int i = 0; i < pages.Count(); i++)
            {
                if (i % this.TakeEvery == 0)
                {
                    yield return pages.ElementAt(i);
                }
            }
        }
    }

So in my Page Template, all I need to do iterate through my Blocks and let them do what they’re good at.

    IEnumerable<PageData> pages = GetChildren(CurrentPage.PageLink); // Getting children is only a simple example.

    if (CurrentPage.FunctionBlocks != null)
    {
        var blocks = this.CurrentPage.FunctionBlocks.FilteredItems.Select(x => this.Get(x.ContentLink)).Where(x => x != null);
        foreach (var block in blocks)
        {
            pages = block.DoSomething(pages);
        }
    }

    // Do something with the pages such as binding a Repeater/PageList or adding to a Model

Summary

This is a really simplified example how the editor easily plugg in some customized logic to my existing function.

It can sometimes be a little confusing for the editor if you use Functionality Blocks to create a too complex solution. But to simplify for the editor, you can use EditorDescriptors or creating custom Dojo components to help your Editor.

In this example, I’m automatically adding the filters. You can also render these blocks to the visitor and let the visitor activate the logic.

Creating Modular Settings with Blocks

2014-05-16: Updated the code to include code that disappeared due to non html-encoded code.

Through the years there has been many ways to add settings to an EPiServer site. To mention a few there are GuiPlugins in the Admin Mode Plug-in Manager, Dynamic Properties, Global Properties on the Start Page, special Settings Page Types etc.

First place: The Settings Page!

SettingsPage One of my favourites was the concept of a “Settings Page”! You’ve got version history, language management, you’re not mixing content and settings! But I would like to have it a little more modular where I could contain the Settings data close to where it is used. For example if I had a Gallery I would like to keep the Gallery settings in that Namespace or even that assembly. But after only a year in a busy project, that Settings Page Type could be bloated with properties, tabs and what not! That I had to add more Properties to that greedy all-knowing SettingsPage class each time I need to add a few more settings made that Page Type to something big and ugly that only a mother would love. Not to mention how difficult it is to find that exact property in Edit Mode and reversing a version in the version manager without accidentally reversing some other setting for some other module.

The runner up: Plug-ins!

GuiPlugIn This is an old sturdy trustworthy concept. I can even drop a complete assembly containing almost everything I need to have my Image Gallery (just as an example) up and running and for settings can easily be managed in the Plug-in Manager without being mixed with other settings in the code structure. But with today’s features where we’re getting used to version and language management we are no longer interested in this old plain data management. Not to forget that the user has to be an administrator to be able to access the settings and we’re easily getting back to the bloated editor view filled with many fields screaming for attention.

The new kid in town: Blocks!

SettingsBlock Now with Blocks in EPiServer 7 the concept “A Page in EPiServer is necessarily not something you can browse to” becomes a fact since a PageData in previous versions was used to contain data of various sorts – such as Settings. By using Blocks in EPiServer for settings, not only can the settings be modular, but you also have some sweet toys such as

  • Access Rights management through the ACL (and the forget-me-not Access to Tabs). If you play your cards right you could also set different settings for different users based on VisitorGroups and Roles.
  • Version management that only handles versions for your current module.
  • Language Management and even being able to set Fallback Languages for a specific Settings.
  • Fallback/Default values capabilities where you can add logic to the Get/Set Properties as well as the SetDefaultValues method.

I’m aware of the cons where it can end up with multiple Blocks instead of a single view filled with multiple fields. I think that this is a start where we can add a rich Settings Manager and improve the experience for the Editor and still have a strong and flexible data layer underneath. I have made a quick Proof of Concept of my idea and it can be found on GitHub.

Cut to the Code!

First, I created an Interface, in this Draft it doesn’t do anything else except helping me identify that this is one of my Settings.

public interface ISettingsBlock : IContentData
{
}

All I need to do is to implement this to my Block. Note that I’ve also made this unavailable for editors. I don’t want this thing being created all over the place!

[ContentType(DisplayName = "My Settings", GUID = "bc9d37bd-c725-4b8b-8a23-039710f476bf", Description = "", AvailableInEditMode = false)]
public class MySettingsBlock : BlockData, ISettingsBlock
{
    [Display(
        Name = "My Setting",
        GroupName = SystemTabNames.Content,
        Order = 1)]
    public virtual string MySetting { get; set; }
        
    [Display(
        Name = "My Setting with a Fallback",
        GroupName = SystemTabNames.Content,
        Order = 2)]
    public virtual string MySettingWithFallback {
        get
        {
            var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
            return this["MySettingWithFallback"] as string
                ?? contentLoader.Get(ContentReference.StartPage).Name;
        }
        set { this["MySettingWithFallback"] = value; } }
}

And since we want to easily find our Settings Blocks and don’t want to manually create them, I’ve added a little utility class for this.

public class SettingsRepository<T> where T : BlockData, ISettingsBlock
{
    #region [ Private Fields ]
    private const string RootFolderName = "[Settings]";
    private static readonly ConcurrentDictionary<string, object> MiniLocks = new ConcurrentDictionary<string, object>();

    #endregion

    protected virtual ContentReference ParentFolder
    {
        get
        {
            return GetOrCreate(ContentReference.SiteBlockFolder, "[Settings]");
        }
    }

    protected virtual string DefaultBlockName
    {
        get
        {
            var repo = ServiceLocator.Current.GetInstance<BlockTypeRepository>();
            var blockType = repo.Load<T>();
            return blockType.DisplayName;
        }
    }

    public T Instance
    {
        get
        {
            return GetOrCreateSettingsBlock();
        }
    }

    protected virtual T GetOrCreateSettingsBlock()
    {
        ContentReference contentLink = this.GetOrCreate(this.GetOrCreateContentFolder(), this.DefaultBlockName);
        var repo = ServiceLocator.Current.GetInstance<IContentRepository>();
        return repo.Get<T>(contentLink);
    }

    private ContentReference GetOrCreateContentFolder()
    {
        var repo = ServiceLocator.Current.GetInstance<IContentRepository>();
        ContentReference contentLink;
        var child = repo.GetChildren<ContentFolder>(ContentReference.SiteBlockFolder, LanguageSelector.AutoDetect(true)).FirstOrDefault(x => x.Name.Equals(RootFolderName));

        if (child == null)
        {
        const string Key = "ContentFolder";

        // Lock to prevent duplicates from being created.
        object miniLock = MiniLocks.GetOrAdd(Key, k => new object());

        lock (miniLock)
        {
            var childInLock = repo.GetChildren<ContentFolder>(ContentReference.SiteBlockFolder, LanguageSelector.AutoDetect(true)).FirstOrDefault(x => x.Name.Equals(RootFolderName));

            if (childInLock == null)
            {
            var clone = repo.GetDefault<ContentFolder>(ContentReference.SiteBlockFolder);
            clone.Name = RootFolderName;
            contentLink = repo.Save(clone, SaveAction.Publish, AccessLevel.NoAccess);
            }
            else
            {
            contentLink = childInLock.ContentLink;
            }

            // Saving some space
            object temp1;

            if (MiniLocks.TryGetValue(Key, out temp1) && (temp1 == miniLock))
            {
            object temp2;
            MiniLocks.TryRemove(Key, out temp2);
            }
        }
        }
        else
        {
        contentLink = child.ContentLink;
        }

        return contentLink;
    }

    private ContentReference GetOrCreate(ContentReference parentLink, string name)
    {
        var repo = ServiceLocator.Current.GetInstance<IContentRepository>();
        ContentReference contentLink;
        var child = repo.GetChildren<T>(parentLink, LanguageSelector.AutoDetect(true)).FirstOrDefault() as IContent;

        if (child == null)
        {
        string key = typeof(T).ToString();
        
        // Lock to prevent duplicates from being created.
        object miniLock = MiniLocks.GetOrAdd(key, k => new object());

        lock (miniLock)
        {
            var childInLock = repo.GetChildren<T>(parentLink, LanguageSelector.AutoDetect(true)).FirstOrDefault() as IContent;

            if (childInLock == null)
            {
            var clone = repo.GetDefault<T>(parentLink) as IContent;
            clone.Name = name;
            contentLink = repo.Save(clone, SaveAction.Publish, AccessLevel.NoAccess);
            }
            else
            {
            contentLink = childInLock.ContentLink;
            }

            // Saving some space
            object temp1;

            if (MiniLocks.TryGetValue(key, out temp1) && (temp1 == miniLock))
            {
            object temp2;
            MiniLocks.TryRemove(key, out temp2);
            }
        }
        }
        else
        {
        contentLink = child.ContentLink;
        }

        return contentLink;
    }
}

To get the setting I’m interested in, all I need to do is

var mySetting = new SettingsRepository<MySettingsBlock>().Instance.MySetting;

My next step would be to maybe move some of the information such as the Block Name to the Interface (and by that to the Block class itself) instead of the Repository but that feels more like a religious discussion. But the thing that would really improve this concept would be a some kind of interface to get an overview of (and possibly edit) all Settings.

Change how your EPiServer ContentType is administered with AdministrationSettingsAttribute

Are you tired of Administrators changing your Content Types and breaking your web site?

Fear not, I’ve found a cool hidden gem that allows you to disable fields when editing Content Types in Admin Mode – the EPiServer.DataAnnotations.AdministrationSettings attribute.

It is simply added to your Content Type Class

[AdministrationSettings(
  [bool CodeOnly],
  [ContentTypeFields ContentTypeFields],
  [string GroupName],
  [int Order],
  [PropertyDefinitionFields PropertyDefinitionFields],
  [bool Visible])]
public class StandardPage : PageData
{
...
}

With the AdministrationSettings attribute you can edit the following parts

How it is displayed

GroupName
The GroupName is used to group your Content Types in Admin mode.

GroupName

I have set the GroupName = “My Custom Pages” to my Page Type “My Custom Page”

Visible
If you simply want to hide your Content Type in Admin Mode.

Order
An integer where you define the sort order inside the Group.

Disable Administrator accessability

CodeOnly
If you simply want to disable changes for the entire Content Type.

PropertyDefinitionFields
This is an enum flag that only enables certain fields on the Edit Property view for the Content Type.
Note that if you create a Property, it cannot be saved unless you allow the required fields.

  • None
  • Type
  • Name
  • PropertySettingsControl
  • DefaultValue
  • Required
  • Searchable
  • LanguageSpecific
  • DisplayEditUI
  • EditCaption
  • HelpText
  • Tab
  • FieldOrder
  • All
Caption

I have only enabled the Help text with PropertyDefinitionFields = PropertyDefinitionFields.HelpText.

ContentTypeFields
Same as PropertyDefinitionFields but with the fields for your Content Type.

  • None
  • Name
  • Description
  • DisplayTemplates
  • SortOrder
  • DisplayName
  • AvailableInEditMode
  • ACL
  • DefaultValues
  • AvailablePageTypes
  • All
ContentTypeFields

I have only enabled the Description field with ContentTypeFields = ContentTypeFields.Description

Summary

This attribute will help you organize your Content Types and to disable editing capabilities for Content Types you really don’t want an administrator to mess with. But use it wisely!

Having many and small groups can make a mess for the administrator and yourself.

Hooking up to the EPiServer QuickNavigator

I’ve seen many projects in EPiServer CMS 5 and 6 where the developers has extended the Context Menu. Menu options such as enabling developer modes or shortcuts to certain tools are some of the most popular extensions.

ContextMenu

In EPiServer 7 CMS, the Context Menu has been replaced with the QuickNavigator. But unfortunately it was not compatible with the Context Menu extensions and it was quite hard coded.

QuickNavigator original

With the Release with EPiServer CMS 7.5, things started to brighten up again! Now you can register your own QuickNavigatorItemProvider.
The biggest difference is that instead of only rendering the options “Edit Mode” and “Dashboard”, the QuickNavigator now iterates through all implementations of the interface IQuickNavigatorItemProvider that is registered in EPiServer’s IoC Container.

QuickNavigator

I’ve taken a look at how to use this to implement one of the missing QuickNavigator options, the Logout button. This option will not only log you out. It will also redirect you to the page you previously watched.

The code can be seen below and also be found on GitHub.

Cut to the code!

First of all I needed to create my own implementation of the IQuickNavigatorItemProvider.

This part is quite similar to when extending the EPiServer Online Center top navigation.

Basically I copied the original implementation but changed which items I wanted to provide.

[ServiceConfiguration(typeof(IQuickNavigatorItemProvider))]
public class LogoutQuickNavigatorItemProvider: IQuickNavigatorItemProvider
{
    private readonly IContentLoader contentLoader;

    public int SortOrder
    {
        get
        {
            return 10;
        }
    }

    public LogoutQuickNavigatorItemProvider(IContentLoader contentLoader)
    {
        this.contentLoader = contentLoader;
    }

    public IDictionary<string, QuickNavigatorMenuItem> GetMenuItems(ContentReference currentContent)
    {
        var urlBuilder = new UrlBuilder("/logout");
        if (this.IsPageData(currentContent))
        {
            var urlResolver = ServiceLocator.Current.GetInstance();
            string url = urlResolver.GetUrl(currentContent);

            urlBuilder.QueryCollection.Add("ReturnUrl", url);
        }

        return new Dictionary<string, QuickNavigatorMenuItem> {
            {
                "customlogout",
                new QuickNavigatorMenuItem("/shell/cms/menu/logout", urlBuilder.ToString(), null, "true", null)
            }
        };
    }

    private bool IsPageData(ContentReference currentContentLink)
    {
        return this.contentLoader.Get(currentContentLink) is PageData;
    }
}

By using the ServiceConfigurationAttribute, I will inject my implementation of IQuickNavigatorItemProvider into the IoC container.
I’m also reusing the xpath /shell/cms/menu/logout that is the xpath for the term “Log out” used in Online Center.

The bonus!

Here, I could just as easily redirect the user to EPiServer’s built-in logout page on /util/login.aspx. But instead of a message that informs the visitor about being logged out, I’d rather redirect the user back to the previous page.

One idea was to create a custom path that performs the signout and redirects my user.

But since I feel EPiServer knows what is needed for most circumstances to logout a user better than me, I’d rather reuse their functionality.

To have this implementation as lightweight as possible I created a custom Route, that takes the visitor to Logout.aspx but with a little twist.
It loads the ordinary Logout.aspx, but hooks up to the PreRender Event to perform a classic Redirect to the previously visited page.

public class LogoutRedirectRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var logout = BuildManager.CreateInstanceFromVirtualPath("/Util/Logout.aspx", typeof(Logout)) as Logout;
        if (logout == null)
        {
            // Something seems wrong
            throw new Exception();
        }

        logout.PreRender += this.LogoutOnPreRender;
        return logout;
    }

    private void LogoutOnPreRender(object sender, EventArgs eventArgs)
    {
        var returnUrl = HttpContext.Current.Request.QueryString["ReturnUrl"];
        HttpContext.Current.Response.Redirect(returnUrl);
    }
}

And to register my Route is easy as cake. With some inspiration from Johan Kronbergs blog about Custom Routes, I create an InitializableModule that adds my route to the RouteTable:

[InitializableModule]
[ModuleDependency(typeof(InitializationModule))]
public class LogoutQuickNavigatorInitializer : IInitializableModule
{
    private const string RouteName = "LogoutRedirect";

    public void Initialize(InitializationEngine context)
    {
        RouteTable.Routes.Add(RouteName, new Route("logout", new LogoutRedirectRouteHandler()));
    }

    public void Preload(string[] parameters) { }

    public void Uninitialize(InitializationEngine context)
    {
        RouteTable.Routes.Remove(RouteTable.Routes[RouteName]);
    }
}

Simplifying allowed content types in Editor Descriptor

Update 2: About a week I noticed that this functionality already exists according to Linus Ekström. But you can take this example on how to simplify attributes with UI hints and being able to read information directly from the Content Type.

Update: Updated to correct code

One of the new things in EPiServer 7.5 is the ability to restrict available content in ContentAreas (and similar Properties). A great way that prevents content to be dragged to a ContentArea compared to the validator option that is common in EPiServer 7 and 7.1 sites.

EPiServer developer Ben McKernan has written a blog post about this.

I’ve simplified this to make it easier for a developer to understand the purpose of the Editor Descriptor and being able to control exactly which types that are allowed from the Content Type.

Cut to the code!

This is how it will look on the accual Content Type when adding a ContentArea Property:

[AllowedContentTypes(typeof(StandardPage))]
public virtual ContentArea StandardPages { get; set; }

The code for the AllowedContentTypes attribute looks like this:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class AllowedContentTypesAttribute : UIHintAttribute
{
  public AllowedContentTypesAttribute(params Type[] types) : base("AllowedContentTypes")
  {
    this.Types = types;
  }

  public Type[] Types { get; set; }
}

And the code for the Editor Descriptor – this is a merge between Ben’s code and the original ContentArea Editor Descriptor from the EPiServer.Cms.Shell.UI assembly.

[EditorDescriptorRegistration(TargetType = typeof(ContentArea), UIHint = "AllowedContentTypes")]
public class AllowedContentTypes : EditorDescriptor
{
  public AllowedContentTypes()
  {
    this.ClientEditingClass = "epi-cms.contentediting.editors.ContentAreaEditor";
    this.OverlayConfiguration.Add("customType", "epi-cms.widget.overlay.ContentArea");
  }

  public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes)
  {
    base.ModifyMetadata(metadata, attributes);

    var attribute = metadata.Attributes.OfType<AllowedContentTypesAttribute>().SingleOrDefault();
    if (attribute != null)
    {
      this.AllowedTypes = attribute.Types;
    }

    metadata.OverlayConfiguration["customType"] = (object)"epi-cms.widget.overlay.ContentArea";
    metadata.CustomEditorSettings["converter"] = (object)"epi-cms.propertycontentarea";
  }
}