Fat Razor in MVC

Let me first define the terms that I will be referring to:

Presentation Code: Is the minimal code in a view that is necessary to display visual elements, but does not take any business decision. This is a fictional simple example:

// Business Logic Code. This code understands how the business works
// (don't do this)
if (Model.IsMonthly && !Model.IsPaid && Model.Payment >= 12 && ...)
{
  <p>Some warning message</p>
}

// Presentation Code. This code is only concerned with whether to
// show or hide a UI element.
if (Model.ShouldWarn)
{
  <p>Some warning message</p>
}

Fat-Razor: I’ve coined this term to define ASP.NET Razor views with a lot of Razor/C# code that is not presentation code. This is about the Razor/C# code specifically and not about HTML/CSS/JS.

Model: This is an overloaded term. However, in this context, it is an object of data representation such as a record in the database.

Viewmodel: They are data transfer objects that are meant to be view-specific and to carry exactly what the view needs, no less and no more. Generally, viewmodels are non-reusable and each vm is meant to to be tightly coupled to one view, however, there are exceptions depending on what you are doing.

To give you a flavour of a model and a viewmodel:

// An example of a model
public class Person
{
    public Guid PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public short YearOfBirth { get; set; }
    public DateTime LastAuth { get; set; }
    // This might represent a one-to-many db relationship
    public Order[] Orders { get; set; }
}

// An example of a viewmodel
public class ProfileViewModel
{
    // Doesn't necessarily need an id

    // Combined first and last name
    public string Name { get; set; }
    // Calculated from YearOfBirth
    public short Age { get; set; }
    // E.g. "One day ago" and constructed from LastAuth
    public string LastSeen { get; set; }
    // Calculated by projections on Orders
    public short NumberOfAllOrders { get; set; }
    public short NumberOfUndeliveredOrders { get; set; }
}

Why would it hurt to have Fat-Razor views?

Before we discuss a solution, we need to agree that there is a problem.

Distribution (non-centralisation) of Business Logic

Fat-Razors carry similar criticism to what the old classical ASP had (and what PHP currently has), that is no separation of concerns. The concern of this piece of code is to show visuals and not to do business logic.

The business logic for your domain should be centralised, having Fat-Razor views highlights the use of business logic in your views, hence you are giving more control to what should be a dumb layer and violating “the power in the wrong hand” principle. You would be risking a fragile business logic that will break more often.

Due to the scope of this post, I will just tell you “It is bad, don’t do it” and won’t dive into this topic but don’t take my word for it, here is Uncle Bob’s Clean Architecture.

Unit Testing

You’e heard it before, views aren’t unit testable (well, you can unit test them with some tools like Razor Generator but this is not the norm), or maybe more accurately shouldn’t be unit tested and should rely on different type of testing such as web testing like Visual Studio Testing Tools, Selenium or Capybara. So, Fat-Razor views mean more code you cannot unit test.

Refactoring

How many times did you change your code and compiled fine until you hit the page with your browser? And boom, your code refactoring did not propagate to the views, because it sees the views as text. Today, it is not that dramatic with tools like ReSharper, but it is still a problem. Having less C# code would reduce the problem size but not eliminate it.

Why would views become Fat-Razor views?

This is my experience of the symptoms from the industry, feel free to highlight more reasons in comments.

Using a generic model rather than a specific viewmodel

The MVC beginner tutorials try to show how simple MVC is by loading a record from the DB, transferring it into an object with an OR Mapper (Entity Framework, NHibernate and the likes) and then passing it to the view which renders it. Building a blog with MVC tutorials, I am looking at you.

Good example for beginners, gets them up to speed, but only works in trivial applications. Why? If your view is not getting exactly what it wants, then it is going to have to do some inspections on the model, hence, code and more code! If you treat the view as dumb and give it exactly what it wants, you will reduce the need for it to have any non-presentation logic.

Get your Fat-Razor into shape

Razor Ramon

Big Boss Man, RIP, was my early childhood wrestling hero (he was strong and cool!), but Razor Ramon fits better in this post.

Now that I have touched some of the necessary concepts around this subject, I could highlight some steps for Thin-Razor views.

1 – Use a viewmodel for your view

Do not take a model from the database and throw it directly into your view. This will couple your views to your entities and will probably invite business logic code.

My favourite way of doing this is by using a CQRS type query, this article does a good job in introducing CQRS: Introduction to CQRS. I query exactly what my view needs into a viewmodel, no intermediate model (or DTO), have a look at Dapper – a simple object mapper for .Net.

If you have a different architecture or legacy code, perhaps you could have a layer that would map a model into a viewmodel, have a look at AutoMapper, it may help.

2 – Try to differentiate business from presentation code

When writing views, always flag the question “Is this presentation code or business logic?” This is a hard question as in some cases, it might be a fine line between the two. Sometimes I ask for a second opinion and encourage a healthy debate.

3 – Package repeated presentation patterns into Html Helpers and Partial Views

Sometimes one repeats the same presentation code again and again in different views. I do that as well following the agile principle “Do the simplest thing that works, then refactor” then when I notice a pattern forming, I do refactor it into a Html Helper or a Partial View.

Conclusion

As they say “More is less”, the less code you write is the less code you have to maintain and the proper code you write will keep you safe if the developer inheriting the project, knows where you live.

I did review this post multiple times to cut it shorter, however, it is a big topic and it is still not complete. If you have any points that you would like to share, then please do comment.