Code Outside of the Box

« Aftermarket ASP.NET MVC - Part 2 - The View Engine Aftermarket ASP.NET MVC - Part 4 - Routing and URL Generation »

Aftermarket ASP.NET MVC - Part 3 - View Model Conventions

First published on January 7, 2016

This is part 3 of a multi-part series on “fixing” some of the inherit design problems with ASP.NET MVC.

Update 2016-02-18: I’ve also setup a repository on GitHub that includes many of these experiments and implementations.

1) Each View Should Have a Strongly Typed View Model

By default, views in ASP.NET MVC have a dynamic view model. The underlying object is simply a dynamic key/value bag. While this aids with “rapid development”, in my opinion it hurts long term maintenance. You lose static code analysis, compilation time error checking, refactoring support, and some debugging support.

Instead, I think it’s far better to always have a strongly typed view model for each and every view.

I also like the convention of putting view models alongside the view itself. By convention the class name is the name of the view followed by “ViewModel”. Thus your view model’s name follows a pattern of [Project].Views.[Feature].[ViewName]ViewModel.

2) A View Should Only Reference Properties and Methods From the View Model

View engines in ASP.NET MVC, such as Razor, typically compile to classes that inherit from System.Web.Mvc.WebViewPage. Unfortunately this includes a lot of cruft that carries over from classic ASP and ASP.NET web forms that inherit from System.Web.UI.Page. Essentially, the view has references to so much more that it probably needs to just simply render a template!

Accessing anything outside of the view model is an anti-pattern. Global or static state is also not a good idea. Remember that the only job of the view engine is to create a string from a template. It can be reduced to just a named function that accepts a model a returns a string.

“But I need to know if a user is authenticated to show/hide things.” Wrong. The view is responsible for rendering content and controls. If content or a control should be conditionally rendered, then the view model should include state that explicitly declares that condition.

ASP.NET MVC has the ability the generate URLs for routes based on a controller/action pair. This is great since since you don’t need to hardcode URLs all over the place (except you still deal with string constants). But you should not use HtmlHelper or UrlHelper to create a link/button or generate an action URL from within the view template. Instead, any generated URLs should be properties in the view model. This helps to further keep the view’s template clean and without references to constants, or worse, controllers and actions.

Additional Tip: Pass a Razor Template as a Parameter to a View Model Helper Function

In your view model you can create methods that accept Razor templates as Func<object, HelperResult>.

public MvcHtmlString RenderIfCanEdit(
    Func<object, HelperResult> template)
{
    if (this.CanEdit) {
        return MvcHtmlString
            .Create(template.Invoke(null)
            .ToHtmlString());
    } else {
        return MvcHtmlString.Empty;
    }
}

Now in the view, you can do awesomeness like this:

<div>
    @Model.RenderIfCanEdit(
    @<p>
        ...
    </p>
    )
</div>

Depending on the scenario this can be a lot cleaner than having nasty looking if-statements. Here’s an article by Phil Haack that explains it more detail.

Further Reading

Comments

Comments are not moderated. But I reserve the right to delete anything hostile, offensive, or SPAMy.