Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Markus Johansson 1936 posts 5864 karma points MVP 2x c-trib
    Feb 16, 2018 @ 10:01
    Markus Johansson
    0

    Multiple property value converters for a property editor

    Hi!

    I have a scenario with a MultiNodeTreePicker2 where I don't what to have the "whole" IPublishedContent but just the Udi's from the property.

    I figured that I could implement a Property Value Converter that handles "IEnumerable

    [PropertyValueType(typeof(IEnumerable<Udi>))]
    public class MultiNodeTreePicker2UidPropertyConverter : PropertyValueConverterBase { 
       .... 
    }
    

    Now all the calls, even the ones with "cointent.GetPropertyValue< IEnumerable< IPublishedContent > >("yada") is routed to my new property value converter and the core converter is never used.

    What I'm I doing wrong? Or can there only be one property value converter per property editor?

    // m

  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Feb 19, 2018 @ 09:37
    Lee Kelleher
    2

    Hi Markus,

    You'll need to de-register the existing (core) property-value converter, like this...

    namespace Our.Umbraco.Web
    {
        public class Bootstrapper : ApplicationEventHandler
        {
            protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
            {
                PropertyValueConvertersResolver.Current.RemoveType<Umbraco.Web.PropertyEditors.ValueConverters.MultiNodeTreePickerPropertyConverter>();
            }
        }
    }
    

    Then your custom converter will be picked up by Umbraco core's startup code.

    Cheers,
    - Lee

  • Markus Johansson 1936 posts 5864 karma points MVP 2x c-trib
    Feb 19, 2018 @ 09:42
    Markus Johansson
    0

    Thanks Lee!

    My problem is that I want my convert to take care of the Udi's and then the core converter to return ipublished content. So I don't want to de-register it I want them to run side by side but respecting the attribute:

    [PropertyValueType(typeof(IEnumerable<Udi>))]
    

    / m

  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Feb 19, 2018 @ 09:49
    Lee Kelleher
    0

    Hi Markus,

    Ah ok, then yes - currently only one property value converter can be registered to a property-editor.

    The .GetPropertyValue<T> method doesn't add any influence to the value-converter at all. The <T> bit is ultimately used by the TryConvertTo<T> extension method, of which a .NET TypeConverter could be registered and used to convert from one object-type to another.

    It requires a bit more code, but it's possible.

    Let me know if that doesn't make sense, I'll try to provide code snippets.

    Cheers,
    - Lee

  • Markus Johansson 1936 posts 5864 karma points MVP 2x c-trib
    Feb 19, 2018 @ 13:52
    Markus Johansson
    0

    Hi!

    So if I understand you correct, since there is only one property value converter per property editor - I need to go with my custom converter, that returns a udi, and then hook into the TryConvertTo-extention and handle when there is a IPublishContent-type parameter to this method?

    If you have any hints on how to do the last part that would be very much appreciated.

    Thanks again Lee!

  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Feb 19, 2018 @ 14:42
    Lee Kelleher
    100

    Hi Markus,

    The TryConvertTo extension method (in Umbraco.Core) does various things to attempt to return the correct type, including calling any registered TypeConverter classes.

    Here's an example that I've just coded... keep in mind that I have not tested this at all, you'll need to tinker with it

    public class MyUdiConverterType : TypeConverter
    {
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(IPublishedContent) || destinationType == typeof(IEnumerable<IPublishedContent>))
                return true;
    
            return base.CanConvertTo(context, destinationType);
        }
    
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            var helper = new UmbracoHelper(UmbracoContext.Current);
    
            // deal with single items
            if (destinationType == typeof(IPublishedContent))
            {
                if (value is GuidUdi guidUdi)
                    return helper.TypedContent(guidUdi.Guid);
            }
    
            // deal with multiple items
            if (destinationType == typeof(IEnumerable<IPublishedContent>))
            {
                if (value is IEnumerable<GuidUdi> guidUdis)
                    return helper.TypedContent(guidUdis.Select(x => x.Guid));
            }
    
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
    

    Then in your app-startup code, you'll need to dynamically register the custom TypeConverter...

    TypeDescriptor.AddAttributes(typeof(Udi), new TypeConverterAttribute(typeof(MyUdiConverterType)));
    

    At this point, it's worth noting that Umbraco core has it's own internal TypeConverter for Udi objects: https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Core/UdiTypeConverter.cs I'm not sure if this impacts the custom TypeConverter code?

    ...

    So once the custom TypeConverter is in place, whenever the content.GetPropertyValue<IEnumerable<IPublishedContent>>("alias") is called... your custom property-value-converter will return a collection of Udi objects, then Umbraco will use TryConvertTo to cast/convert the IEnumerable<Udi> to IEnumerable<IPublishedContent>, by running it through the MyUdiConverterType.ConvertTo method.

    You'll probably need to do some tinkering around with the ConvertTo method to make sure that returns exactly what you want.

    I hope that makes sense? It feels like there are a few moving parts that need aligning with all that?

    Cheers,
    - Lee

  • Markus Johansson 1936 posts 5864 karma points MVP 2x c-trib
    Feb 19, 2018 @ 14:52
    Markus Johansson
    1

    Hi Lee!

    What a hero you are, thanks for explaining this in a very clear way!

    I'll try to get this working and get back when I have something running.

    Thanks man!

    // m

  • Markus Johansson 1936 posts 5864 karma points MVP 2x c-trib
    Feb 19, 2018 @ 15:52
    Markus Johansson
    1

    Thanks Lee!

    I got it to work!

    public class MyUdiConverterType : TypeConverter
    {
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(IPublishedContent) || 
                destinationType == typeof(IEnumerable<IPublishedContent>) || 
                destinationType == typeof(List<IPublishedContent>) || 
                destinationType == typeof(IList<IPublishedContent>))
                return true;
    
            return base.CanConvertTo(context, destinationType);
        }
    
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            var helper = new UmbracoHelper(UmbracoContext.Current);
    
            // deal with single items
            if (destinationType == typeof(IPublishedContent))
            {
                if (value is GuidUdi guidUdi)
                    return helper.TypedContent(guidUdi.Guid);
            }
    
            // deal with multiple items
            if (destinationType == typeof(IEnumerable<IPublishedContent>))
            {
                if (value is IEnumerable<GuidUdi> guidUdis)
                {
                    var list = new List<IPublishedContent>();
                    foreach (var item in guidUdis)
                    {
                        list.Add(helper.TypedContent(item.Guid));
                    }
    
                    return list;
                }
    
            }
    
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
    

    My custom property value converter

    [PropertyValueType(typeof(IEnumerable<GuidUdi>))]
    [PropertyValueCache(PropertyCacheValue.Object, PropertyCacheLevel.ContentCache)]
    [PropertyValueCache(PropertyCacheValue.Source, PropertyCacheLevel.Content)]
    [PropertyValueCache(PropertyCacheValue.XPath, PropertyCacheLevel.Content)]
    public class MultiNodeTreePickerPropertyConverter : PropertyValueConverterBase
    {
        public override bool IsConverter(PublishedPropertyType propertyType)
        {
            if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePicker2Alias))
                return true;
    
            return false;
        }
    
        public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
        {
    
            if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePicker2Alias))
            {
                var nodeIds = source.ToString()
                    .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
                    .Select(GuidUdi.Parse).ToList();
    
                return nodeIds;
            }
            return null;
        }
    
        public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview)
        {
            if (source == null)
            {
                return null;
            }
    
            return (IEnumerable<GuidUdi>) source;
    
        }
    
    }
    

    And then the registration during startup:

    public class Startup : ApplicationEventHandler
    {
        protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            PropertyValueConvertersResolver.Current.RemoveType<Umbraco.Web.PropertyEditors.ValueConverters.MultiNodeTreePickerPropertyConverter>();
    
            TypeDescriptor.AddAttributes(typeof(GuidUdi), new TypeConverterAttribute(typeof(MyUdiConverterType)));
            TypeDescriptor.AddAttributes(typeof(IEnumerable<GuidUdi>), new TypeConverterAttribute(typeof(MyUdiConverterType)));
        }
    
    }
    

    Then my view will return a List of GuidUdi's when not providing the type parameter, but when I provide the type parameter I get IPublishedContent back.

      <p>
            @Umbraco.Field("relatedContent")
      </p>    
       <p>
       Val:
       @{
            var test = Model.Content.GetPropertyValue<IEnumerable<IPublishedContent>>("relatedContent");
         }
    
       @if (test != null)
       {
          <text>Count:</text> @test.Count()
      }
    

    Thanks a lot for taking the time to help me on this one! #h5yr!

  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Feb 19, 2018 @ 16:11
    Lee Kelleher
    0

    Cool! Happy to hear that it worked for you.

Please Sign in or register to post replies

Write your reply to:

Draft