Creating reusable code in MVC apps
Heads Up!
This article is several years old now, and much has happened since then, so please keep that in mind while reading it.
As developers, I'm sure that we have all been through projects where the requirements changed in some way or another during the development iteration(s). More often than not, these requirements range from changing a CSS class to changing the way the menu structure is ordered which should be easy enough to handle. However, when the project manager calls you to let you know that the entire datacontext of a specific section needs to be changed in the final hour, things can/will eventually start to heat up and get messy.
The purpose of this post is to consider how we can set up our Umbraco solutions to prepare ourselves for these kinds of changes through the process by creating reusable code and make it as easy as possible to change certain parts of a website.
The basics
In the following examples, I'm using the freshly released Umbraco 7 with a very basic structure to simulate what could be a (part of a) large site. The site structure consists of a homepage node, textpages and a news folder with newsitems:
Let's dig right into the code for this incredible complicated website. The _Layout.cshtml file (the Master) contains all the general code which should be displayed at all times, i.e. the main navigation, logos, footer etc.:
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
<!DOCTYPE html>
<html lang="da">
<head>
<!-- Markup omitted -->
</head>
<body>
<!-- Markup omitted -->
<ul class="nav navbar-nav">
@foreach (var menuItem in CurrentPage.AncestorOrSelf(1).Children.Where("Visible"))
{
<li>
<a href="@menuItem.Url">@menuItem.Name</a>
</li>
}
</ul>
@RenderBody()
<!-- Markup omitted -->
</body>
</html>
And the actual frontpage will have a header, a bodytext and the latest news:
@using Umbraco.Web
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
<div class="container">
<div class="row">
<div class="col-lg-12">
<h1>@Model.Content.GetPropertyValue("header")</h1>
@Model.Content.GetPropertyValue("bodyText")
</div>
</div>
<div class="row">
<div class="col-lg-12">
<!-- Show latest news -->
<h3>Latest news</h3>
<ul class="list-group">
@{
var newsFolder = Model.Content.AncestorOrSelf(1).Children.FirstOrDefault(x => x.DocumentTypeAlias.Equals("News"));
if (newsFolder != null)
{
foreach (var newsItem in newsFolder.Children.OrderByDescending(x => x.GetPropertyValue<DateTime>("date")).Take(3))
{
<li class="list-group-item">
<h4>@newsItem.GetPropertyValue("header")</h4>
<span>@newsItem.GetPropertyValue("date")</span>
<p>@Umbraco.Truncate(Html.Raw(newsItem.GetPropertyValue("bodyText")), 250, true)</p>
<a href="@newsItem.Url">Read more »</a>
</li>
}
}
}
</ul>
</div>
</div>
</div>
While the above code works just fine, it's not pretty since the view shouldn't know anything about where the data comes from, it should only be responsible for transforming the data. As far as this goes, we've, essentially, only made use of the View in MVC (Model-View-Controller) even though the view does inherit from the UmbracoTemplatePage model object.
Setting up strongly typed views and controllers
What I've found most flexible in the long run, is to make an almost direct mapping of Document types to model classes and map data to these for the UI to transform. Let's consider the following structure in Visual Studio:
Here, I have added two new projects: a model layer and the controllers project. Notice the naming: the model may not be directly related to our Umbraco website while the Controllers is. As a given project grows in size, more models could be added for any other (web)projects aswell.
Also notice that I have a MasterController and a Master model (Credit goes to Nick Frederiksen). The Master model contains all data, that should be used across the site such as SEO related stuff, navigation, footer information etc. This enables us to let any other model inherit from the Master and then display the general data:
namespace clientname.Model
{
public class Master
{
public string Header { get; set; }
public HtmlString BodyText { get; set; }
public string MetaTitle { get; set; }
public string MetaDescription { get; set; }
public string MetaKeywords { get; set; }
public IEnumerable<MenuItem> MainNavigation { get; set; }
}
}
(For this example I've just included the header and bodytext, these should be moved to their respective classes for convenience)
The MasterController allows us to map the data from Umbraco to our Master model and then return it to the view (notice: we're serving the view with data, the view doesn't ask for it) Like so:
namespace clientname.Umbraco.Controllers
{
public class MasterController : RenderMvcController
{
protected ViewResult View(Master model)
{
return View(null, model);
}
protected ViewResult View(string view, Master model)
{
// Map fields from Umbraco
model.BodyText = CurrentPage.HasProperty("bodyText")
? new HtmlString(CurrentPage.GetPropertyValue<string>("bodyText"))
: new HtmlString(string.Empty);
model.Header = CurrentPage.HasProperty("header") ? CurrentPage.GetPropertyValue<string>("header") : CurrentPage.Name;
model.MetaKeywords = CurrentPage.GetPropertyValue<string>("metaKeywords");
model.MetaDescription = CurrentPage.GetPropertyValue<string>("metaDescription");
model.MetaTitle = CurrentPage.GetPropertyValue<string>("metaTitle");
// Build main navigation
model.MainNavigation = (from node in CurrentPage.AncestorOrSelf(1).Children.Where("Visible")
let isActive = node.Id == CurrentPage.Id
select new MenuItem
{
IsActive = isActive,
Name = node.Name,
Url = node.Url
}).ToList();
return base.View(model);
}
}
}
To make use of the MasterController, we can simply make our other controllers inherit from it rather than inheriting from the RenderMvcController:
namespace clientname.Umbraco.Controllers
{
public class FrontpageController : MasterController
{
public ActionResult Frontpage()
{
var model = new Frontpage();
var latestNews = new List<NewsItem>();
foreach (var newsNode in uQuery.GetNodesByType("NewsItem").Take(3))
{
latestNews.Add(new NewsItem()
{
Header = !string.IsNullOrEmpty(newsNode.GetProperty<string>("header"))
? newsNode.GetProperty<string>("header")
: newsNode.Name,
BodyText = new HtmlString(newsNode.GetProperty<string>("bodyText")),
Author = newsNode.GetProperty<string>("author"),
Date = newsNode.GetProperty<DateTime>("date"),
Url = newsNode.Url,
TeaserText = library.TruncateString(library.StripHtml(newsNode.GetProperty<string>("bodyText")), 250, " ...")
});
}
model.LatestNews = latestNews;
return View(model);
}
}
}
Now, let's switch to our view again and refactor some code. Instead of inheriting from the UmbracoTemplatePage, we can now make our views strongly typed. Also, to separate reusable code, we can make partial views for i.e. our main navigation and our latest news section:
_Layout.cshtml
@using System.Web.Mvc.Html
@model clientname.Model.Master
<!DOCTYPE html>
<html lang="da">
<head>
<!-- Markup omitted -->
</head>
<body>
<header class="navbar navbar-inverse bs-docs-nav" role="banner">
<div class="container">
<div class="navbar-header">
<a href="../" class="navbar-brand">Client name</a>
</div>
@Html.Partial("Partials/_MainNavigation", Model.MainNavigation)
</div>
</header>
<!-- Markup omitted -->
</body>
</html>
_MainNavigation.cshtml
@model IEnumerable<clientname.Model.MenuItem>
@{
if (Model.Any())
{
<nav class="collapse navbar-collapse bs-navbar-collapse" role="navigation">
<ul class="nav navbar-nav">
@foreach (var menuItem in Model)
{
<li><a href="@menuItem.Url">@menuItem.Name</a></li>
}
</ul>
</nav>
}
}
Frontpage.cshtml
@inherits Umbraco.Web.Mvc.UmbracoViewPage<clientname.Model.Frontpage>
@using System.Web.Mvc.Html
<div class="container">
<div class="row">
<div class="col-lg-12">
<h1>@Model.Header</h1>
@Model.BodyText
</div>
</div>
@Html.Partial("Partials/_LatestNews", Model.LatestNews)
</div>
_LatestNews.cshtml
@model IEnumerable<clientname.Model.NewsItem>
@{
if(Model.Any())
{
<div class="row">
<div class="col-lg-12">
<!-- Show latest news -->
<h3>Latest news</h3>
<ul class="list-group">
@foreach (var newsItem in Model)
{
<li class="list-group-item">
<h4>@newsItem.Header</h4>
<span>@newsItem.Author</span>
<p>@newsItem.TeaserText</p>
<a href="@newsItem.Url">Read more »</a>
</li>
}
</ul>
</div>
</div>
}
}
The repository
So, what we've been doing up until now, is refactoring code to make it more reusable and thus separating our concerns of the implementation of the views.
Let's, for a moment, pretend that our client wants to display the latest news on regular textpages just like they are on the frontpage. This should be simple enough, since we can just copy our logic from the FrontpageController to our TextpageController. However, by doing this, we're duplicating code.
What we can do to decouple the code in our controller(s) is to make another layer to our solution: the repository.
The repository should serve as our central data access layer (some would argue that you should have both a repository and a data access layer, though) so basically every single query to get data from somewhere should be placed here. In the following examples I'm using concrete repository classes. However, if your project demands standard CRUD operations like Save(), Delete(), Update() etc., you could go for a generic repository.
As you can tell from my figure above, I've added a folder called Umbraco. Classes in this folder is responsible for mapping data from Umbraco to our models to serve to the controllers. Let's have a look at our latest news example:
namespace clientname.Repository.Umbraco
{
public class NewsRepository
{
public IEnumerable<NewsItem> GetAllNewsItems()
{
return uQuery.GetNodesByType("NewsItem").Select(MapNewsItem);
}
public IEnumerable<NewsItem> GetLatestNews(int count)
{
return uQuery.GetNodesByType("NewsItem").Take(count).Select(MapNewsItem);
}
public NewsItem GetNewsItem(int id)
{
var newsNode = uQuery.GetNode(id);
return MapNewsItem(newsNode);
}
public NewsItem MapNewsItem(Node newsNode)
{
return new NewsItem
{
Header = !string.IsNullOrEmpty(newsNode.GetProperty<string>("header"))
? newsNode.GetProperty<string>("header")
: newsNode.Name,
BodyText = new HtmlString(newsNode.GetProperty<string>("bodyText")),
Author = newsNode.GetProperty<string>("author"),
Date = newsNode.GetProperty<DateTime>("date"),
Url = newsNode.Url
};
}
}
}
This leaves us with a much cleaner controller for the frontpage:
namespace clientname.Umbraco.Controllers
{
public class FrontpageController : MasterController
{
public ActionResult Frontpage()
{
var newsRepository = new NewsRepository();
var model = FrontpageRepository.GetFrontpage(CurrentPage);
model.LatestNews = newsRepository.GetLatestNews(4);
return View(model);
}
}
}
The FrontpageRepository.GetFrontpage(IPublishedContent content) does the exact same as the NewsRepository: takes the current IPublishedContent and maps it to our model to serve to the view.
So, with the repository in place, we're able to add in more datasources as we wish (or as your boss wishes, rather) I.e., if the news should come from a MSSQL database, we could add a PetaPoco folder to our repository and simply change the repository in our controllers to use that one instead:
Taking things a bit further with dependency injection
To make our code even more reusable, we can tighten things up a bit using dependency injection (DI) for our controllers. For this, I'm using Autofac.
Basically, we don't want to new up a NewsRepository each and every time it should be used in our controllers. Instead, we would like for our controllers to be "dumb" and not care about the implementation. Let's have a look at the new Visual Studio structure:
Here, I have added a project for the interfaces and one for handling Umbraco events. The INewsRepository interface defines methods for getting news items from a given datasource:
namespace clientname.Interfaces
{
public interface INewsRepository
{
IEnumerable<NewsItem> GetAllNewsItems();
IEnumerable<NewsItem> GetLatestNews(int count);
NewsItem GetNewsItem(int id);
}
}
The dark magic for registering dependencies happens in the ApplicationEvents class:
namespace clientname.Umbraco.Events
{
public class ApplicationEvents : IApplicationEventHandler
{
public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
}
public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof (clientname.Umbraco.Controllers.FrontpageController).Assembly);
builder.RegisterType<Repository.Umbraco.NewsRepository>().As<INewsRepository>();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { }
}
}
In the ApplicationStarted event, we register the controllers (here I'm just registering the FrontpageController) and then tell the DI container that whenever there's a constructor asking for the INewsRepository, we'd like to pass the Repository.Umbraco.NewsRepository concrete object.
Now, let's go back to our FrontpageController and refactor some code:
namespace clientname.Umbraco.Controllers
{
public class FrontpageController : MasterController
{
private readonly INewsRepository _newsRepository;
public FrontpageController(INewsRepository newsRepository)
{
_newsRepository = newsRepository;
}
public ActionResult Frontpage()
{
var model = FrontpageRepository.GetFrontpage();
model.LatestNews = _newsRepository.GetLatestNews(4);
return View(model);
}
}
}
With the DI container wired up, we can now add a constructor for our FrontpageController to take an INewsRepository interface and store that in a global variable. In the Frontpage method, we then make use of that interface to get the latest news items.
So basically, we now have both a view and a controller, that doesn't know anything of the actual implementation. Also, if the datasource should be changed from using the Umbraco news items to i.e. MSSQL, we can just swap it in the following line:
builder.RegisterType<Repository.PetaPoco.NewsRepository>().As<INewsRepository>(); and let the PetaPoco.NewsRepository implement the INewsRepository.
Afterword
I can almost hear you ask the following question: "but isn't this overengineering the project?" The answer is easy: Yes! It's absolutely overengineering if the website is for your uncles carpenter company which needs 4 pages and a contact form.
The above example is really smallscale and it should be used to taste and to match the ambitions of a project. If you're developing a medium-to-large website with integrations to external systems/services, it could be useful in the long term to separate code like this.
Also, I'm personally not a software architect of any kind (and I'm not striving to be), so I'm definitely listening to any feedback and would invite you all to an open discussion about this approach and structure in general.
Happy christmas and happy coding! :-)
Bo Mortensen
Bo is on Twitter as @bo_mortensen