Category Archives: EPiserver 7

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.

Slight update on the Tool for Page Type Classes from Page Types in EPiServer 7 CMS

I made a small mistake on the Page Type Class Tool for EPiServer 7 CMS. This was on the SetDefaultValues method where the ChildOrderRule was set to FilterSortOrder.None.

If a Page have a ChildOrderRule set to None, EPiServer won’t be able to find the Children of that Page since the Stored Procedure does not know how to handle that ChildOrderRule.

Therefore I have added a check whether pageType.Defaults.ChildOrderRule is not FilterSortOrder.None before setting the value.

Some information behind the scenes

FilterSortOrder.None is basically a value that means that there is no specific default value setting for this Page Type stored in tblPropertyDefinitionDefault.

If the ChildOrderRule is set to None, the default values provided by PageDataBuilder.SetPropertyValues() will be set, which means that the default value is 1 (CreatedDescending).

Even the class PageData where SetDefaultValues() is defined have a check for ChildOrderRule.

if (pageType.Defaults.ChildOrderRule != FilterSortOrder.None)
    property["PageChildOrderRule"].Value = (object) pageType.Defaults.ChildOrderRule;

What should I do if I have used the faulty version?

All you need to do is to remove the following row from all your Page Type Classes:

this[MetaDataProperties.PageChildOrderRule] = FilterSortOrder.None;

And run following in your database to fix all pages that have already been created with wrong ChildOrderRule:

UPDATE [tblContent] SET ChildOrderRule = 1 WHERE ChildOrderRule = -1

I am sorry for the inconvenience, if you find more bugs or have suggestions on how to improve, please tell me.

Find Pages containing a certain Content in a ContentArea (UPDATE)

good-news-everyone

I had some discussions about this blog post and had to check up some things. Accidently I found the IContentRepository.GetReferenceInformationForContent(ContentReference contentLink, bool includeDecendents) (implemented on DataFactory) which runs the Stored Procedure [editDeletePageCheck]. This is normally used to warn an editor about referencing pages when editing och deleting content.

According to the ExpressProfiler it goes to the database each time I run the command so it might be good to keep this in mind.

Here is the new code:

var contents = new List<ContentData>();
var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
IEnumerable references = repository.GetReferencesToContent(contentLink, false);
foreach (ReferenceInformation reference in references)
{
    ContentReference ownerContentLink = reference.OwnerID;
    CultureInfo ownerLanguage = reference.OwnerLanguage;
    ILanguageSelector selector = new LanguageSelector(ownerLanguage.Name);
    var content = repository.Get<ContentData>(ownerContentLink, selector);

    var contentArea = content["ResponsibleEditors"] as ContentArea;
    if (contentArea != null)
    {
        if (contentArea.ContentFragments.Any(fragment => fragment.ContentLink == contentLink))
            contents.Add(content);
    }
}

While browsing in the tblContentProperty I stumbled upon how a ContentArea is saved in the database.

It’s quite similar to how Dynamic Content is serialized in a XhtmlString:

<div data-classid=”36f4349b-8093-492b-b616-05d8964e4c89″ data-contentguid=”7cef1c69-b345-493c-a60e-ce011fe42c31″ data-contentlink=”35″ data-contentname=”Content Name”>{}</div><div data-classid=”36f4349b-8093-492b-b616-05d8964e4c89″ data-contentguid=”216210e0-f687-4f7a-9bc9-c2a51badab1a” data-contentlink=”701″ data-contentname=”Another Content Name”>{}</div>

In my example I have a Page Type called EditorPage presenting all persons in the staff who can be responsible to the web sites articles.

To present an articles responsible editor I have added a ContentArea where the editor simply drops Editor Pages.

On the Editor Page I want to list all the article where the Editor is connected.

So all I have to do to find pages that have a specific Content (for example Page or Block) referenced in a ContentArea is this:

string id = ID-TO-YOUR-CONTENT;
PropertyCriteriaCollection criterias = new PropertyCriteriaCollection
{
    new PropertyCriteria
        {
            Condition = CompareCondition.Contained,
            Name = "ResponsibleEditors",
            Required = true,
            Type = PropertyDataType.String,
            Value = string.Format("data-contentlink=\"{0}\"", id)
        }
};
IPageCriteriaQueryService queryService = ServiceLocator.Current.GetInstance<IPageCriteriaQueryService>();
PageDataCollection pages = queryService.FindPagesWithCriteria(ContentReference.StartPage, criterias);

You can also use the content GUID and use this as Value: string.Format(“data-contentguid=\”{0}\””, guid)

As usual, if you want to use FindPagesWithCriteria, remember to put a cache on it since it can be quite heavy on the database.

The quest of FindPagesWithCriteria with a Block Property in EPiServer 7 CMS

There are several exciting things with the new Blocks in EPiServer 7 CMS. One of the most convenient things when you need to add a group of data to a page type, for example an image + alternative text or an url + clickable text or maybe a group of SEO data such as title, description and keywords.

Note that this article does not include how Global Blocks are saved. I have only focused on Blocks that are added as Properties on a Content Type.

I have create a sample Page Type TestPage containing 2 sets of the block TestBlock which itself only contains the string Property TestValue. TestBlock1 contains the value “abc” and TestBlock2 contains the value “def”.

When I check the database structure I can find these traces of ContentTypes, Properties and values. I have marked the recurring values to mark how the relations are set between all  tables.

Database Structure

[tblContentType]

pkID ContentTypeGUID ModelType Name ContentType
23 34CC512A-5680-47CA-86EF-80F38A688F40 EPiServer.Templates.Alloy.Models.Blocks.TestBlock, EPiServer.Templates.Alloy, Version=1.0.0.30998, Culture=neutral, PublicKeyToken=null TestBlock 1
24 64E21501-096A-4983-AE87-03D274FE98B2 EPiServer.Templates.Alloy.Models.Pages.TestPage, EPiServer.Templates.Alloy, Version=1.0.0.30998, Culture=neutral, PublicKeyToken=null TestPage 0

[tblPropertyDefinitionType]

pkID Property Name TypeName AssemblyName fkContentTypeGUID
1010 12 TestBlock NULL NULL 34CC512A-5680-47CA-86EF-80F38A688F40

Where the column fkContentTypeGUID is null on PropertyDefinitionTypes that are not connected to a ContentType

[tblPropertyDefinition]

pkID fkContentTypeID fkPropertyDefinitionTypeID Name Property
133 23 7 TestValue 7
134 24 1010 TestBlock1 12
135 24 1010 TestBlock2 12

Where fkPropertyDefinitionTypeID 7 mean it’s a normal string and strangely 12 is a XhtmlString but I guess it has something to do with rendering. *adding to future blogs list*

[tblContentProperty]

pkID fkPropertyDefinitionID fkContentID fkLanguageBranchID ScopeName guid LongString LongStringLength
1628 133 183 1 .134.133. 1C2D6A8D-6973-4E36-9BC9-9E655506795C abc 6
1628 133 183 1 .135.133. 1C2D6A8D-6973-4E36-9BC9-9E655506795C def 6

I have created a Page of TestPage. The page have Id 183, it didn’t feel necessary to add that table to this chart but as you can see, the values of TestBlock1 and TestBlock2 have a fkContentID that referers to the PageData it is stored on.

The column ScopeName is a relational path that connects the Name property to the TestBlock Block named TestBlock1.

Find Pages based on this?

The datastructure for Blocks in tblContrentProperty are quite flat. Therefore I can find my Test Page by creating a FindPagesWithCriteria:

IPageCriteriaQueryService repository = ServiceLocator.Current.GetInstance();

PropertyCriteriaCollection criterias = new PropertyCriteriaCollection
{
    new PropertyCriteria
    {
        Condition = CompareCondition.Equal,
        Required = true,
        Name = "TestValue",
        Type = PropertyDataType.String,
        Value = "abc"
    }
};
PageDataCollection result = repository.FindPagesWithCriteria(ContentReference.RootPage, criterias);

FindPagesWithCriteria sends queries to a couple of Stored Procedures in the database and checks among the tables whether the Pages have a Property with the given name and  if the Property value fits the criteria.

And what is the result?

Unfortunately since there is no way to define that I only want to search for a page where TestBlock1 have a TestValue with value “abc”, I will receive ALL pages that have a Property called TestValue with a value of “abc” no matter if they are saved on a block or directly on the page.

This really sounds like a job for EPiServer Find but if you don’t have it installed, FindPagesWithCriteria can be quite good at times except for the disadvantages of many queries to the database and its cumbersome API.

Though the advantages is its stability and being straightforward. Remember to not add too many criterion and try to Cache-smart if you using it

What I can find there is no good way to change the behaviour of FindPagesWithCriteria without copying quite alot of the existing logic or rewriting the existing Stores Procedures.

Resolve PageType & ContentType from Class

In PageTypeBuilder for EPiServer CMS 6 we had a lovely feature called PageTypeResolver which could be used to find the Id of a PageType based on your implementation of TypedPageData. All you needed to do is run PageTypeResolver.Instance.GetPageTypeID(typeof(MyPageType)) and you would get a Nullable Int which represents the Id of your PageType, or null if no PageType could be found. In EPiServer 7 CMS this has been built into the main functionality of Page Types / Content Types and all you need to do is this:

  • Find the implementation of IContentTypeRepository you’re looking for:
    var repository = ServiceLocator.Current.GetInstance<IContentTypeRepository<PageType>>();
    var repository = ServiceLocator.Current.GetInstance<IContentTypeRepository<BlockTypeType>>();
    var repository = ServiceLocator.Current.GetInstance<PageTypeRepository>();
    var repository = ServiceLocator.Current.GetInstance<BlockTypeRepository>();
  • Load the page type based on your implementation of BlockData/PageData:
    var contentType = repository.Load(typeof(MyPageType));

If you’re locating the PageTypeRepository or BlockTypeRepository implementations directly you can also find the shortcut:

var contentType = repository.Load<MyPageType>();

If you try to load an instance of ContentData that does not exist, Load() will return null.

Simplifying event handling in EPiServer 7 CMS

While adding some Blocks to a project I was thinking of a concept that I’ve had in mind for a while. Some way to make it easier to hook your logic to EPiServer events such as creating pages, publishing pages.

The project can be downloaded from my repository on GitHub. I hope this will make your event handling in EPiServer much easier!

My example is a block that represents a header to group blocks.

[ContentType(DisplayName = "Column Heading", Description = "A heading in the column", GUID = "0A9BEC10-5795-43D5-B305-130ED6222D8A")]
public class ColumnHeadingBlock : BlockData
{
  [Display(GroupName = SystemTabNames.Content, Order = 1)]
  public virtual string Heading { get; set; }
}

Since it only has one property, it would be nice that it would be populated with the name given when the Block was created.

column heading

First approach: override SetDefaultValues

I noticed that the instance of my ColumnHeadingBlock is a ColumnHeadingBlockProxy that seems to be created  by EPiServer.Construction.SharedBlockFactory.CreateSharedBlock and that the proxy also is implementing the IContent Interface. Hence I can Cast my to IContent and retrieve the IContent.Name implementation.

Sadly SetDefaultValues are run before the Name is set and returns null.

Second approach: EPiServer.DataFactory.CreatingContent Event

This seems to do the trick. CreatingContent is triggered after SetDefaultValues and contentEventArgs.Content.Name returns the name I gave when creating the Block.

Wrapping things up: Initialization and reusability

The idea that have been growing inside my head is that I shouldn’t need to clutter global.asax or create InitializationModules everytime I want something to happen when I create, save, publish, move a page.

Therefore I created my EventHelper where I only need to implement an Interface to my Page Type Class where I add the logic to be run under certain Events. This could be re-registering Routes, notifying people, logging things etc when something is done in EPiServer.

In my example where I want to set a value to a Block Property when it is created, the implementation would look like this:

[ContentType(DisplayName = "Column Heading", Description = "A heading in the column", GUID = "0A9BEC10-5795-43D5-B305-130ED6222D8A")]
public class ColumnHeadingBlock : BlockData, ICreatingContent
{
  [Display(GroupName = SystemTabNames.Content, Order = 1)]
  public virtual string Heading { get; set; }

  public void CreatingContent(object sender, ContentEventArgs contentEventArgs)
  {
    Heading = contentEventArgs.Content.Name;
  }
}

Behind the scenes

The project contains an InitializationModule where I hookup basically all Events I could find in the DataFactory. I excluded the LoadingChildren Events since I don’t really know what the best implementation of this could be. Maybe something for a later version?

The CreatingContent Event is hooked up to the method DataFactoryEvents.CreatingContent.

DataFactory.Instance.CreatingContent += DataFactoryEvents.CreatingContent;

The DataFactoryEvents.CreatingContent method looks like this:

public static void CreatingContent(object sender, ContentEventArgs e)
{
  var content = e.Content as ICreatingContent;
  if (content == null)
    return;
  content.CreatingContent(sender, e);
}

Here is a list of the other Events I’m using – as you can see I’ve also implemented Page Events as well as the Content Events.

I have not yet given myself time to see if it’s necessary to be able to implement both Content and Page, but since the Page Event is not obsolete I guess both would be good.

  • CheckedInContent
  • CheckedInPage
  • CheckingInContent
  • CheckingInPage
  • CreatedContent
  • CreatedPage
  • CreatingContent
  • CreatingPage
  • DeletedContent
  • DeletedContentLanguage
  • DeletedContentVersion
  • DeletedPage
  • DeletedPageLanguage
  • DeletingContent
  • DeletingContentLanguage
  • DeletingContentVersion
  • DeletingPage
  • DeletingPageLanguage
  • FailedLoadingContent
  • FailedLoadingPage
  • FinishedLoadingPage
  • LoadedContent
  • LoadedDefaultContent
  • LoadedDefaultPageData
  • LoadedPage
  • LoadingContent
  • LoadingDefaultContent
  • LoadingDefaultPageData
  • LoadingPage
  • MovedContent
  • MovedPage
  • MovingContent
  • MovingPage
  • PublishedContent
  • PublishedPage
  • PublishingContent
  • PublishingPage
  • SavedContent
  • SavedPage
  • SavingContent
  • SavingPage
Tagged

Creating Page Type Classes from Page Types in EPiServer CMS 7

After my upgrade of Stockholm Pride to EPiServer 7 CMS (blog post coming soon) I wanted the project to be a bit more shiny with some Code Managed Page Types.

I forked Erik Nordin’s utility to create classes for PageTypeBuilder in an existing project. Did some research on what attributes I needed to create a similar thing with EPiServer 7 CMS.

As a bonus I also gave it a little more EPiServer look-and-feel:

create pt

Since I don’t know if I should commit this back to Erik Nordin’s GitHub or not, you can download it from GitHub.

Current status

I would see this as a first draft of the creation tool that worked quite good for my upgrade of Stockholm Pride.

It creates Properties for the most common properties in EPiServer but gives a template where you can decide if you want to go with a BackingTypeAttribute or using EditorDescriptors/UIHints instead (see below).

Attributes and value types

I’ve tried to cover all correct DataTypes Attributes such as replacing PropertyImageUrl with Url and setting UIHints but I still thinks it needs some more cleaning.

Note that since I only check for what’s in the database, avoid running this tool while already having Page Types Classes in your project.

Custom Properties

For now, the support for Custom Properties are not that great. I try to read out what kind of Data Value the Properties are meant to return but leave a code comment to those Properties I don’t know what to do with.

This way you can make a search for //DefinitionType= and decide for yourself if you want to use the UIHintAttribute or the BackingFieldAttribute to solve this problem.

Some of the older EPiServer Properties are obsolete which mean that they should not be used as a BackingField.

UILanguageProperty is one of them which luckily is only a property that returns a string, but the Editor may select among all activated Languages.

By reflection I can find out exactly how that list is populated.

Example of EditorDescriptor

This means that UILanguageProperty can easily be replaced with an EditorDescriptor that gives the same functionality:

//DefinitionType=EPiServer.SpecializedProperties.PropertyUILanguage
//[BackingType(typeof(PropertyUILanguage))] -- PropertyUILanguage has been obsoleted and will be removed in a future release.
[UIHint("UILanguage")]
[Display(Name = "Content Language")]
public virtual string ContentLanguage { get; set; }

[EditorDescriptorRegistration(TargetType = typeof(string), UIHint = "UILanguage")]
public class UILanguageEditorDescriptor : EditorDescriptor
{
   public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
   {
      SelectionFactoryType = typeof(UILanguageSelectionFactory);
      ClientEditingClass = "epi.cms.contentediting.editors.SelectionEditor";

      base.ModifyMetadata(metadata, attributes);
   }
}

public class UILanguageSelectionFactory : ISelectionFactory
{
   public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata)
   {
      var languages = new List<SelectItem>();
      foreach (System.Globalization.CultureInfo info in LocalizationService.Current.AvailableLocalizations)
      {
         string nativeName = info.NativeName;
         if (info.TextInfo.ANSICodePage != 0x4e4)
         {
            nativeName = nativeName + string.Format(" ({0})", info.EnglishName);
         }
         languages.Add(new SelectItem { Value = info.Name, Text = nativeName });
      }
      return languages;
   }
}

NullReferenceException from TinyMCESettings, InputTinyOptions.GetActiveToolsHTML() and ToolbarRow in EPiServer 7

I’m upgrading a quite old EPiServer CMS website that has been alive since about 2007 from EPiServer CMS 6 to EPiServer 7 CMS.

Among the first things after using EPiServer Deployment Center to upgrade from CMS 6 R2 to 7 CMS is that I can’t use the new shiny Editor Mode.

Not being able to edit First Page

It looks good but no reaction when I click to edit a property or trying to enter Forms Editing.

A quick look in my browser’s Developer Network  gives me a HTTP Error 500 from the following url:
/secure/shell/Stores/metadata/?type=EPiServer.Core.ContentData&modelAccessor=%7B”contentLink”%3A”3_35644″%7D&dojo.preventCache=1362601343237

secure-shell-stores-metadata

I also had some strange errors when trying to edit TinyMCE Property Settings:

edit xhtmlstring settings

After some Reflecting, Debugging and some hints from http://world.episerver.com/Modules/Forum/Pages/thread.aspx?id=64169 I found that in the upgrade from 6R2 to 7, the Upgrade Tool moves some DDS-items such as Property Configuration from tblBigTable to tblSystemBigTable.

But the View VW_EPiServer.Editor.TinyMCE.ToolbarRow nor the Stored Procedure Save_EPiServer.Editor.TinyMCE.ToolbarRow are not updated to load or save from the correct Big Table causing the DDS to be able to find the TinyMCESettings correctly but when the nested entities ToolbarRow are loaded, no such data is found. Hence TinyMCESettings.Toolbar returns a list but all items in the list are NULL.

The correct procedure seems to be to change the design of the View to

SELECT CAST(R01.pkId AS varchar(50)) + ':' + UPPER(CAST([Identity].Guid AS varchar(50))) AS Id, R01.pkId AS StoreId, [Identity].Guid AS ExternalId, R01.ItemType
FROM dbo.tblSystemBigTable AS R01 INNER JOIN
 dbo.tblBigTableIdentity AS [Identity] ON R01.pkId = [Identity].pkId
WHERE (R01.StoreName = 'EPiServer.Editor.TinyMCE.ToolbarRow') AND (R01.Row = 1)

and the Stored Procedure to

USE [dbStockholmPrideDev_EPi7]
GO
/****** Object: StoredProcedure [dbo].[Save_EPiServer.Editor.TinyMCE.ToolbarRow] Script Date: 03/04/2013 09:45:36 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[Save_EPiServer.Editor.TinyMCE.ToolbarRow]
@Id bigint output,
@UniqueId uniqueidentifier,
@ItemType nvarchar(2048)
as
begin
declare @pkId bigint
 select @pkId = pkId from tblBigTableIdentity where [Guid] = @UniqueId
 if @pkId IS NULL
 begin
 begin transaction
 insert into [tblBigTableIdentity]([Guid], [StoreName]) values(@UniqueId, 'EPiServer.Editor.TinyMCE.ToolbarRow')
select @Id = SCOPE_IDENTITY()
 insert into [tblSystemBigTable] (pkId, StoreName, Row, ItemType)
 values(@Id, 'EPiServer.Editor.TinyMCE.ToolbarRow', 1, @ItemType )
commit transaction
 end
 else
 begin
 begin transaction
 select @Id = @pkId
 DECLARE @rows int
 select @rows = count(*) from [tblSystemBigTable] where pkId = @Id
 if(@rows < 1) begin
 insert into [tblSystemBigTable] (pkId, StoreName, Row, ItemType)
 values(@Id, 'EPiServer.Editor.TinyMCE.ToolbarRow', 1, @ItemType )
end
 commit transaction
 end
end