Teach Them How To Do ASP.NET MVC Right (And Let Them Figure Out the Rest)


Editor's note: The following post was written by Visual Studio and Development Technologies MVP Dino Esposito as part of our Technical Tuesday series. Ovais Mehboob Ahmed Khan of the MVP Award Blog Technical Committee served as the technical reviewer for this piece.

In version 5.x of Visual Studio, the File|New Project menu item enables you to create a brand-new project of any supported type. Let’s say you are interested in ASP.NET web application projects. Whether you want to work on a new classic ASP.NET MVC 5.x project, or an ASP.NET Core project, by default you have to choose between an empty project or sample web application project.

Oftentimes you end up with a lot of boilerplate code—that is, always the same—in an Empty project, and having to remove it from a sample web application project. In this article, I discuss the bare minimum code that every ASP.NET MVC application should have, to really save some valuable time and to help programmers - especially junior ones - to develop a more pragmatic mindset.

In my opinion, the best way to train junior developers is by teaching them how to do things right, and leaving them to their own devices in hands-on situations. A brand-new, tailor-made project template - approved by the team leaders - is an excellent tool to achieve this goal.

Here I illustrate my favorite one, also available for ASP.NET Core on GitHub here

Dealing with Globally Accessible Data

Many applications have some read-only data that must be globally accessible - for example, the application name, copyright information, functional and operational settings, and in general any information that’s preferable not to have hard-coded in the source files. If this information changes during use, then things can get quite complicated and a thread-safe cache object is desirable.

However any global read/write data tends to be application and business specific, so any attempt to generalize it might be superficial. Although if the data is read/write you might want to consider some flavors of a cache object, and some flavors of a distributed cache object if your application is deployable on web farms or subject to scale on multiple cloud servers.

Global read-only data is much easier to handle, even in a distributed environment. By read-only globally accessible data, I mean any constant data that you typically read from web.config and that can cause an application restart if modified.

So how would you store this read-only global data?

The easiest approach to take - and also the fastest-running - is using static variables. This is faster than reading them from an application cache. From a design perspective, you might want to shape up a class that gathers all possible pieces of data under the same roof, and then make a singleton instance of the class to use member variables, globally available to code. Although there’s some architectural changes, but the same approach can be used in ASP.NET Core. Let’s first see how to do this in classic ASP.NET MVC and then in ASP.NET Core.

Configuration Data in Classic ASP.NET MVC

In classic ASP.NET MVC, the start of an application is notified through the Application_Start event you handle in global.asax.

 public class StarterKitApplication : HttpApplication
{
    public static StarterKitSettings AppSettings { get; private set; }

    protected void Application_Start()
    {
        // Load configuration data
        AppSettings = StarterKitSettings.Initialize();

        // More things to do here
        ...
    }
    ...
}

In Application_Start, you can initialize the class to store all global configuration data. In the code snippet above, this class is called StarterKitSettings and it offers a factory method, named Initialize, to read data from whatever store they were kept, most likely the web.config file. Here’s an example that might work as a good starting point for the settings class of nearly any application.

 public class StarterKitSettings
{
    public string ApplicationTitle { get; private set; }

    public static StarterKitSettings Initialize()
    {
       var title = ConfigurationManager.AppSettings["starterkit:app-title"] 
                       ?? String.Empty;
       var settings = new StarterKitSettings
       {
            ApplicationTitle = String.Format("{0}", title)
       };
       return settings;
    }
}

All members of StarterKitSettings are read-only and initialized only once. An instance of the StarterKitSettings class is exposed out of the application main object as a static property. In this way, the data is globally available, and as fast as possible, because of the static nature of the container. It is still bound to the application via the HttpApplication object - the beating heart of ASP.NET applications. Consuming configuration data is as easy as using the following code:

 var title = StarterKitApplication.Settings.ApplicationTitle;

The settings are available from anywhere in the application because of the static attribute, but this doesn’t strictly mean that the data should always be consumed directly from the place it lives. Let’s consider the problem of setting the title of any views to the application name. The following code used in Razor layout view would certainly work:

<html>
<head>
   <title>
     @StarterKitApplication.Settings.ApplicationTitle
   </title>
</head>
...
<html>

However, it is probably cleaner from the design perspective if you just package the title as read from the settings into a view model object passed to the view. In this way, the desired data is injected where it is really useful. I’ll expand on the view model example later on in the article, when touching on the relationships between controllers and views. For the time being, I’d say that this approach has been taken one step further and, more importantly, engineered as built-in infrastructure in ASP.NET Core.

Configuration Data in ASP.NET Core (done editing)

ASP.NET Core allows you to read configuration data through a variety of providers and from a variety of sources, most notably JSON files. (There’s no web.config file required in ASP.NET Core). The framework provides an easy way to map the content of the configuration JSON file to your class. Let’s assume the following JSON settings:

 {
  "ConnectionStrings": {
    "YourDatabase": "Server=(local);..."
  },
  "Globals": {
    "Title": "Your application title"
  }
}
The code below loads it into the memory of the application:
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env) 
{
    var builder = new ConfigurationBuilder() 
         .SetBasePath(env.ContentRootPath) 
         .AddJsonFile("StarterKitSettings.json"); 

    // This is a container defined on the Startup class as a property
    Configuration = builder.Build(); 
}

The code runs in the constructor of the Startup class and populates an IConfigurationRoot object with the entire list of settings. But how can you consume that data? Here’s where the Options service of ASP.NET Core fits in:

 public void ConfigureServices(IServiceCollection services)
{
      services.AddOptions();
      services.Configure(Configuration.GetSection("Globals"));
}

The Options service has the ability to load an instance of the specified type—GlobalConfig—in the code snippet with any properties in the given section of the configuration tree that matches member names. In a way, it is as if settings were model-bound to an instance of the GlobalConfig class. Say the class looks like the following:

 public class GlobalConfig 
{ 
    public string Title { get; set; } 
}

The value of the Title property in the Globals section of the JSON file is copied into the matching property of the GlobalConfig class. In the end, there’s an instance of a user-defined class that holds all the configuration data. However, this class is NOT globally accessible from any place in the code. To be usable, it must be explicitly injected wherever there’s the need for it. The ASP.NET Core Dependency Injection (DI) infrastructure provides an easy and effective way to do just this. Here’s how you can inject the instance of GlobalConfig in a controller.

 public class HomeController : Controller
{
    private GlobalConfig Configuration { get; }

    public IActionResult Index(IOptions config)
    {
        Configuration = config.Value;
    }
    ...
}

The configured instance of GlobalConfig is treated as a singleton by the DI infrastructure so that the overall pattern is very close to the one outlined for classic ASP.NET MVC.

Dealing with Application’s Errors

Another feature that nearly any application needs to have properly addressed - even if it’s only a prototype - is error handling. If you read through the ASP.NET MVC documentation, you’ll see there are many ways to do this. However, my take on it is that you only need to decide whether the error (or rather, the exception in the code) is something you want to try to prevent or just handle, if and when it happens.

If you are aware that your code is performing a possibly problematic operation, then the best you can do is wrapping that code in a try/catch block and recover nicely if something goes wrong. If you used a try/catch block, then from the perspective of the caller of the failing code nothing bad has happened. The caller receives a response, except that the response could be empty or incomplete. All other scenarios are better handled through a global error handling strategy.

ASP.NET MVC offers an attribute like HandleError and action filters specialized in dealing with exceptions, but all those things don’t address all possible exceptions an application can run into. In particular, route exceptions and model binding exceptions are not covered. That’s why it is preferred to only go with an application level error handler - and one that’s done in a smart way, as the one in ASP.NET Core.

Error Handling Strategy in Classic ASP.NET MVC

In ASP.NET, any exceptions not trapped locally by a try/catch block bubbles up and turns into a yellow-page-of-death unless an Application_Error handler is defined in global.asax.

 protected void Application_Error(object sender, EventArgs e)
{
   var exception = Server.GetLastError();

   var httpContext = ((HttpApplication)sender).Context;
   httpContext.Response.Clear();
   httpContext.ClearError();
   InvokeErrorAction(httpContext, exception);
}

There are two main things you want to do from within the error handler. First, grab the last error that occurred. In classic ASP.NET the information is returned by the GetLastError method on the Server object (this is different in ASP.NET Core). Next, you have to display—in some way—the error view. Sounds easy? Well, not exactly. If you just redirect to an error page you lose the information about the exception, unless you find a way to package it up with the redirect operation. Losing the exception details might not be a problem if it is OK for you to just show a static HTML page - for security reasons this is sometimes just the way to go. However, if you want to enable some debug mode on production code you have no way to get to know exactly what went wrong.

You can't serve HTML from the handler either. Creating yourself an instance of some Error controller class and invoking a view from there is not an option, either. You can call the controller but rendering the view is a different story. This solution is a bit tricky, but it works: 

 private static void InvokeErrorAction(HttpContext httpContext, Exception exception)
{
     var routeData = new RouteData();
     routeData.Values["controller"] = "error";
     routeData.Values["action"] = "index";
     routeData.Values["exception"] = exception;
     using (var controller = new ErrorController())
     {
       ((IController)controller).Execute(
            new RequestContext(new HttpContextWrapper(httpContext), routeData));
     }
}

Essentially, the code prepares an internal request for the action invoker to consider, and makes it look as if it came from the outside. The request takes the form of the URL error/index with exception data packaged in the route data container.

The Index method on the ErrorController receives the exception via model binding and goes through its own workflow to figure out which view to serve and which data to pass. 

 public ActionResult Index(Exception exception)
{
    ...
}

With this safety net installed from anywhere in your code, you can just throw an exception if something is wrong and you don’t want to even try to recover. The exception goes unhandled and results in a nice error page displayed that at the very minimum may contain the message associated with the exception.

Error Handling Strategy in ASP.NET MVC Core

Interestingly, ASP.NET Core takes a similar approach when it comes to production-level error handling and the tricky code shown above (or rather its .NET Core equivalent) is baked in the framework and available through a simple call. In ASP.NET Core, nothing - not even error pages - come out of the box and every functionality must be explicitly enabled. Here’s a common pattern for dealing with error handling in ASP.NET Core: 

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
       app.UseDeveloperExceptionPage();
       app.UseStatusCodePagesWithReExecute("/Error/Index/{0}");
   }
   else
   {
       app.UseExceptionHandler("~/Home/Error");
       app.UseStatusCodePagesWithReExecute("/Error/Index/{0}");
   }
   ...
}

If the application is running in development mode, you instruct the runtime to serve the canonical error page, which is full of detailed information about what has happened, including the stacktrace. Otherwise, the specified error handler is invoked as a URL. The URL is processed within the runtime as if it were a request coming from the outside. Special attention is also put on particular status codes, such as 404. In this case, if it’s not an internal error, the status code is passed on the URL and can be captured by the controller method via model binding. In ASP.NET Core, the error method doesn’t receive information about the exception but has to work its way to retrieve it. 

 var lastException = HttpContext.Features.Get().Error;

The object retrieved exposes the last occurred exception via the Error property of the IExceptionHandlerFeature object. The information in the exception can then be used to prepare the view model to render back to the user.

Application Services and Controllers

ASP.NET MVC forces you to create controller classes to organize the functionalities of an application, and this seems ideal to achieve due to separation concerns. The reality is a bit different. Further layering is necessary and it is entirely up to you. An easy and effective way to achieve true separation of concerns in ASP.NET MVC is using application service classes from within the controller. These classes form the “application layer” of the solution and are responsible for the implementation of use-cases. The recommended strategy is based on the following points:

  • Any action the controller must take is delegated to a controller-specific, application service class. This class is not expected to be reusable and largely depends on the frontend that called it. It comes as no surprise that you may end up with different application services for mobile application calls and web application calls.
  • Methods on the application service class accept data as it comes from the model binder. Whatever the controller gets, it passes down the stack to the application service class. Only if strictly necessary the controller will make changes to the received data before passing it down to the service class.
  • Methods on the application service class return data packaged in a view model object ready to be handed to the view engine for rendering out the HTML view. (This is required only if the controller is expected to return HTML.)

In classic ASP.NET MVC, you can define the application service as below. 

 public class HomeController : Controller
{
    private HomeService _homeService = new HomeService();

    public ActionResult Index(...)
    {
        var model = _homeService.GetIndexViewModel(...);
        return View(model);
    }
}

Needless to say, you can extract an interface from the application service class and program the controller against it rather than having it work against a concrete implementation. You may use poor-man dependency injection for this purpose: 

 public class HomeController : Controller
{
    private IHomeService _homeService; 
    public HomeController(IHomeService service)
    { 
       _homeService = service;
    }
    public HomeController() : this(new HomeService())
    { 
       _homeService = service;
    }

    public ActionResult Index(...)
    {
        var model = _homeService.GetIndexViewModel(...);
        return View(model);
    }
}

In addition, you can also use the appropriate IoC library to resolve the controller instance. This is fully supported by ASP.NET MVC and requires you to set a custom, IoC-based controller factory.

Nicely enough, this same pattern comes out of the box in ASP.NET Core where an internal DI system is in place. All you have to do in ASP.NET Core is register your application service classes with the DI system and just inject instances wherever you need them to be.

 public class HomeController : Controller
{
   private readonly HomeService _homeService;

   public HomeController(IHomeService service)
   {
      _homeService = service;
   }

   public IActionResult Index()
   {
       var model = _homeService.GetHomeViewModel();
       return View(model);
   }
}

To make sure the dependency is correctly resolved you need to let the DI system know about the injectable type—the IHomeService interface. Here’s the code you need to have in the configuration of the ASP.NET Core application: 

 public void ConfigureServices(IServiceCollection services)
{
   services.AddTransient<IHomeService, HomeService>();
   ...
}

The net effect of the code above is that any new instance of the controller class will receive a fresh new instance of the HomeService class.

Controllers, Views and View Models

There are many ways for a controller method to pass data on to a view. You can use the ViewData dictionary, the ViewBag dynamic object, or view model classes. I’m a strong believer in view model classes. However conceptually, a view model class is quite different from the entity class you might use via Entity Framework to read and write data from a database.

A view model class defines the amount of data being passed to a particular view in the shape and form that the view needs. The view is essentially passive in the sense that either it doesn’t contain code at all, or it just contains some specific pieces of rendering and formatting logic. A view model class may sometimes reference an entity object but I haven’t yet found a realistic application that populates its views only through an entity object.  

View model classes should be grouped by layout. Any layout view you use in the application should have its own base view model class from which all view model classes derive to fill out views based on the layout. Here’s a sample view model base class for a layout: 

 public class ViewModelBase
{
    public ViewModelBase(string title = "")
    {
        Title = title;
        ErrorMessage = "";
        StatusCode = 0;
    }

    public string Title { get; set; }
    public string ErrorMessage { get; set; }
    public int StatusCode { get; set; }
}

The layout file declares its model to be of the ViewModelBase type and all views based on the layout will have a view model class that just inherits from ViewModelBase. 

 @model ViewModelBase

View model classes can be conveniently grouped under a ViewModels folder in the project, with each going into a subfolder specific of the layout it refers to. You may think this organization is a bit too complex for simple things. However I believe disorder and lack of organization make things more complex than they really are. Far too often I have met developers who justified limited use of view models with the fact that they were in a hurry and the approach was time-consuming. They were all developers who then had problems to fix down the road.

The approach described here based on view models works well both in classic ASP.NET MVC and in ASP.NET Core.

Summary

While each application is different by nature, as long as they are based on a given framework, they all should look the same when it comes to fundamental features and overall design. Regarding ASP.NET MVC, the way you organize controllers and views and ancillary aspects - such as configuration and error handling - should be common to all applications because in the end, it’s just infrastructure.

Every team probably has its own ideal way of coding common aspects of their applications. This article only presented my personal take for both ASP.NET MVC and ASP.NET Core. However, for me it was a nice finding when I realized that most of my personal best practices of ASP.NET MVC are now consolidated and engineered straight in ASP.NET Core. You will find a full implementation of the ASP.NET MVC template discussed in the article for ASP.NET Core at http://github.com/despos/youcore. I’m looking forward to your feedback.


Since 2003, Dino Esposito has been a voice for Microsoft Press, and the author of many popular books on ASP.NET and software architecture. For example, Dino authored “Architecting Applications for the Enterprise” with fellow MVP Andrea Saltarello in 2015, and “Modern Web Development” in 2016. He has the book “Programming ASP.NET Core” in the works for 2017. When not training, Dino serves as the CTO of Crionet—a fast growing firm that specializes in Web and mobile solutions for the world of professional sports. Follow him on Twitter @despos

 

Comments (1)

  1. Always nice to see a known expert doing things the same way you figured it out yourself. Guess that's a good thing!

Skip to main content