Skip to content

Instantly share code, notes, and snippets.

@robertjf
Last active October 4, 2016 19:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertjf/78a97240e4304b8e5bcc to your computer and use it in GitHub Desktop.
Save robertjf/78a97240e4304b8e5bcc to your computer and use it in GitHub Desktop.
NuPicker TypeConverter for Ditto - this allows Ditto to convert from a nuPickers.Picker object to the target type
using nuPickers;
using Our.Umbraco.Ditto;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Web;
using Umbraco.Core.Models;
using Umbraco.Web;
namespace Extensions.TypeConverters
{
/// <summary>
/// Converts a NuPicker value into a Ditto object or string.
/// </summary>
/// <remarks>
/// This can be used with a single type (e.g. string) or IEnumerable<>.
///
/// If the target type is string, then this will return the Name(s) of each picked content node.
/// Otherwise it will use Ditto to try and convert the IPublishedContent item(s) to the target type.
///
/// Handles single objects as well as IEnumerable<>.
///
/// E.G.:
///
/// [TypeConverter(typeof(DittoNuPickerTypeConverter))]
/// public IEnumerable<MyPoco> MyProperty { get; set; }
///
/// // Returns a single MyPoco
/// [TypeConverter(typeof(DittoNuPickerTypeConverter))]
/// public MyPoco MyProperty { get; set; }
///
/// // Returns a list of strings based on the Name of each item.
/// [TypeConverter(typeof(DittoNuPickerTypeConverter))]
/// public IEnumerable<string> MyProperty { get; set; }
///
/// // Returns the Name of the first item.
/// [TypeConverter(typeof(DittoNuPickerTypeConverter))]
/// public string MyProperty { get; set; }
/// </remarks>
public class DittoNuPickerTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return typeof(Picker).IsAssignableFrom(sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
MethodInfo dittoAs = typeof(Our.Umbraco.Ditto.PublishedContentExtensions)
.GetMethod(
"As",
BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly,
null,
new Type[] {
typeof(IPublishedContent),
typeof(Action<ConvertingTypeEventArgs>),
typeof(Action<ConvertedTypeEventArgs>),
typeof(CultureInfo) },
null);
// get type of objects to be returned in the collection
Type outputItemType = context.PropertyDescriptor.PropertyType;
bool isList = (outputItemType.IsGenericType && outputItemType.GetGenericTypeDefinition() == typeof(IEnumerable<>));
bool isString = isList ?
typeof(string).IsAssignableFrom(outputItemType.GetGenericArguments()[0]) :
outputItemType.IsAssignableFrom(typeof(string));
// create a list for the output collection
// Get the list as IPublishedContent.
var picker = value as Picker;
var items = picker.AsPublishedContent();
if (isList)
{
object outputList = Activator.CreateInstance(typeof(List<>).MakeGenericType(outputItemType.GetGenericArguments()[0]));
foreach (IPublishedContent inputItem in items)
{
if (isString)
{
((IList)outputList).Add(inputItem.Name);
}
else {
((IList)outputList).Add(
dittoAs
.MakeGenericMethod(new[] { outputItemType })
.Invoke(null, new[] { inputItem, null, null, null })
);
}
}
return outputList;
}
else
{
if (isString)
{
return items.Any() ? items.FirstOrDefault().Name : null;
}
else
{
return dittoAs
.MakeGenericMethod(new[] { outputItemType })
.Invoke(null, new[] { items.FirstOrDefault(), null, null, null });
}
}
}
}
}
@JimBobSquarePants
Copy link

You can make this a lot simpler by inheriting one of the built in converters. I'm using latest MyGet build here...

using System;
using System.ComponentModel;
using System.Globalization;

using nuPickers;

using Our.Umbraco.Ditto;

/// <summary>
/// Provides a unified way of converting the <see cref="Picker"/> type to the given <see cref="Type"/>
/// </summary>
public class NuPickerMultiNodePickerConverter : DittoMultiNodeTreePickerConverter
{
    /// <summary>
    /// Returns whether this converter can convert an object of the given type to the type of this converter,
    /// using the specified context.
    /// </summary>
    /// <param name="context">
    /// An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.
    /// </param>
    /// <param name="sourceType">
    /// A <see cref="T:System.Type"/> that represents the type you want to convert from.
    /// </param>
    /// <returns>
    /// true if this converter can perform the conversion; otherwise, false.
    /// </returns>
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string) || sourceType == typeof(Picker))
        {
            return true;
        }

        return base.CanConvertFrom(context, sourceType);
    }

    /// <summary>
    /// Converts the given object to the type of this converter, using the specified context and culture information.
    /// </summary>
    /// <param name="context">
    /// An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.
    /// </param>
    /// <param name="culture">
    /// The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.
    /// </param>
    /// <param name="value">
    /// The <see cref="T:System.Object"/> to convert.
    /// </param>
    /// <returns>
    /// An <see cref="T:System.Object"/> that represents the converted value.
    /// </returns>
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        Picker picker = value as Picker;
        if (picker != null)
        {
            return base.ConvertFrom(context, culture, picker.PickedKeys);
        }

        return base.ConvertFrom(context, culture, value);
    }
}

@robertjf
Copy link
Author

yep, that works really nicely - I'd seen that in the source earlier but hadn't considered using it like this...

however, will it return a single object (string or POCO) if the target type isn't IEnumerable<>?

@robertjf
Copy link
Author

@JimBobSquarePants - Just tested it; fails with the following:

NuPickerMultiNodePickerConverter cannot convert from System.String[].

looks like it needs some tweaking.

@JimBobSquarePants
Copy link

What Ditto version are you running and what configuration is the picker set to. json, xml, or csv?

It's just that I've been running that particular bit of code now for weeks without issue.

@sniffdk
Copy link

sniffdk commented Jun 19, 2015

Just tipping in, running version 0.7.0 it will indeed fail with the error 'cannot convert from System.String[]' if either the property is not an IEnumerable<> or if the PickedKeys is an empty collection.
What I ended up doing for now is:

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
    var picker = value as Picker;
    if (picker == null || !picker.PickedKeys.Any())
    {
        return Enumerable.Empty<string>(); //base.ConvertFrom(context, culture, Enumerable.Empty<string>());
    }

    return base.ConvertFrom(context, culture, picker.PickedKeys);
}

And then I have a NuPickersSingleConverter

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var picker = value as Picker;
if (picker == null || !picker.PickedKeys.Any())
{
    return null;
}

    return base.ConvertFrom(context, culture, picker.PickedKeys.First());
}

Not too pretty, but it works 😃

@naepalm
Copy link

naepalm commented Oct 4, 2016

Hey, @JimBobSquarePants, does your technique still work? Or is there a better way now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment