Linus Ekström
Dec 16, 2013
  19631
(3 votes)

Restriction of content types in properties

There has been a few blog and forum posts about how to restrict what can be added to a content area. In this blog post I’ll show a new attribute in EPiServer 7.5 and how this can be used to accomplish this.

Restricting a ContentArea

The AllowedTypes attribute (placed in the EPiServer.Shell assembly) can be applied to a property like this:

[AllowedTypes(typeof(ProductPage))]
public virtual ContentArea RelatedContentArea { get; set; }

When an item that is not part of the allowed types, is being dragged over this property, it will be greyed out and the editor will not be able to add the item to the property.

DndAllowedTypes

You can specify several allowed types as well as specifying inherited types:

[AllowedTypes(new [] {typeof(PageData), typeof(BlockData)})]
public virtual ContentArea RelatedContentArea { get; set; }

 

Restrict content reference properties

The AllowedTypes attribute can be used for ContentReferences as well:

[AllowedTypes( typeof(ProductPage))]
public virtual ContentReference SomeLink { get; set; }

This results in the same behavior as for content areas when dragging items to the property: only items of the type “ProductPage” can be added to the property. Since it’s also possible to add items by opening the content selector dialog this dialog has also got support to restrict types. Items that are not allowed according to the AllowedTypes attribute are not selectable.

Note: Though the content selector dialog restricts selection of items that are not allowed, there is room for improvements to better indicate what is selectable or not since there are no differences of selectable/non selectable items at the moment.

Known limitations

There are currently a few known bugs and limitations that you might want to be aware of:

  • Restriction does not work for overlays when editing on page. (update: this is now available in the NUGET feed)
  • No server validation. Currently, the attribute only adds restriction in the UI. We hope to be able to add support for server validation soon which would also give the posibility validate your custom properties.
  • No validation when creating local blocks in content areas. If you use the new feature to add local blocks to a content area, there is currently no filtering of the content types when you create your new block.

Hopefully, we can add support for the limitations above quite soon.

Dec 16, 2013

Comments

Dec 16, 2013 01:21 PM

Oh great. I was missing that on Ben's blog post the other day!

Dec 16, 2013 02:15 PM

Are allowed types possible to set via admin mode like on page types?

Dec 16, 2013 02:39 PM

@Per: No, this support is only possible by defining the attribute on your models and not available through the administrative interface.

Dec 17, 2013 01:49 PM

Dammit. There goes one of my favourite demos when delivering CMS training ;)

Dang Viet Hung
Dang Viet Hung Dec 18, 2013 10:31 AM

thanks you for great post. I have a question. I hope that you can help me.
In Episerver 7.5, the ContentArea property use _ContentAreaTree.js for rendering items and i have to customize it for request.

This question is: How to reload tree in ContentArea affter changing model of the tree in js?
Thanks,

Dec 20, 2013 01:26 PM

Hi!

The _ContentAreaTree base class is a very internal implementation for the EPiServer user interface and I would not recommend customizing this since you are then very deep into the implementation of this editor, which might change without notice. Without knowing what you are trying to achieve it's hard to give a recommendation of how to solve your requirements.

Justin Le
Justin Le Jan 13, 2014 06:36 AM

Hi,

I've tried this with standard Alloy installation and there is weird thing, I don't know if it's the know limitation or not. Simply edit the PageListBlock, and put AllowedTypes (typeof(ArticlePage)) to the property "Root" which is PageReference.

When I go to Form Editing, try to drag and drop my Article page to the property, the property value is "undefined NaN", I got an error:

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'EPiServer.Core.PageReference' because the type requires a JSON string value to deserialize correctly. To fix this error either change the JSON to a JSON string value or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path 'isPreferredLanguageAvailable', line 1, position 32.

But the picker work pretty well. Is it a bug or known bug?

Jan 14, 2014 01:46 PM

Chao Dung,
There is a workaround for getting what you're trying functioning. The problem is that when you set the AllowedTypes attribute a necessary conversion metadata-info "disappears" for the root property. To add it again define the attribute like:

[AllowedTypes(typeof(ArticlePage), AllowedTypesFormatSuffix = "reference")]

You should of course not have to do that... I will report a bug on this to remove the need for extra config.

André Hedberg
André Hedberg Jan 24, 2014 08:55 AM

There's one more limitation if I'm not mistaken, it does not work with interfaces. :)

Jan 24, 2014 10:03 AM

@André: Actually, it is possible to use this attribute with interfaces and base classes though you might need some additional setup. Since the allowance check is done on the client, we need to calculate if a given object is allowed to be dropped on an area. However, we do not send the entire class structure for all objects to the client. By default, all content types are registered and sent to the client as well as the base types and interfaces (PageData, ContentFolder, IContentMedia etc.). If you want to add your own base class or interface this can simply be done by adding an UIDescriptor:

public interface IFancyPage
{
}

[UIDescriptorRegistration]
public class IFancyPageUIDescriptor : UIDescriptor
{
}

André Hedberg
André Hedberg Jan 24, 2014 02:58 PM

Oh, lovely! Thank you Linus!

André Hedberg
André Hedberg Jan 24, 2014 04:06 PM

It seems that I do not fully understand the implementation. :)

I've got a marker interface "IContentColumnData" for which every BlockData or PageData can be marked with.
I then create a IContentColumnUIDescriptor : UIDescriptor.
Then mark my ContentArea property with the AllowedTypes attribute: typeof(IContentColumnData). Still, I'm not able do drag any of the marked classes to my ContentArea.

What am I missing?

Jan 30, 2014 02:14 PM

André: Just tried the code again and it works fine. Are you sure that you are not experiencing the bug described above with "on page editing"? If so, the latest patch of the EPiServer Framework NUGET package should fix this.

Eric Bruno
Eric Bruno Feb 3, 2014 11:26 AM

I have tried the exact same thing as André described above, and it's not working for me neither. I have all the latest patches installed.

What I have done exactly is the following:

- Created a interface called IAllowRightColumn
- Created a UIDescriptor of the type IAllowRightColumn
[UIDescriptorRegistration]
public class AllowRightColumnUIDescriptor : UIDescriptor
{
}

- Marked a blockdata with the interface
public class FreeTextBlockData : BlockData, IAllowRightColumn

- Added AllowedTypes to a contentarea with the IAllowRightColumn as type
[AllowedTypes(typeof(IAllowRightColumn))]

Is this not the correct way to get it to work?

Feb 3, 2014 03:35 PM

@Eric: That's the same code that I'm using except for different naming of my interface. I need some more info in order to be able to troubleshoot:
Are you allow to drop anything or nothing on your content areas?
Can you find your interface if you search the response from the following service: [uipath]/shell/Stores/uidescriptor/

Eric Bruno
Eric Bruno Feb 4, 2014 04:14 PM

@Linus: I can't drop anything on the content area. Nothing works.

I find my interface in the response and it looks like this:
{"typeIdentifier":"sublime.site.models.iallowrightcolumn","baseTypeIdentifier":null,"iconClass":null,"containerTypeIdentifier":null,"containerTypes":null,"mainWidgetType":null,"disabledViews":null,"dndTypes":["sublime.site.models.iallowrightcolumn"],"languageKey":"iallowrightcolumn","defaultView":null,"createView":null,"publishView":null,"availableViews":[],"commandIconClass":null,"sortKey":null,"isPrimaryType":false}

Lars Loennechen Skjelbek
Lars Loennechen Skjelbek Feb 6, 2014 03:02 PM

@Linus: I experience the same behaviour as @Eric and @Andre (it doesn't work).

However, I peeked at the disassembled code (I'm sorry) and made a custom MyProjectAllowedTypes attribute for our project which does the same thing as EPiServers AllowedType in the OnMetadataCreated method, except that we use all classes that are assignable from the types specified in the attribute, not just the types themselves. Is there any reason why EPiServer's implemented isn't like that (maybe perfomance drawbacks)?

Feb 7, 2014 04:02 PM

Hi!

Sorry for the delay, installed a new OS this week and after setting up a new site I could finally reproduce the issues you are having. After looking into the code some more my guess is that the reason that we got different behaviour was due to the code depending on in which order types are scanned at start up.

@Lars: It's great that you found a solution. As you mention, there might be performance penalties with doing calculations in OnMetadataCreated since this is called each time an editor visits a content item (page, block , etc.) in the editorial view. Therefore I've done an alternative solution that you can run at start up that makes it possible to use the built in attribute until we can add this support to the core (This sample only shows one interface but I guess it can easily be expanded to handle multiple interfaces):

[ModuleDependency(typeof(Web.InitializationModule))]
public class UIDescriptorInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
var uiDescriptorRegistry = ServiceLocator.Current.GetInstance();
var descriptors = uiDescriptorRegistry.GetAllRegisteredUIDescriptors();

var fancyType = typeof(IFancyItem);//Where IFancyItem is your interface

foreach (UIDescriptor descriptor in descriptors)
{
if (fancyType.IsAssignableFrom(descriptor.ForType))
{
descriptor.DndTypes.Add(fancyType.FullName.ToLowerInvariant());
}
}
}

Jonas Boman
Jonas Boman Feb 13, 2014 12:57 PM

dbl post

Jonas Boman
Jonas Boman Feb 13, 2014 12:57 PM

Any updates on resolving the issue AllowedTypes not working?!

Feb 13, 2014 02:06 PM

The bug with on page edit not working correctly has been released (just install the latest patches). The other issues/limitations still exists.

Justin Le
Justin Le Mar 1, 2014 10:40 AM

Oh I missed the comment from Erik. Thanks alot.

Mar 20, 2014 08:39 PM

Is there a way not to allow the user to create a new block?

Mar 24, 2014 10:31 AM

You can actually override the translated text message that contains the create link. Try adding something like this in your "local" language files: