Category Archives: Content Types

Presenting: Find My Content

My main project is a multi website solution where 16 different websites is sharing the same source code.

Sometimes when we need to change specific Content Type and find a strategy on how to migrate the changes it’s good to know how much that Content Type is used, and in what way.

How do you do that?

A way to to find how much it is used is to perform a simple SQL Query:

SELECT c.pkID FROM tblContent c
INNER JOIN tblContentType ct ON c.fkContentTypeID = ct.pkID
WHERE ... = '...' -- various ways to identify your Content Type using pkID, ContentTypeGUID or Name

And go to Episerver Edit Mode to see how the editor has worked with each content.

But to be able to run the SQL query, you will need to have access to the production database which makes things a little more annoying. Specially if you need to use VPN connections or similar.

Therefore I have created an Admin Tool called “Find My Content” where you easily can find all content based on their Content Types.

Listing all Content Types

findmycontent.PNG

Here you see how many items of this Content Type is created by clicking the Name.

Details of a Content Type

findmycontent standardpage.PNG

In the details you can see all Content of this Content Type and which language they are created in. There is also a shortcut that will take you to Edit mode for that specific Content.

How do I get it?

I’m waiting for a NuGet package to be published on Episerver’s NuGet feed. Otherwise you can find the code on my GitHub.

Update: It is now available in the feed: https://nuget.episerver.com/en/OtherPages/Package/?packageId=Toders.FindMyContent

What’s next?

This was just something I threw together since I got tired connecting to my client’s environments using VPN and Remote Desktop.

I’ve got some ideas to add references between the different content, filtering on values for specific properties and so on.

Do you have any other ideas that would be nice to have in this tool? Just give me a shout in the comments below.

Advertisements

Episerver Event Helper v3.0

Years pass and hopefully you learn something and become better!

One thing that have been poking on my conscience for a while is the old EPiEventHelper that I blogged about a couple of years ago.

What’s wrong with it?

There are two things I don’t like with it.

1) Unclear usage

I have seen that it has been some confusion with how to best access the content – using the eventArgs.Content or using this.

public class StandardPage : PageData, IPublishingContent
{
    public void PublishingContent(object sender, ContentEventArgs e)
    {
        // this is how to access the already cast instance of the content.
        StandardPage standardPage = this;

        //This is the content from the argument, needs to be casted to your Content Type. But to me it feels more correct.
        IContent contentFromArgument = e.Content;
    }

}

A common question is if the e.Content and this are two different instances (they are the same) and which one should be used (honestly I don’t have a recommendation).

2) Single Responsibility Principle

The second thing is that this implementation violates the Single Responsibility Principle. The class representing your Content Type should been seen as a View Model or Data Model and therefore it should not contain any behavior or business logic.

I have seen some examples where the logic found in the event is really large, making the Content Type class very messy and hard to understand.

Presenting – EPiEventHelper 3.0

What? Why 3.0?

Pavel Nezhencev already created a NuGet package with EPiEventHelper and later upgraded it to use Episerver 10, calling it version 2.0. If I want to publish my version with new features to NuGet.org I need to bump the version even further.

What’s new?

First of all I have stopped checking if the current Content implements the event interface. Instead I’m using to use the IoC container in Episerver where you as a developer can add your own things.

This means that you will need to decorate your implementations of the interfaces with the ServiceConfiguration attribute.

[ServiceConfiguration(typeof(IPublishingContent))]
public class PublishingContentEvent : IPublishingContent
{
    public void PublishingContent(object sender, ContentEventArgs e)
    {
        IContent content = e.Content;
        var standardPage = content as StandardPage;
        if (standardPage == null)
            return;

        // Do something with your standard page
    }
}

Breaking change: This also means that if you are using the previous version of EPiEventHelper, you can no longer refer to the content using this!

public void PublishingContent(object
    sender, ContentEventArgs e)
{
    this.Name = "Hello World!";
}
public void PublishingContent(object
    sender, ContentEventArgs e)
{
        e.Content.Name = "Hello World!";
}

Breaking change: The interface does not know anything about the class representing your Content Type and therefore you will need to cast e.Content yourself!

public void PublishingContent(object
    sender, ContentEventArgs e)
{
    this.Title = "Hello World!";
}
public void PublishingContent(object sender,
    ContentEventArgs e)
{
        var content = e.Content as StandardPage;
        if (content == null)
            return;

        content.Title = "Hello World!";
}

Unless…

I have created a workaround for this where you can have the same pattern to bind the Episerver events, but only apply if to specific Content Types AND the Content will also be typed!

New functionality: Each interface have an abstract base class where you specify which Content Type class you want to use. As usual you use the ServiceConfiguration attribute to bind your implementation to the event.

[ServiceConfiguration(typeof(IPublishingContent))]
public class PublishingStandardPage : PublishingContentBase<StandardPage>
{
    protected override void PublishingContent(object sender, TypedContentEventArgs e)
    {
        // Here you can access the standard page
        StandardPage standardPage = e.Content;
    }
}

You can also specify if the Content that the event applies to needs to be the exact Content Type or that the class would inherit from it by overriding the boolean property AllowInheritance.

protected override bool AllowInheritance { get { return true; } }

Tests

I’m still learning how to best create tests with Episerver, so I have added a Test Project where I want to test if the events are bound and unbound properly.

I have also added some tests to make sure that I have not missed any interfaces or classes. The project is found in the same GitHub Repo.

You’re welcome!

I would really recommend that you take your time to extract the code for your events from the Content Types.
Just putting the ServiceConfiguration attribute on your Content Types would instantiate the class in a way that doesn’t always play well with Episerver.

I’m planning to update the NuGet feed with my new version! Keep your eyes open for updates in Visual Studio if you have used the previous version.

How to NOT use properties on Content Types

I’ve seen more and more examples on how to NOT use properties on your Content Types – setting visit/Request specific information to them.

My example below is a very harmless, but I’ve seen examples where the price for an e-com site is set like this!

How do you mean?

The example would be to display the visitor’s IP, which is common to be different for each request.

First of all we have a Block Type:

[ContentType(GUID = "5597f215-f5cc-4fd0-bcff-eb06a44fc343")
public class MyPage : PageData
{
    public string MyProperty { get; set; }
}

And the controller set’s the value:

public class MyPageController : PageControllerBase<MyPage>
{
    public ActionResult Index(MyPage currentPage)
    {
        currentPage.MyProperty = Request.UserHostAddress;
        return View(currentPage);
    }
}

And a view displays it:

@model MyPage
Name: @Html.PropertyFor(m => m.Name)
IP: @Model.MyProperty

But what’s the problem?

The problem is that each instance of MyPage is cached and could basically be referred to as “static” – when a value is set to MyProperty, the value is set for all visitors.

For example when visitor A visits from IP address “192.168.0.10”, MyProperty is set with the IP. When visitor B visits from “192.168.0.11”, MyProperty will still have the value from visitor A until the controller sets the IP from visitor B.

Or even worse: When the the visitor A comes to the view and MyProperty is rendered there is a chance that visitor B comes to the controller and sets a new value to MyProperty – so that the view will render the IP from visitor B.

I’m sorry for the crappy GIF, but hope you get the point ;)

properties

So what should I do?

Make sure that you have a specific class that contains your information and create a new instance of this class for each Request. For my example I would have a viewmodel instead. In Episerver projects the ViewModel could also contain the Content itself:

[ContentType(GUID = "5597f215-f5cc-4fd0-bcff-eb06a44fc343")
public class MyPage : PageData
{
}

public class MyPageViewModel
{
    public string MyProperty { get; set; }
    public MyPage CurrentPage { get; set; }
}

With the Controller

public class MyPageController : PageControllerBase<MyPage>
{
    public ActionResult Index(MyPage currentPage)
    {
        var viewModel = new MyPageViewModel
        {
             MyProperty = Request.UserHostAddress,
             CurrentPage = currentPage
        };
        return View(viewModel);
    }
}

And with the View

@model MyPageViewModel
Name: @Html.PropertyFor(m => m.CurrentPage.Name)
IP: @Model.MyProperty

Again this a simple example, but say you’re having an e-com solution and want to display the price. You don’t want to display other visitor’s prices don’t you? ;)

Customize “Suggested Page/Block types” when creating content

I’ve found a neat way to improve the editor experience – IContentTypeAdvisor and it can be found in the EPiServer.Cms.Shell.UI.Rest namespace of the assembly EPiServer.Cms.Shell.UI.dll.

This interface is used to populate the list of suggested Content Types when creating new content.

This interface has one method GetSuggestions which returns an collection of ids of block types and takes the following input parameters:

  • iContent Parent
    The parent item where the editor wants to create the new content.
  • bool contentFolder
    This is true if the editor is creating your new content within a Content Asset Folder.
  • IEnumerable<string> requestedTypes
    This is a list of the kind of content to create. The value comes from the UI and therefore they are just strings like “episerver.core.blockdata” or “episerver.core.pagedata”. If created through a component in the asset pane it can also have the value of the CreateableTypes.

The default implementation in Episerver is to suggest the same content types as already created.

defaultcontenttypeadvisor
An example of usage would be to help the editor create a slideshow where the slideshow including each slide in it are blocks.

So what I want to do is to suggest the Slide block if the editor already has created a Slideshow block in that same folder.

CUT TO THE CODE

First of all I’ve have some new Block Types, one for the Slideshow that has a ContentArea where you put each slide represented by a SlideBlock.

And I will need to create an implementation of the IContentTypeAdvisor.
[ServiceConfiguration(typeof(IContentTypeAdvisor))]
public class ContentTypeAdvisor : IContentTypeAdvisor
{
    public IEnumerable<int> GetSuggestions(IContent parent, bool contentFolder, IEnumerable<string> requestedTypes)
    {
        ...
    }
}

Note that I’m registering this to Episerver’s IOC using the ServiceConfigurationAttribute. This is to tell Episerver that I want to use this implementation.

Episerver looks for all registered implementations of this interface instead of just 1, therefore I don’t need to do anything with the previous registrations (unless I want to, see further down).

What I want the advisor to do is check is whether the parent has any children that is a Slideshow block. If so the advisor will suggest the Slide Block.

bothadvisors
This is quite easy as can be seen in my SlideshowContentTypeAdvisor.

It is also a good practice to check whether the suggested content type (in this case the Slide Block) is available as child to the content parent and that the editor has the proper access rights to create a Slide Block).

But I want to use my Content Type advisor INSTEAD of the Default one?

It’s easy to remove the already registered implementation of IContentTypeAdvisor by implementing the IConfigurableModule interface, remove the current registrations and to register your own implementation instead.

slideshoadvisor

Look at my DependencyResolverInitialization for that.

Disable the “Suggested Content Types” feature

Look at DependencyResolverInitialization and just don’t register your own IContentTypeAdvisor after ejecting the existing registrations.

This will remove all registered implementations and your editor will never see any suggested content types.

noadvisors

Creating a Content Folder that only allows specific Content Types

If you need to organize the content among Assets, such as blocks and media, you can do this by creating your own Content Types that inherits from ContentFolder.

Unfortunately the Episerver UI only supports creating Content Folders in the folder section of the Asset pane, so you’ll need to find a way to create these new folders. If it’s a strict structure it’s very easy using the ContentRepository in Episerver. But if the editor should create them you’ll probably need to create a separate view for this or do some Dojo tweaks.

I might look more into the first option using some inspiration from Fredrik Vig‘s “Instant Templates“.

But for now, let’s focus on creating folders that only allows specific types.

Cut to the code

First of all, I need some Content Types that represents my folders – one that only allows Teaser Blocks and one that only allows Image Files, see Alloy Templates to look further into these two Content Types.

[ContentType(GUID = "964063f0-613b-4ce5-85a0-2d11fee0b905")]
[AvailableContentTypes(Availability.Specific, Include = new[] { typeof(TeaserBlock) })]
public class TeaserContentFolder : ContentFolder
{
}

[ContentType(GUID = "e730c883-f9b7-4d5e-b309-a00070159771")]
[AvailableContentTypes(Availability.Specific, Include = new[] { typeof(ImageFile) })]
public class ImageContentFolder : ContentFolder
{
}

At startup I will make sure that these two folders are created using an Initialization Module:

[InitializableModule]
[ModuleDependency(typeof(Web.InitializationModule))]
public class RestrictedContentFolderInitializationModule : IInitializableModule
{
    public void Initialize(InitializationEngine context)
    {
        IContentRepository contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();

    ContentReference siteAssetFolder = ContentReference.SiteBlockFolder;
        IEnumerable<ContentFolder> children = contentRepository.GetChildren<ContentFolder>(siteAssetFolder).ToList();

        CreateSpecificFolder<TeaserContentFolder>(children, "Teaser folder", siteAssetFolder, contentRepository);
        CreateSpecificFolder<ImageContentFolder>(children, "Image folder", siteAssetFolder, contentRepository);
    }

    public void Uninitialize(InitializationEngine context)
    {
    }

    private void CreateSpecificFolder<T>(IEnumerable children, string teaserFolderName, ContentReference siteAssetFolder, IContentRepository contentRepository) where T : IContent
    {
        if (children.Any(child => child.Name == teaserFolderName))
        {
            return;
        }

        var teaserFolder = contentRepository.GetDefault<T>(siteAssetFolder);
        teaserFolder.Name = teaserFolderName;
        contentRepository.Save(teaserFolder, SaveAction.Publish, AccessLevel.NoAccess);
    }
}

This will make sure that you’ll have two folders in the Assets Pane.

asset folders

So when I try to create any Blocks in the Teaser folder, I’ll only be able to create a Teaser block, and as usual when I’m only able to select one Content Type, it will automatically select it without giving me a list of available Content Types.

create teaser block

And when I try to upload files to the Image folder, I will only be able to upload files of that specific Media Type. Which in this example means, only images and not text files.

upload files

And when I try to create a Block in the Image folder, I will not be able to select any Block Types since none are available.

The code is also available on the GitHub Repository https://github.com/alfnilsson/RestrictedContentFolders

If you’re a Dojo Guru and have a way to tweak Episerver UI to allow the editor select which type of folder to create, please hit me up!

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.

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";
  }
}