Constellation.Foundation.ModelMapping has been updated to support easier mapping of complex Sitecore XML fields. Both Image and General Link field types now have their own FieldModel classes in ModelMapping that allow you to access the attributes of Sitecore’s native ImageField and LinkField classes from a single ViewModel property.
The primary driver for this change was not a simplification of ViewModels. As Mobile internet usage continues to eclipse desktop broadband users, both page rank and perceived page speed are driving the use of more advanced use of images on page – Adaptive strategies like the Picture tag are becoming far more prevalent.
Using these tags obligates the Sitecore developer to produce multiple valid Media URLs from a single Image field, and here lies the problem.
Producing a MediaItem URL requires the actual MediaItem, along with references to MediaManager and MediaUrlBuilderOptions. Access to these objects is fundamentally against the tenets of my Sitecore programming style, which dictates that Views do not reference the Sitecore API directly.
Helix design patterns also complicate access to MediaItems. What if you have a Helix Feature that is consumed by more than one Project website, where the HTML is fundamentally different? You’ll need multiple Image URLs, one for each Project that implements the Feature – The choices are ugly: Branching code in your Feature that is Project aware, or two virtually identical Features, one for each website. Or you could generate the links in the View, which may be the lesser evil, but it’s a code smell.
Rant
Helix explicitly makes no accomodation for Multi-site, but the idea that all Projects in a Sitecore install would use exactly the same HTML is an academic exercise. It does not accomodate reality where customers change Sitecore implementation partners constantly; sometimes for each site in their installation! Creating a Feature that is HTML agnostic became an invaluable goal. It lead to the creation of the ImageModel class: It puts the responsibility of reading the ImageField into the ViewModel on the Feature, but allows the View to live in Project-land.
ImageModel is a sub-ViewModel that you assign to a ModelMapped property that represents an Item’s ImageField but also provides a few methods that completely encapsulate the Media URL generation process. Here’s an example of what that looks like in a View:
<picture>
<source media="(min-width: 576px)" srcset="@Model.FullSizeImage.GetCustomHeightImageSrc(285)" />
<source media="(min-width: 676px)" srcset="@Model.FullSizeImage.GetCustomHeightImageSrc(387)" />
<source media="(min-width: 992px)" srcset="@Model.FullSizeImage.GetCustomHeightImageSrc(161)" />
<source media="(min-width: 1200px)" srcset="@Model.FullSizeImage.GetCustomHeightImageSrc(195)" />
<img class="card-img-top" src="@Model.FullSizeImage.GetCustomHeightImageSrc(195)" alt="@Model.FullSizeImage.Alt" />
</picture>
I have been very reluctant to add such complex objects to ModelMapper. It starts to resemble more robust Sitecore ORM systems, which I wholeheartedly warn against implementing. I feel that ImageModel is a reasonable balance, and it’s based on Media URL workarounds I’ve seen in the wild:
- “Extension” methods on specific ViewModels that encaspulate the calls to MediaManager
- Methods on ViewModels themselves
- “Utility” classes (yuck) composed solely of a static method to handle the transformation.
Because ModelMapper de-couples Sitecore Items from Views, the above hacks tend to force ViewModels to have unnecessary information attached (like Item IDs and FullPaths). Very ugly, and now completely unnecessary.
Having URL generation hang off of the ViewModel for a particular field just made sense. It’s just a little more work than the “*Url” property on GeneralLink mapping. It keeps the utility close to the thing that needs the functionality, and the “how” is both 100% pure Sitecore while also being completely obfuscated from the developer. Intern Proof™.
Solves a problem I’m thinking about all the time. Good stuff.