The Missing Controller
Heads Up!
This article is several years old now, and much has happened since then, so please keep that in mind while reading it.
These controllers will also get the required data that you want to display on your view. I was using Entity Framework for this, retrieving a bunch of data and creating a View Model for my view.
Something told me there should be a controller somewhere, that I could use controllers somehow, so I started chasing the Missing Controller.
But when I first jumped into Umbraco I was quite lost. You don’t have to create controllers so how are your views displayed? Displaying page content is fine but what if you want to send some custom data to the templates? If I don’t have controllers, how can I use my old friends the View Models?.
And because we don’t have controllers (this is not true, more about this to come) I was just throwing all possible logic into the templates as it seems to be the only place where we can put our code right?. Wrong.
Something told me there should be a controller somewhere, that I could use controllers somehow, so I started chasing the Missing Controller.
Our Case Scene
So, you finally understood how to create your document type, your template and a page with some content.
And your content tree looks like this:
The root of your Blog will display this:
The code for this template looks like:
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@using ContentModels = Umbraco.Web.PublishedContentModels;
@{
Layout = "Master.cshtml";
}
@Html.Partial("~/Views/Partials/SectionHeader.cshtml")
<section class="section">
<div class="container">
@Umbraco.Field("bodyText")
</div>
</section>
So you managed to display the intro for your blog but no articles are being displayed!. No articles, no blog.
There is only one place you can think of where to put your code and retrieve your articles: your view. And you do something like this:
@inherits Umbraco.Web.Mvc.UmbracoViewPage<BlogViewModel>
@using ContentModels = Umbraco.Web.PublishedContentModels;
@{
Layout = "Master.cshtml";
}
@Html.Partial("~/Views/Partials/SectionHeader.cshtml")
@{
var articles = Model.Content.Children;
}
<section class="section">
<div class="container">
@Umbraco.Field("bodyText")
<h2>Posts</h2>
<ul>
@foreach (var article in articles)
{
<li>
<p>
<strong>@article.Name</strong><br />
@(article.GetPropertyValue<string>("excerpt"))
</p>
</li>
}
</ul>
</div>
</section>
That doesn’t look too bad actually. Easy and simple. Job done.
This technique might be easy for simple tasks, everybody using Umbraco does it this way when the data to retrieve is simple, but it can lead you to the worst scenarios. When you have several sources and start applying complex logic to your data the thing gets messy.
The next example is from an actual site, I’ll paste here for the sake of demonstration of how your template should never look like:
Well, it turns out that there are several techniques in Umbraco to move your logic away from your views.
Yes, this works, but at the bottom of your heart you know this looks terrible. They are basically using the view as a controller to gather all the data and then a view model is created to be sent to a partial view.
When you work on a plain MVC project you know is good practice to separate your logic from your views, but you don’t know where The Missing Controllers are so you can do that.
Well, it turns out that there are several techniques in Umbraco to move your logic away from your views. Let’s start with the simpler one: Partial Actions.
Your first clue: The Partial Action
Have you heard about Surface Controllers? They will be one of your best friends in this apparent controller-less Umbraco world. They are easy to create, to use and you don’t have to worry about routing.
Preparing the scene
To make the case a bit more interesting, I have added a new News branch and we will use our Users list to display info about who wrote the posts:
And added some users:
So Posts, News and Users will have to live together in the same template and, as we saw, putting our logic in the view can make things confusing.
Moving the logic
One way of moving your logic off the templates is using a technique that involves Surface Controllers and Partial Actions. We will create our action methods to retrieve all the data and then return a partial view with it. We will also use a custom view model to merge posts and writers’ details and make things even easier.
Our Models
These will be the view models of our partial views.
public class PostViewModel
{
public string Title { get; set; }
public string Excerpt { get; set; }
public WriterViewModel Writer { get; set; }
}
public class WriterViewModel
{
public string Image { get; set; }
public string Name { get; set; }
}
Our Controller
public class AwesomeSiteController : SurfaceController
{
public PartialViewResult LatestPosts()
{
var posts = new List<PostViewModel>();
var blog = Umbraco.TypedContentAtRoot().First().Children.FirstOrDefault(x => x.DocumentTypeAlias == "blog");
if (blog != null)
{
var children = blog.Children;
foreach (var c in children)
{
posts.Add(new PostViewModel
{
Title = c.Name,
Excerpt = c.GetPropertyValue("excerpt").ToString(),
Writer = new WriterViewModel
{
Name = c.CreatorName,
Image = ApplicationContext.Services.UserService.GetUserById(c.CreatorId).Avatar
}
});
}
}
return PartialView(posts);
}
public PartialViewResult LatestNews()
{
IEnumerable<IPublishedContent> articles = Enumerable.Empty<IPublishedContent>();
var news = Umbraco.TypedContentAtRoot().First().Children.FirstOrDefault(x => x.DocumentTypeAlias == "news");
if (news != null)
{
articles = news.Children;
}
return PartialView(articles);
}
}
Our template
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@using ContentModels = Umbraco.Web.PublishedContentModels;
@{
Layout = "Master.cshtml";
}
@Html.Partial("~/Views/Partials/SectionHeader.cshtml")
<section class="section">
<div class="container">
@Umbraco.Field("bodyText")
<div class="row">
<div class="col-md-6">
@Html.Action("LatestPosts", "AwesomeSite")
</div>
<div class="col-md-6">
@Html.Action("LatestNews", "AwesomeSite")
</div>
</div>
</div>
</section>
Our Partial Views
LastestPost.cshtml
@inherits UmbracoViewPage<IEnumerable<PostViewModel>>
<h2>Latest Posts</h2>
@foreach (var post in Model)
{
<div class="post row">
<div class="col-md-2">
<figure>
<img class="avatar" src="/media/@post.Writer.Image?width=90&height=90&mode=crop">
<figcaption><i>@post.Writer.Name</i></figcaption>
</figure>
</div>
<div class="col-md-8">
<h4>@post.Title</h4>
<p>@post.Excerpt</p>
</div>
</div>
}
LatestNews.cstml
@inherits UmbracoViewPage<IEnumerable<IPublishedContent>>
<h2>Latest News</h2>
<ul>
@foreach (var article in Model)
{
<li>
<p>
<strong>@article.Name</strong><br />
</p>
</li>
}
</ul>
Analysing the details
The Latest Posts view
As you can see we have modified our Blog template, so we use:
@Html.Action("LatestPosts", "AwesomeSite")
This will call our Action method, we will query Umbraco to give us our Posts and we will create a custom view model to merge data from our posts and writers. Then we’ll pass our view model to the partial view:
return PartialView(posts);
On our partial view we are using the handy UmbracoViewPage class. This class will give us all the good Umbraco helpers and services and at the same time we can specify our custom view model as the model for the view so we get a strongly typed view.
In this case, the Model of the view will be a IEnumerable<PostViewModel>. Remember this is the type of model we are getting from our action method.
The Latest News view
The LatestNews view is slightly different but it uses the same concept. Our LatestsNews action method retrieves all the pages of type ‘news’ but in this case, we are not creating a custom view model, we are just creating a list with the IPublishedContent that Umbraco gives us by default.
Our result
Wrapping the Partial Action clue
As we saw, we could use partial views, controllers, view models the same way we would do in a vanilla MVC project! We just had to load all the goodness using the Action helper.
Your second clue: Hijacking routes
Previously we have been able to move our logic from the template to controllers and use view models in partial views. All this allows us to separate concerns in logic chunks instead putting all together in the same template. This will make the site much more maintainable and easier to extend and support.
Sometimes, though, this still feels like a hack. You must create a template just to host the rest of your partial views. To be fair you still haven’t seen the Missing Controller that loads the main template.
Hijacking routes
There is something called route hijacking. With this technique we can create our ‘first level’ controller that will load our main template. I’m not going to say what the docs explain well.
In our case we want to use route hijacking to have one controller and one view model, no partial views, no action helpers. It can’t be that difficult, right?!
Our view model
public class BlogViewModel : RenderModel
{
public BlogViewModel(IPublishedContent content) : base(content) { }
public IEnumerable<PostViewModel> LatestPosts { get; set; }
public IEnumerable<IPublishedContent> LatestNews { get; set; }
}
Our Controller
public class BlogController : Umbraco.Web.Mvc.RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
var viewModel = new BlogViewModel(model.Content);
viewModel.LatestPosts = GetLatestPosts();
viewModel.LatestNews = GetLatestNews();
return base.Index(viewModel);
}
private IEnumerable<PostViewModel> GetLatestPosts()
{
var posts = new List<PostViewModel>();
var blog = Umbraco.TypedContentAtRoot().First().Children.FirstOrDefault(x => x.DocumentTypeAlias == "blog");
if (blog != null)
{
var children = blog.Children;
foreach (var c in children)
{
posts.Add(new PostViewModel
{
Title = c.Name,
Excerpt = c.GetPropertyValue("excerpt").ToString(),
Writer = new WriterViewModel
{
Name = c.CreatorName,
Image = ApplicationContext.Services.UserService.GetUserById(c.CreatorId).Avatar
}
});
}
}
return posts;
}
public IEnumerable<IPublishedContent> GetLatestNews()
{
IEnumerable<IPublishedContent> articles = Enumerable.Empty<IPublishedContent>();
var news = Umbraco.TypedContentAtRoot().First().Children.FirstOrDefault(x => x.DocumentTypeAlias == "news");
if (news != null)
{
articles = news.Children;
}
return articles;
}
}
Our View
@inherits Umbraco.Web.Mvc.UmbracoViewPage<BlogViewModel>
@using ContentModels = Umbraco.Web.PublishedContentModels;
@{
Layout = "Master.cshtml";
}
@Html.Partial("~/Views/Partials/SectionHeader.cshtml")
<section class="section">
<div class="container">
@Umbraco.Field("bodyText")
<div class="row">
<div class="col-md-6">
<h2>Latest Posts</h2>
@foreach (var post in Model.LatestPosts)
{
<div class="post row">
<div class="col-md-2">
<figure>
<img class="avatar" src="/media/@post.Writer.Image?width=90&height=90&mode=crop">
<figcaption><i>@post.Writer.Name</i></figcaption>
</figure>
</div>
<div class="col-md-8">
<h4>@post.Title</h4>
<p>@post.Excerpt</p>
</div>
</div>
}
</div>
<div class="col-md-6">
<h2>Latest News</h2>
<ul>
@foreach (var article in Model.LatestNews)
{
<li>
<p>
<strong>@article.Name</strong><br />
</p>
</li>
}
</ul>
</div>
</div>
</div>
</section>
As you can see, we are just creating a controller that inherits from RenderMvcController. We are giving it the name BlogController so it’s used by the pages that implement the Blog DocType.
Because we are not using different action methods for different templates, we just override the Index method. In that method we create our view model, and we get our data using two private methods.
These methods are just our old partial Action methods but instead returning a Partial View they return the IEnumerables for our view model.
Once we have our view model hydrated we just pass it to our base controller, that will send it to the template: return base.Index(model);
TIP: If you check the code for RenderMvcController you will see that it’s just doing this: return CurrentTemplate(model);
On our view, we have replaced UmbracoTemplatePage by UmbracoViewPage<T> This change allows us, as we saw with the partial views before, to specify our model so we have a strongly typed view.
Now our Model will be BlogViewModel. Awesome! So we can access our view model properties to render our data as we please.
Note: I’m not using partial views here but it would be good if you separate each piece of your template into more task-specific chunks. You can of course use Html.RenderPartial for this.
Wrapping the Hijacking Routes clue
So, there you go! We managed to use a controller and view model to send our stuff to the template, very similar to our old MVC way of doing things.
We just had to create our controller inheriting from RenderMvcController and render our template using CurrentTemplate. This is a bit different than vanilla MVC, of course, instead returning a ViewResult, that CurrentTemplate method will know what template our page has been assigned from the Umbraco backoffice and will pass our model to it. Apart of that, everything else is pretty straight forward.
Closing the case
As we have seen on our investigations, we started with a nasty situation. The only place we could put our code on was the templates. This worked but eventually got messy when things got more complex and, because we love to do things right (we do, don’t we?, don’t we?), we wanted to separate our presentation layer from our logic layer.
We had a clue that threw some light. This was using Action helpers to call our Action methods from a custom controller that inherits from a Surface Controller. This allowed us to send our data to a partial view that is then rendered into our main template.
Our second clue was using Route Hijacking. This allowed us to ‘intercept’ Umbraco default behaviour so we could create a custom controller, a view model and pass it to the main template.
Case closed.
Hmmm, but you didn’t find the Missing Controller!
We did, didn’t you realise? Our missing controller is RenderMvcController!. That is the controller that Umbraco uses as default to render every single page on your site. The normal Umbraco routing process will create a RenderModel that is passed to the Index of RenderMvcController and this will send it to the CurrentTemplate method we saw before.
If you check the core code for CurrentTemplate you will see this:
protected ActionResult CurrentTemplate<T>(T model)
{
var template = ControllerContext.RouteData.Values["action"].ToString();
if (EnsurePhsyicalViewExists(template) == false)
throw new Exception("No physical template file was found for template " + template);
return View(template, model);
}
There you go! It’s returning a normal ActionResult using return View(template, model);. Exactly as you would do in a normal MVC project.
Case Closed. For Good.
Moving further
There are some more techniques that involve controllers and you can use for more complex scenarios:
- One is creating your own routes so you can use completely customize controllers. For instance, you can pass more properties to your controller from the Url instead just using the RenderModel that Umbraco send to the controllers by defaut.
- You can also modify your base controller so you don’t even need to hijack your routes.
Mario Lopez
Mario is on Twitter as @skartknet