This is the second post in a series about using Sitecore MVC out-of-the-box. Last weeks post explored how to put together a simple page. This week I’ll try and create a typical navigation control which will involve pulling out data from the hierarchy of child items. While I believe this would be possible using views alone, the code would quickly become very messy, so I’m going to start by looking at creating my own model.
I’m still working with the a view rendering. Personally I think we only need to drop to a controller rendering when we have some kind of interaction with the user. Paging through a list of articles for example. I’ll want to fill the Model field shown below with a class I write myself.
To get started I need to create a class with the IRenderingModel interface. Note I’m playing with formatting to aid readability in this blog format. Here I’m just outputting a title field when the view calls @Model.Title
using Sitecore.Mvc; using Sitecore.Mvc.Helpers; using Sitecore.Mvc.Presentation; using System.Web; namespace SunTzu.Models { public class SimpleModel : IRenderingModel { private SitecoreHelper helper; public HtmlString Title { get { return helper.Field("title"); } } public void Initialize(Rendering rendering) { helper = PageContext.Current.HtmlHelper.Sitecore(); } } }
I’m sure the decision to push the call to the helper function inside the model goes against the grain for many (please feel free to comment below). However my design goal is to keep the views simple and focused on final published page rendering. Ideally the views know nothing of Sitecore. They contain only domain presentation logic and layout. On top of this I believe it is the responsibility of the Render Field Processors to add Page Editor magic not the helper. Finally I haven’t violated the principle of having page mark up in the model code which John West warns about.
To register this class with Sitecore I need to create a Model item in Sitecore. At this time Rocks doesn’t provide a nice Add Model option, so either go to the content editor (which does) or use Add Item and select the template /sitecore/templates/System/Layout/Model. You’ll need to add a reference to your full class name and the assembly it is compiled in to the Model Type field. Assuming my dll above is called Suntzu.dll then I enter:
SunTzu.Models.SimpleModel,SunTzu
Finally I add the full path to this Model item into my View Rendering. The view itself is simple. As ever we get the full page editor experience, including MVT and personalisation logic.
@model SunTzu.Models.SimpleModel <h1>@Model.Title</h1>
Now if you like strongly typed views then we’re done. However the thought of having to write C# code every time I add a field to a template or to reference parent, sibling or child items, fills me with dread. If I’m going to be doing something which requires complex business or presentation logic but when all my views are doing is traversing the content database structure I’d prefer to write one model and be done. Life’s too short.
So time to go dynamic. Using the class above as a base I now inherit from the DynamicObject and override the TryGetMember method. I know have a single model I can use to simplify the format of my view files which only requires compiling once, after that I can add Templates, ViewRenderings and Razor files as I please. Giving me a very rapid development cycle. OK the downside is I lose Intellisense and I’ve will need to deal with field names with spaces in some how.
using Sitecore.Mvc; using Sitecore.Mvc.Helpers; using Sitecore.Mvc.Presentation; using System.Dynamic; namespace SunTzu.Models { public class FieldOnlyModel : DynamicObject, IRenderingModel { private SitecoreHelper helper; public override bool TryGetMember( GetMemberBinder binder, out object result) { result = helper.Field(binder.Name); return result == null ? false : true; } public void Initialize(Rendering rendering) { helper = PageContext.Current.HtmlHelper.Sitecore(); } } }
So far I’m really back to where I was last week. Accessing fields from within views. Next step is to move up and down the content tree. To do this I’m going to need a DynamicItem. I’m going to move my field rendering code into this new class and then get the model to inherit from this new type.
using Sitecore.Data.Items; using Sitecore.Mvc.Helpers; using System.Dynamic; namespace SunTzu.Models { public class DynamicItem : DynamicObject { protected SitecoreHelper helper; protected Item item; public DynamicItem() { } public DynamicItem( SitecoreHelper helper, Item item) { this.helper = helper; this.item = item; } public override bool TryGetMember( GetMemberBinder binder, out object result) { result = helper.Field(binder.Name, item); return result == null ? false : true; } } }
And the model now just hooks into the Sitecore Initialize call.
using Sitecore.Mvc; using Sitecore.Mvc.Presentation; namespace SunTzu.Models { public class DynamicModel : DynamicItem, IRenderingModel { public void Initialize(Rendering rendering) { this.item = rendering.Item; this.helper = PageContext.Current.HtmlHelper.Sitecore(); } } }
That’s the refactoring done. Now I can start adding to the DynamicItem. First the simple case of accessing the Parent.
public DynamicItem Parent { get { return new DynamicItem( this.helper, this.item.Parent); } }
The view can now access all fields on the parent item.
@model dynamic <h1>@Model.Parent.Title</h1> <h2>@Model.Title</h2>
So we’re getting there. Back at the beginning I promised the code for a navigation control. To achieve this I need the access to child items.
public List<DynamicItem> Children { get { List<Item> children = new List<Item> (this.item.Children); return children.ConvertAll<DynamicItem> (child => new DynamicItem(this.helper, child)); } }
So here is the final view. I can see already extensions are needed to check for empty fields and the template of the content items I’m iterating through. I’ll leave that out as implementation detail for now.
@model dynamic @foreach (var headerSection in @Model.Children) { <ul class="sections"> <li class="section"> @headerSection.Link <ul class="columns"> @foreach (var column in headerSection.Children) { <li class="column"> <ul class="links"> <li class="title">@column.Title</li> <li class="gap"></li> @foreach (var item in column.Children) { <li class="link">@item.Link</li> } </ul> </li> } </ul> </li> </ul> }
Reference