linuse
Dec 11, 2013
  22492
(4 votes)

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

Comments

Martin Pickering
Martin Pickering Dec 12, 2013 11:37 AM

@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?

Dec 12, 2013 01:31 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.

Dec 13, 2013 09:40 AM

Great! Lots of new presents coming in 7.5!

Viktor
Viktor Dec 19, 2013 11:38 AM

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?

Dec 20, 2013 09:08 AM

@Viktor: Foreach on what?

Jonathan Roberts
Jonathan Roberts Apr 1, 2014 11:54 AM

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

Apr 1, 2014 12:40 PM

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

Peter S
Peter S Jun 1, 2014 08:31 PM

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

Jun 10, 2014 10:10 PM

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

Adam B
Adam B Aug 5, 2014 06:04 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

Aug 14, 2014 11:16 AM

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.

Adam B
Adam B Aug 14, 2014 04:40 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

Aug 18, 2014 05:34 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

Aug 25, 2014 10:14 PM

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
Shoma Gujjar Jul 3, 2015 04:25 PM

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

Jiri Cepelka
Jiri Cepelka Feb 7, 2018 06:46 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);
}

Feb 8, 2018 03:33 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
Jiri Cepelka Feb 8, 2018 11:42 AM

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.
Latest blogs
Optimizely SendGrid SMTP host is deprecated

SendGrid is a services for sending email that is included in Optimizely DXP. Previously smtp.episerver.net was the recommended SMTP server to use,...

Tomas Hensrud Gulla | Dec 4, 2022 | Syndicated blog

Hosting Optimizely CMS 12 on Docker Engine

Since Optimizely CMS can now be deployed as a Docker container, here is a demonstration of building, running and scaling an Optimizely CMS 12 site ...

Stefan Holm Olsen | Dec 4, 2022 | Syndicated blog

How to use CacheTagHelper with content areas in Optimizely CMS 12

I might be going out on a limb here - if you have a better solution, feel very free to share it!  Upgrading your Optimizely web application from .N...

Andreas J | Dec 2, 2022

The 1001st Piece in your 1000 Piece Puzzle: .NET Default Interface Functions

I was recently working with a client who wanted a reasonably large subsystem added to Optimizely that would add automated management to their...

Greg J | Nov 28, 2022 | Syndicated blog