Issues

Custom Property Value Converters

For the past several years, my role in the workplace has been “the JavaScript guy”. As frameworks were born, enjoyed their moment in the spotlight, and died an ignoble death, I was there using them: jQuery, Angular, React, sometimes even Vanilla JS (which is an old school framework we hipster coders enjoy). And no matter which framework it was, ultimately I’d be using it to make a “cool thing” that involved a map.

It was always a map.Clients love maps. Especially complex maps that do cool things.

Even when they have only one office for their customers to locate.

I made a lot of map-based sites in the past few years, is what I’m saying.

The catch with being the JavaScript guy in an Umbraco team, other than having to explain that yes, the S is actually supposed to be capitalized, is that you might find yourself getting into the back end code less and less frequently. Other team members get to handle the delicious C# and database tasks in order to free you up to make yet another map do another cool thing. Only this time it’s Chinese maps, so you need to use Baidu.

Which is documented in Chinese.

When we’re talking about human languages (as opposed to computer languages), I know only two: English, and really bad Spanish. I can order tacos and ask for directions to the library, but that’s about it.

So registering for the Baidu API key was a bit of a painful process.

I’m getting off topic here. This is not an article about maps. Or tacos. (I could really go for some tacos right now).

So what is the topic?

As the JavaScript guy, I’ve recently been making some custom property editors for the team for projects that don’t involve maps. Since property editors are little more than Angular controllers and views, they’re fairly straightforward for a front-end developer to set up. Umbraco has some great documentation for them here. In my case, I always like to keep this little gem handy for when I need a quick refresher on the basics:

Yes, that’s right. It’s a workshop booklet signed by the mighty Per himself, to me, with a heart around my name. It’s one of my prized possessions, an heirloom that someday I’ll bequeath to a young distaff relative as I shuffle off this mortal coil.

I’m not saying that I cradle it as I fall to sleep… but I’ve thought about it.

Custom property editors are rad. (Does anyone say “rad” anymore?) They’re a great way to build a custom editor for data that your client needs, without leaving a messy UI experience that makes it hard for their staff to make use of. As the creation of a custom property editor is covered in detail via the link I provided above, I won’t reiterate the steps to creating one here.

Ugh, JSON.

One potential problem with custom property editors is that they often save data to a JSON format. And don't get me wrong. I love JSON. It's a great data format. It's especially great when the data from it is being delivered to the front of your website via an API call, as that works nicely with any Javascript functionality that might be present there.

JSON also has some sick abs.

APIs are so hot. You know, Todd.

But as much as I hate to admit it, there’s a lot of good reasons to minimize your use of JavaScript in a website, saving it for when you need something interactive or animated… like some sort of clever map with pins.

Sorry, having a flashback there.

Frequently, you’ll want to be able to render your custom data from your custom property editor in an MVC view, using Razor. But manually converting a JSON object into Razor ranks right up there with kidney stones on experiences I’d like to repeat in my life.

Fortunately, we don’t need to pour gasoline over our computers and make a suspicious call to our insurance company. Because there is another way to get that data to your MVC view:

Custom property value converters

Despite the fact that I’m “the JavaScript guy™”, I do actually know a thing or two about C#. Which is what we can use to transform the JSON into a defined model that the Razor can use with ease.

Code Example Time!

Let us assume that we have a custom property editor that is responsible for keeping track of fruit and vegetables. With it, the client can, via the back-office, edit two lists, one of fruit and one of vegetables. For fruit, the user can enter its name, its color, and whether it has seeds or not. For vegetables, the user can enter its name, its color, and whether it needs to be peeled.

We’ll say we’ve assigned it to a property editor for this called “productListing”, and added a couple items each to both fruit and vegetables. This (admittedly rather strange) collection of data would look something like this when saved.

{
	fruit: [
		{
			name: "cherry",
			color: "red",
			hasInedibleSeeds: true
		},
		{
			name: "orange",
			color: "orange",
			hasInedibleSeeds: false
		}
	],
	vegetables: [
		{
			name: "cucumber",
			color: "green",
			shouldPeel: false
		},
		{
			name: "squash",
			color: "yellow",
			shouldPeel: true
		}
	]
}

We’ll want to have code that can take this JSON object and easily convert it to code we can use with Razor.

Step #1: Models

First we’ll want to define some models, which will be used by our property value converter.

We’ll define a fruit model (Fruit.cs):

{
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    public class Fruit
    {
        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("color")]
        public string Color { get; set; }
		
	[JSonProperty("hasInedibleSeeds")]
	public bool HasInedibleSeeds { get; set; }

        public static Fruit Deserialize(string json)         {             // Validate the JSON             if (json == null || !json.StartsWith("{") || !json.EndsWith("}"))             {                 return null;             }             // Deserialize the JSON             var jobj = (JProperty)JsonConvert.DeserializeObject(json);             return new Fruit()             {                 Name = (string)jobj.Value["name"],                 Color = (string)jobj.Value["color"], HasIndedibleSeed = (bool)jobj.Value["hasInedibleSeed"]             };         }     } }

A vegetable model (Vegetable.cs):

{
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    public class Vegetable
    {
        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("color")]         public string Color { get; set; } [JSonProperty("shouldPeel")] public bool ShouldPeel { get; set; }         public static Vegetable Deserialize(string json)         {             // Validate the JSON             if (json == null || !json.StartsWith("{") || !json.EndsWith("}"))             {                 return null;             }             // Deserialize the JSON             var jobj = (JProperty)JsonConvert.DeserializeObject(json);             return new Vegetable()             {                 Name = (string)jobj.Value["name"],                 Color = (string)jobj.Value["color"], ShouldPeel = (bool)jobj.Value["shouldPeel"]             };         }     } }

And lastly, a produceList model (ProduceList.cs):

{
    using System.Collections.Generic;
    using System.Linq;
    using System.Web.Mvc;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    public class ProduceList
    {

        [JsonProperty("fruit")]
        public IEnumerable Items { get; set; }

        [JsonProperty("vegetables")]
        public IEnumerable Categories { get; set; }

        public static ProduceList Deserialize(string json)
        {
	    // Validate the JSON
	    if (json == null || !json.StartsWith("{") || !json.EndsWith("}"))
	    {
		return null;
            }

            // Deserialize the JSON
            var jobj = (JObject)JsonConvert.DeserializeObject(json);
	    var fruit = jobj.GetValue("fruit").Cast().Select(j => new Fruit()
	    {
		Name = j.Value("name"),
		Color = MvcHtmlString.Create(j.Value("color")),
		HasInedibleSeeds = j.Value("hasInedibleSeeds")
	    });
	    var vegetables = jobj.GetValue("vegetables").Cast().Select(j => new Fruit()
	    {
		Name = j.Value("name"),
		Color = MvcHtmlString.Create(j.Value("color")),
		ShouldPeel = j.Value("shouldPeel")
	    });
            return new ProduceList()
	    {
		Fruit = fruit,
		Vegetables = vegetables
	    };
        }
    }
}

These are for the most part like normal C# models. But pay special attention to their Deserialize() functions, which define how the JSON model  being passed into them is converted into the C# variables using JsonConvert.DeserializeObject.

Step #2: Converter

Now that we have our models, we’ll need to build a converter. The models are doing most of the heavy lifting, so this is a comparatively simpler file (which we’ll say we named ProduceListingPropertyValueConveter.cs):

{
    using Umbraco.Core.Models.PublishedContent;
    using Umbraco.Core.PropertyEditors;

    public class ProductListingPropertyValueConverter : IPropertyValueConverter
    {
        public bool IsConverter(PublishedPropertyType propertyType)
        {
            return propertyType.PropertyEditorAlias == "Product.Listing";
        }

        public object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
        {
            return source;
        }

        public object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview)
        {
            return ProduceListing.Deserialize(source as string);
        }

        public object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview)
        {
            return null;
        }
    }
}

Most of the code here is basic boilerplate and can be repeated in any property value converter you make. Two points are worth paying attention to.

The first is that the IsConverter() function is responsible for determining what Umbraco property editor it converts the values for, based upon the property editor alias you assign to propertyType.PropertyEditorAlias. In this case, we have it as “Produce.Listing”, which we would have assigned the property editor to have in its package.manifest file (see the linked tutorial on property editors for more information).

The second is that the ConvertSourceToObject() function, which triggers the deserialization, is set to return ProduceListing.Deserialize(), which was defined in ProduceListing.cs. You could swap this (and the alias in IsConverter()) to other objects for another property editor.

Step #3: Using the converter in Razor

Now that we have the converter, we can make use of it in our MVC views. First we add our models to the top of the view:

@using Fruit
@using Vegetable
@using ProduceListing

Then we can include Razor that makes use of the models, which have the JSON data converted using Model.Content.GetPropertyValue:

@if (Model.Content.HasProperty("produceListing") && Model.Content.HasValue("produceListing"))
{
	<h1>Fruit</h1>
    <ul>
        @foreach (var fruit in Model.Content.GetPropertyValue<ProduceListing>("produceListing").Fruit)
        {
            <li>
                <h3>@fruit.Name</h3>
				<div>Color: @fruit.Color</div>
				@if (fruit.hasInedibleSeed == true) {
					<div>Make sure to remove the seed.</div>
				}
            </li>
        }
    </ul>
	<h2>Vegetables</h2>
	<ul>
        @foreach (var vegetable in Model.Content.GetPropertyValue<ProduceListing>("produceListing").Vegetables)
        {
            <li>
                <h3>@vegetable.Name</h3>
				<div>Color: @vegetable.Color</div>
				@if (vegetable.shouldPeel == true) {
					<div>Make sure to peel it before serving.</div>
				}
            </li>
        }		
	</ul>
}

As you can see, this is simple, easy to read and write Razor, with all the messy JSON banished from the view.

Wrap up

And that’s it! Load the view on the front, and you’ll see your strange list of fruit in vegetables in all its glory.

I’d like to give a hat tip to Anders Bjerner for his explanation of property converts in this Our Umbraco forum post. It proved to be very useful to me.

Now armed with this knowledge, you too can render out complex objects created by custom property editors without having to do some sort of complex voodoo. Unless you want. I mean, it’s your time. If you want invest it in setting up a complex and somewhat shady ritual involving a chicken, I’m not going to stop you.

But I would think twice, if I were you. He looks like a fighter.

Kyle Weems

Kyle is a co-founder and editor for Skrift, where he does a good deal of the editing and makes uninformed opinions about which shade of pink to use that the others wisely ignore. He write code as a Lead UI Engineer at Tonic, is the lead keytarist for the band Moosewine (who have no current plans to release any music), tinkers endlessly with machine generation, designs quirky indie tabletop RPGs, and is (technically, if just barely) an award-winning cartoonist and journalist.

He likes his IPAs likes he likes his trees, reminiscent of pine.

comments powered by Disqus