Umbraco 8.1.4 Breaking & Models Builder

Posted on September 4, 2019 in umbraco and edited on September 5, 2019

UPDATED: the proposed workaround has been simplified, see at the bottom of this post.

The Umbraco 8.1.4 release fixes issue #6033 but introduces a breaking change.

Up until this release, when a media picker was configured to pick only images, the corresponding property value converter would report the property type as Image or IEnumerable<Image> and Models Builder would generate the property as (in MyModel.generated.cs):

public partial class MyModel
{
  // ...

  [ImplementPropertyType("myImage")]
  public Image MyImage
    => this.Value<Image>("myImage");

  [ImplementPropertyType("myImages")]
  public IEnumerable<Image> MyImages
    => this.Value<IEnumerable<Image>>("myImages");
}

Thus allowing you to write:

<img src="@Model.MyImage.GetCropUrl("crop")"
     alt="@Model.MyImage.AltDescription" />

Unfortunately, that was based on the assumption that, with this configuration, the media picker would only allow media of type "image" (or anything inheriting "image") to be picked. In reality, it seems that the media picker has different rules and allows various types of media to be picked.

As a consequence, users would see nasty YSODs when picking anything that would not inherit from "image", as it would of course not fit within an IEnumerable<Image>.

The fix that was introduced forces the property value converter to report the property type as IPublishedContent or IEnumerable<IPublishedContent> no matter what can be picked. Alas, that breaks the existing Models Builder properties. And, if you re-generate models, what was Image becomes IPublishedContent and you lose the direct access to properties such as width or height.

Workaround

However, assuming that you know that you are only going to pick images, you do not want to clutter your views with casts or non-strongly-typed property accesses.

The workaround to keep all your views unchanged and clean, consists in declaring your own implementations of these properties, in a partial class, alongside the existing models. Models Builder will detect them, and not generate anything. You want (in MyModel.cs):

public partial class MyModel
{
  [ImplementPropertyType("myImage")]
  public Image MyImage
    => this.Value<IPublishedContent>("myImage") as Image;

  [ImplementPropertyType("myImages")]
  public IEnumerable<Image> MyImages
    => this.Value<IEnumerable<IPublishedContent>>("myImages")
           .OfType<Image>();
}

This can be tedious, though, if you have a lot of models.

Workaround (2)

UPDATE: This workaround works but is too complex, for no reason. Read it, then see the next workaround for the simple solution.

Alternatively, there is a more radical approach, if you are brave enough. Get the code for MediaPickerValueConverter from 8.1.3, drop it into your own solution, and use a composer to use it in place of the one that ships with 8.1.4:

using UmbracoConverters = Umbraco.Web.PropertyEditors.ValueConverters;

public void Compose(Composition composition)
{
  composition.PropertyValueConverters()
    .Remove<UmbracoConverters.MediaPickerValueConverter>()
    .Append<MyConverters.MediaPickerConverter>()
}

Everything will be back to normal, and that is probably what I am going to do on this site. But then of course, you end up running with a non-standard converter.

Workaround (3)

Same as above, get the code for MediaPickerValueConverter from 8.1.3, drop it into your own solution, and make sure you remove its [DefaultPropertyValueConverter]. That's it. No need for a composer. Since your converter is not "default", it will override the default one.

And, even better, you can use the converter posted by Ronald in this comment on the original GitHub issue. It tries to mitigate the issue in a nice way.

I am afraid there is no ideal, immediate solution. In a perfect world... the media picker should be fixed, so one can indicate a (base) type to be picked, and so that only media of that type can be picked...

There used to be Disqus-powered comments here. They got very little engagement, and I am not a big fan of Disqus. So, comments are gone. If you want to discuss this article, your best bet is to ping me on Mastodon.