Marrying Ditto with ModelsBuilder
I was happy to be allowed to speak at this years Umbraco UK Festival.
The topic was based on my previous post where I compare Ditto and ModelsBuilder.
While preparing for that talk, I couldn't help but notice that the tools and techniques
aren't mutually exclusive at all. On the contrary, they can compliment each other in a really nice way.
Slides and video from presentation linked further down.
I won't dive into too many details in this article, I recommend you read the previous article, and take a swim through the code at the github repository.
Cleaning up the processors
In the Dittoified TXT site Matt Brailsford made, we saw a bunch of processors querying the hierarchy. In my Modelsbuilderified version, we do nice and clean domain oriented queries.
Take for instance the top navigation on the site, where we look up all the visible children of the home page. The Ditto processor looks as such:
public class MainNavAttribute : DittoProcessorAttribute
{
public override object ProcessValue()
{
var content = Value as IPublishedContent;
if (content == null) return Enumerable.Empty<NavLink>();
var homePage = content.AncestorsOrSelf(1).First();
return new[] { homePage }.Union(homePage.Children.Where(x => x.IsVisible()));
}
}
With ModelsBuilder, we point to the homepage from the base document type, and implemented the navigation items query on the homepage:
public partial class UmbHomePage
{
IEnumerable<INavigationContent> INavigation.MenuItems
{
get
{
return new[] { this }
.Union(
Children
.OfType<INavigationContent>()
.Where(c => c.IsVisible)
);
}
}
}
What we keep forgetting though, is that the ModelsBuilder models are created before the content leaves the cache. If we install Ditto in the ModelsBuilderified version, or vice versa, we can actually use that MB query in the processor. Whether we'd like to keep the query and interface segregation on our ModelsBuilder classes, or we'd like to put most logic in the Ditto processors is still a matter of taste.
However, by just letting MB generate its models in the Dittoified project, not writing one single interface,
we can refactor the MainNavAttribute
as such:
public class MainNavAttribute : DittoProcessorAttribute
{
public override object ProcessValue()
{
var content = Value as IPublishedContent;
if (content == null) return Enumerable.Empty<NavLink>();
var homePage = content.AncestorOrSelf<UmbHomePage>();
return new[] { homePage }.Union(homePage.Children.Where(x => x.IsVisible()));
}
}
The same can be done to the BaseNewsProcessorAttribute
with even more "domain language":
public abstract class BaseNewsAttribute : DittoProcessorAttribute
{
protected IEnumerable<UmbNewsItem> GetNews()
{
var content = Value as UmbMaster;
if (content == null) return Enumerable.Empty<UmbNewsItem>();
var newsArchive = content.Home.FirstChild<UmbNewsOverview>();
if (newsArchive == null) return Enumerable.Empty<UmbNewsItem>();
return newsArchive.Children<UmbNewsItem>()
.OrderByDescending(x => x.DisplayDate);
}
}
You'll notice I've added the DisplayDate
implementation to UmbNewsItem
so we don't need to
think about the PublishDate
and CreateDate
properties every time we do ordering.
Where to start
I'd recommend that if you don't use either tool today, you should really just start using ModelsBuilder. It will improve your code immensly over using magic strings, level-based queries and all that comes with the basic IPublishedContent implementation. When you start to see that you want more separation of concerns and interfaces don't do that for you, look into adding Ditto on top.
Serialization
The main pain point of using ModelsBuilder today is that IPublishedContent
implementations
lend themselves badly to serialization. Serializing it without care will lead to cyclic references
and/or super big graphs of parents and children.
By mapping the content to POCOs with Ditto, you don't have to care about this.
Strike a balance
In my opinion, one can go way too far with the processors in Ditto. Separation of concerns is good, but not at the cost of having to wade through 10-20 classes for one coherent piece of functionality.
The same can be said about ModelsBuilder. Creating too many compositions, adding to many interfaces, creating too many extensions can be just as overwhelming.
It basically boils down to the good old YAGNI principle.
Further exploration
The examples in this article is avaiable in a branch on GitHub.
I also did a presentation comparing the two tools, and marrying them at last at this years Umbraco UK Festival.
The presentation can be seen on YouTube.
The slides from the presentation are available here.