Views: 18504
Number of votes: 4
Average rating:

Single/Multiple selection in EPiServer 7.5

This is an updated blog post on how to set up single/multiple selection from a list of predefined values (The original blog post can be found here). In this blog post we are going to use two new attributes in EPiServer 7.5 that are located in the EPiServer.Shell.ObjectEditing namespace in the EPiServer.UI assembly: SelectOne and SelectMany. These can be defined on a property and requires a reference to a class implementing the ISelectionFactory interface:

[ContentType]
public class SamplePage : PageData
{
    [SelectOne(SelectionFactoryType=typeof(LanguageSelectionFactory))]
    public virtual string SingleLanguage { get; set; }
 
    [SelectMany(SelectionFactoryType = typeof(LanguageSelectionFactory))]
    public virtual string MultipleLanguage { get; set; }
}
 
public class LanguageSelectionFactory : ISelectionFactory
{
    public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata)
    {
        return new ISelectItem[] { new SelectItem() { Text = "English", Value = "EN" }, new SelectItem() { Text = "Guinean", Value = "GN" } };
    }
}
 
The result looks something like this:

SelectionFactoryScreenShot

Creating your own attributes

Since you wan’t to follow the DRY principle and avoid adding the reference to the selection factory in a lot of attributes it might be good creating your own attribute if you will use them in several places. This can be done by inherriting from the EPiServer attributes and just overriding the SelectionFactoryType property:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class LanguageSelectionAttribute : SelectOneAttribute
{
    public override Type SelectionFactoryType
    {
        get
        {
            return typeof(LanguageSelectionFactory);
        }
        set
        {
            base.SelectionFactoryType = value;
        }
    }
}
Dec 11, 2013

Martin Pickering
(By Martin Pickering, 12/12/2013 12:37:30 PM)

@Linus
Thanks for a great post and a most welcome feature.
I think the screen shot image might have the two fields backwards.

From a code-first development point of view isn't the semantic difference between a SelectOne scenario and a SelectMany scenario indicated by the Property's type being defined as a scalar type or an array of a scalar type.
For example,
public virtual string LanguageSelection {get; set;} // is an indication that a single or scalar value is expected

public virtual string[] LanguageSelection {get; set;} // is an indication that multiple values are possible

This, to me, speaks more clearly about true intent and is more straight forwards to subsequently consume in downstream code, i.e. it avoids having to de-multiplex the Content Model Property value all the time.

Is there any support that you guys can give to this sort of Design/Development Use Case rather than always requiring exotic Attribute Classes please?

linus.ekstrom
(By linus.ekstrom, 12/12/2013 2:31:44 PM)

@Martin
Thanks for the feedback. I copied the image from last years blog post, thus the small difference compared to the code.

Regarding property types that arrays (or IEnumerables) the support for this in EPiServer core is somewhat limited. You can create custom properties that return complex types, including arrays and IEnumerables, but it requires some work and it delegates most of the work to the custom property and it's therefore hard to build a good UI support on top of the current implementation.

I totally agree with you that declaring properties that are IEnumerables should be part of the core without any custom work from the partners side. I actually had a standing phrase to state this need on our weekly meeting similar to the classic quote by Cato (http://en.wikipedia.org/wiki/Carthago_delenda_est) :). Well have your input in mind when implementing better support for this and I can just hope that it's not in a too far future.

alf.nilsson
(By alf.nilsson, 12/13/2013 10:40:21 AM)

Great! Lots of new presents coming in 7.5!

Viktor
(By Viktor, 12/19/2013 12:38:43 PM)

Just tried this out, seems to work like a charm! Got an issue, when trying to do a simple foreach, the item becomes a "char". Why is that?

linus.ekstrom
(By linus.ekstrom, 12/20/2013 10:08:47 AM)

@Viktor: Foreach on what?

Jonathan Roberts
(By Jonathan Roberts, 4/1/2014 1:54:50 PM)

Hi, Im using 7.5 and even after adding using EPiServer.Shell.ObjectEditing; I can't use SelectOne or SelectMany. Am I missing something?

linus.ekstrom
(By linus.ekstrom, 4/1/2014 2:40:53 PM)

@Jonathan: Have you added the EPiServer.CMS.UI.Core NUGET package to your project/solution?

Peter S
(By Peter S, 6/1/2014 10:31:27 PM)

Is there any way I could add css styling to each individual checkbox label? Preferably by looping each select item.

linus.ekstrom
(By linus.ekstrom, 6/11/2014 12:10:26 AM)

@bmdeveloper: You could but that would probably mean that you have to make a custom editor.

AB
(By AB, 8/5/2014 8:04:01 PM)

Hi, this is a really great post. Thanks for sharing.

I have implemented the above for a SelectMany property which is rendering a list of child nodes generated from a page reference. The list appears correctly and when I publish the page the data is saved. However, the saved values do not re-bind to the check box list once saved. All the items in the check box list are cleared.

Is there something additional that I am missing here?

Thanks

linus.ekstrom
(By linus.ekstrom, 8/14/2014 1:16:00 PM)

Hi Adam!

Could you provide some more information on how to recreate this. What is the datatype that you are using for instance for your property? I have only tested this with a string as a backing datatype so it might be that it does not handle other data types.

AB
(By AB, 8/14/2014 6:40:07 PM)

Hi Linus,

Thanks for coming back to me.

Here is the code sample for the property:

[SelectMany(SelectionFactoryType = typeof(ContactSelectionFactory))]
public virtual string Contacts { get; set; }

and here is the ContactSelectionFactory class:

public class ContactSelectionFactory : ISelectionFactory
{
public IEnumerable GetSelections(ExtendedMetadata metadata)
{
var repository = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance();
var contactContainer = repository.Get(ContentReference.StartPage);

var list = new List();

if (contactContainer == null || contactContainer.ContactsRoute == null) return list;

var contacts = repository.GetChildren(contactContainer.ContactsRoute);
list.AddRange(contacts.Select(contact => new SelectItem { Text = contact.Name, Value = contact.ContentLink.ID }));

return list;
}
}

Thanks

Adam

linus.ekstrom
(By linus.ekstrom, 8/18/2014 7:34:07 PM)

Hi!

I've tested your code and can reproduce your problem. The main problem seem to be that you are returning the value as an integer instead of a string which the editor does not seem to like. If I change the value to a string, using the ToString()-method, it works better. However, I can see some sort of timing issue when selecting multiple values at once where all items it not saved. I have create a bug report to investigate both these issues:
Bug #116628: Multiple selection editor not saving values correctly

linus.ekstrom
(By linus.ekstrom, 8/26/2014 12:14:29 AM)

Hi again Adam. It seems that the issue with timing and saving multiple values was actually fixed and released last friday. The issue with values other than strings still exists but just use ToString() on your content reference and you should be fine if you upgrade to the latest NUGET packages.

Shoma Gujjar
(By Shoma Gujjar, 7/3/2015 6:25:01 PM)

Is it possible to generate the list in a prent-child format with features like collapsing and expanding the list?

Jiri Cepelka
(By Jiri Cepelka, 2/7/2018 7:46:40 PM)

Is there some reason to override virtual property Type SelectionFactoryType, Linus Ekström

On my tests it seems no.

Alternative implementation:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class LanguageSelectionAttribute : SelectOneAttribute
{
    public LanguageSelectionAttribute() => SelectionFactoryType = typeof(LanguageSelectionFactory);
}

linus.ekstrom
(By linus.ekstrom, 2/8/2018 4:33:35 AM)

Hi Jiri!

I had no problem getting this to work with a standard property override, so you can use that pattern instead:

using EPiServer.Shell.ObjectEditing;
using System;
using System.Collections.Generic;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class LanguageSelectionAttribute : SelectOneAttribute
{
    public override Type SelectionFactoryType
    {
        get
        {
            return typeof(LanguageSelectionFactory);
        }

        set
        {
            base.SelectionFactoryType = value;
        }
    }
}

internal class LanguageSelectionFactory : ISelectionFactory
{
    public IEnumerable GetSelections(ExtendedMetadata metadata)
    {
        return new List { new SelectItem { Text = "123", Value = "123"} };
    }
}

Jiri Cepelka
(By Jiri Cepelka, 2/8/2018 12:42:18 PM)

Hi Linus,

ah, ok. It seemed to me chaotic to set virtual property value at base to something unknown to me while overridden property returns always the same but maybe it is just my impression.

😜

Please login to comment.