Getting started with MVC in Umbraco 4.10
Prepping the Umbraco back office for MVC.
By default Umbraco 4.10 only knows about WebForms. On the surface its not too obvious how to put the Umbraco back office into MVC mode.
To switch on MVC features we need to modify the ~/config/umbracoSettings.config file. Scroll down the file to about half way and you will come across a key called templates with a child key called defaultRenderingEngine. You will need to change this keys value from WebForms to Mvc.
<defaultRenderingEngine>Mvc</defaultRenderingEngine>
This will put your Umbraco 4.10 install into MVC mode, and make the back office aware that you are working in MVC.
Creating Your first View
Now that you have Umbraco configured to use MVC we want to get started building a website. Nothing is different from a standard Umbraco website, where you begin with your document types, but now, rather than the system creating MasterPages in the templates area, it is creating MVC Razor views. By default, Views created by Umbraco look like this.
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
Layout = null;
}
The first line basically wires up your view to Umbraco and gives you access to Umbraco features such as the Umbraco Helper (more on this later). The Layout = null; line is just like MasterPage inheritance in WebForms that allows you to set what the parent View is. Template inheritance is as easy as it was in WebForms and we will explain how to do this later.
Your HTML markup goes in below the closing }.
You may have noticed that there are very few options on the template editor toolbar. This is because MVC features are a work in progress across a number of versions, and as features become available, this toolbar will become more populated with options such as insert Macro, insert Partial View.
What is the Umbraco Helper?
The Umbraco Helper is just that, a helper that you can call on to retrieve things from Umbraco, to perform tasks like Searching the content index, rendering Macros, stripping HTML from text, finding out if a content item is protected etc. Generally useful and commonly used tasks that would otherwise take you a long time to build yourself.
To access the helper in your Views is easy. Just use @Umbraco. If you are working in Visual Studio and you you will get Intellisense and it will allow you explore all of its options.
Here is a quick example of how you would use the @Umbraco helper to perform a search on the Umbraco content index.
<ul class="search-results">
@foreach(var page in Umbraco.TypedSearch("mvc")){
<li><a href="@page.NiceUrl">@page.Name</a></li>
}
</ul>
Or here is an example of how to use the helper to truncate (shorten) some text.
@Umbraco.Truncate(Model.Content.GetPropertyValue("bodyText"),200)
So how do I add page properties?
There are a number of ways to put your content properties on the page, through the Umbraco helper, through the strongly typed Model.Content, or via the Dynamic CurrentPage. There are advantages to each.
@Umbraco.Field("alias")
This is the way that the insert Umbraco Page Property button will put your properties on the page. The helper has all the options that you are used to with the WebForms <umbraco:item> tag such as fallback fields, insert before, insert after etc.
@Model.Content.GetPropertyValue("alias")
@Model.Content.GetPropertyValue<Type>("alias")
@Model.Content.GetProperty("alias").Value
This is how we access our properties through the strongly typed IPublishedContent (@Model.Content) representation of our Umbraco content. The main advantage is that because its strongly typed, you get intellisense in VS, and you also can write linq against it. This is probably my preferred way to work with Umbraco content when developing views in VS.
@CurrentPage.Alias
If you have been using Razor via dynamicNode you would probably have used the similar dynamicNode derivative. @CurrentPage is basically the same. It is a dynamic representation of your Umbraco content (DynamicPublishedContent or the Dynamic version of IPublishedContent).
What about template inheritance?
Create a new template and then set the Layout value to the filename of the parent template. For example to set a View to have the parent View of _Layout.cshtml we would modify it to be as follows:
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
Layout = "_Layout.cshtml";
}
Lets take a look at what your _Layout.cshtml template might look like as we need a couple tags to tell our child templates where to put their content.
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
<!DOCTYPE html>
<html>
<head>
<title>@Model.Content.Name</title>
@RenderSection("head",false)
</head>
<body>
@RenderBody()
</body>
</html>
There are two very important tags on this template which are standard Razor View tags. They are @RenderSection("head",false) and @RenderBody()
@RenderSection("head",false) is the equivilant of the <asp:contentPlaceHolder runat="server" ID="head"> that you would use in WebForms MasterPages. It specifies a named section where a child template can place content. The boolean value specifies if it is required for a child template implement this section.
@RenderBody() is a catch all type tag. Basically it will render any content from a child template that has not been placed inside a named section.
Lets look at how a child template might be formatted to use this parent View.
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
Layout = "_Layout.cshtml";
}
@*this content will be rendered in the named section head*@
@section head{
<script type="text/javascript">
alert("Iz in yo head");
</script>
}
@*The following content will be caught by the RenderBody() tag in the parent template*@
<h1>@Model.Content.Name</h1>
@Html.Raw(Model.Content.GetPropertyValue("bodyText"))
What about Macros?
Working with MVC is a little different than working with Umbraco in WebForms mode. You will find that most of the time you will get away with not having to create Macros at all as you can either write the Razor directly in the template or include a Razor Partial View. Macros with MVC will most likely only be needed when you want to allow editors to insert functional blocks into the rich text editor or when you need to include a form (Child Action Macro) in the page.
Tooling for inserting Child Action Macros and Partial View Macros will be coming in v4.11.
With all that to say if you have an existing Razor or XSLT or UserControl macro that does not require any postback (ie no forms) there is an Umbraco helper method to include your macro.
@Umbraco.RenderMacro("myMacroAlias", new { name = "Ned", age = 28 })
The anonymous object (new{}) passed in are your macro parameters.
But you're using MVC so lets look at Partial Views.
The power of the Partial View
We are building our site using MVC so with MVC we also get access to Partial Views. So for instance we could build our site navigation as a Partial View. To build a partial view they live in the standard MVC Partials folder ~/Views/Partials folder. At the moment there is no tooling for creating a Partial view in the Partials folder via the back office. If you must create it via the back office you can just create them in the template section which means they are just in the ~/Views folder which is ok.
Lets look at a typical navigation Partial View and then dissect it.
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
<ul>
@foreach(var page in Model.Content.AncestorOrSelf(1).Children.Where(x => x.IsVisible()))
{
<li><a href="@page.NiceUrl()">@page.Name</a></li>
}
</ul>
Notice that it inherits the class Umbraco.Web.Mvc.UmbracoTemplatePage. We do this is so we can get access to the Umbraco helper and the the Current Page and other Umbraco specific features.
To include this Partial on our page we use the following syntax.
@Html.Partial("Navigation")
This tells MVC to render a partial called navigation.cshtml passing it the Model to use. In our case it is the IPublishedContent representation of the current page.
If you want to render a Partial View with a custom Model we can use the following inheritance and still have access to the Umbraco helper.
@inherits Umbraco.Web.Mvc.UmbracoViewPage<YourCustomModel>
To insert this partial we would do the following
@Html.Partial("Navigation", YourCustomModel)
Sometimes you will want to pass parameters to your Partial View. This is done through the ViewData dictionary. ViewData is an object that allows us to store and retrieve values for the current request.
So lets say we wanted to pass a value to a partial we would do it like this.
@Html.Partial("YourPartialName", new ViewDataDictionary{{ "age",33},{"name","peter"}})
And then in your Partial you can retrieve the values using:
ViewData["age"]
Go to it!
That should be enough to get you going with MVC under v4.10 BETA. This was only a simple primer and doesn't go into querying or how to build forms etc but we will cover this in future posts and there is already a lot of documentation written for the new 4.10 features over at GitHub that can fill in some gaps. Also you might want to check out this blog post by Shannon on his personal blog that gives a little more information on some of the other more technical aspects of MVC features of 4.10.
Please also be aware that currently the MVC features are a work in progress and there is still a lot of tooling to be built to support it in the back office that will appear in future versions.
This is a BETA, we need your help!
4.10 is a massive step forward for Umbraco and besides the new MVC features there are loads of bug fixes and small tweaks that need testing. Unit tests only go so far and will not always catch everything. We are also "dog fooding" this release on a few internal projects but we might not catch everything either. The more real world developers we have helping to identify issues the better the final release will be. So this is a call to anyone who has some spare time or is considering starting a build on the BETA please provide your feedback and report issues at issues.umbraco.org and help make this one of the best releases yet!
UPDATE 5/11/2012: an issue has been identified in the RC that causes a YSOD when you try to pass an IPublishedContent obejct to a partial (not menitoned in this blog post but could frustrate some developers trying it out).
For instance if you were iterating over some child pages and calling a partial for each child item.
@foreach(var page in Model.Content.Children){
@Html.Partial("somePartial",page)
}
And then your partial inherits like this
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
it would throw a YSOD saying:
The model item passed into the dictionary is of type 'Umbraco.Web.Models.XmlPublishedContent', but this dictionary requires a model item of type 'Umbraco.Web.Models.RenderModel'.
This is due to the Partial expecting a RenderModel. The good news is that this has now been fixed! and will work as expected in the release.