July 2017

Volume 32 Number 7

[Cutting Edge]

Finding the Cheese in ASP.NET Core

By Dino Esposito | July 2017

Dino EspositoDo you know the fable of two mice, two little boys and their cheese? It’s not an Aesop’s classic fable, but it’s a modern account full of motivational points written some 20 years ago by Spencer Johnson under the title, “Who Moved My Cheese?” The story is a metaphor for change and the way we should cope with and adapt to it. I dare say that the first, perhaps unconfessed, sentiment that some developers feel about ASP.NET Core is dismay—like in this fable when the characters reach out to the cheese station only to find it empty.

Who moved my cheese, then? The good news is that there’s still a lot of cheese around for every ASP.NET Core developer—probably more cheese than ever—but some of it’s no longer in the corridor of the development maze where it was before. Out of metaphor, that means that some common ASP.NET MVC tasks require different programming skills and sometimes a different approach. This month, I’ll pick up on actions such as registering the Model-­View-Controller (MVC) framework, defining core routes and controllers, and passing global data around.

It’s No Longer MVC By Default

A new empty ASP.NET Core project you create in Visual Studio 2015 or Visual Studio 2017 is primarily a Web project, meaning that it produces a server front end for an HTTP-enabled client to call. As pointed out in my May 2017 column (msdn.com/magazine/mt808498), ASP.NET Core is now great at setting up mini-Web servers devoid of the overhead and sophistication of a full Web site. However, ASP.NET Core isn’t limited to that and still provides plenty of opportunities to arrange full Web sites through the familiar programming pattern of routes, controllers and views. ASP.NET Core, though, requires an extra initial step before you can start adding routes and creating controllers and lets you register routes through a slightly different approach than in classic ASP.NET MVC. Let’s see how it works.

As mentioned, ASP.NET Core is a plain Web front end based on two main segments of code: routing and application model. The transition between segments isn’t particularly visible to developers and takes place in the MvcRouteHandler service (see bit.ly/2osQOcs). Once the control leaves the routing repository, the MVC application model can be enabled explicitly. If not, any requests end up being processed in the terminating method of the ASP.NET Core middleware. To add the MVC service to the ASP.NET container, you add a line of code to the ConfigureService method of the startup class:

public void ConfigureServices(IServiceCollection services)

{

  services.AddMvc();

}

Note that the code requires a reference to an additional package that the IDE of choice (Visual Studio) typically offers to restore for you. The parameter-less version of the AddMvc method uses all default settings for the MVC route handler service. Most of the time, you don’t need to change any of those default settings, but surely situations might occur where you just need to make changes. A second overload of the AddMvc method lets you select ad hoc options. The second overload receives an instance of the MvcOptions class as an argument. Here’s an example:

services.AddMvc(options =>

{

  options.ModelBinderProviders.Add(new MyDateBinderProvider());

  options.SslPort = 345;

});

The MvcOptions class is a container of configuration parameters for features you might want to customize in the MVC framework. For example, the previous code snippet adds a new model binder that changes the standard way in which posted dates are mapped to .NET date objects. You can assume that the MyDateBinderProvider class here adds the ability to parse ad hoc strings into valid DateTime objects. In addition, the example specifies the SSL port to be sniffed when any controller class is decorated with the RequireHttps­Attribute. The list of options you can configure isn’t limited to the three examples here. The full list can be found at bit.ly/2oK7ETs.

It’s worth noting that the AddMvc method is an umbrella method under which a lot of finer-grained services are initialized and added to the pipeline. Because of this, the effect of AddMvc on the memory footprint of the whole application might need some forethought. Not that AddMvc bloats the runtime, but it does quite a few things internally and atomically enables quite a few services. In addition to the core services of the MVC application model, such as routes and controllers, it also enables authentication and authorization, tag helpers, URL resolution helpers, default media type mappings, data annotations, and cross-origin resource sharing (CORS). Furthermore, AddMvc sets up the service to process action results as HTML views and JSON streams and registers the Razor view engine into the MVC system.

Cutting Down the List of Default MVC Services

Especially if you’re hosting the application in some sort of cloud configuration, you might want to be very stingy about resources and have the application reference nothing but the bare metal of the ASP.NET framework. Calling AddMvc might give much more than you need sometimes. Let’s see how to make the list of references shorter. The following code is enough to serve plain HTML views to browsers. It should be noted, though, that it doesn’t support some more advanced features, including data annotations for form validation and tag helpers:

public void ConfigureServices(IServiceCollection services)

{

  var builder = services.AddMvcCore();

  builder.AddViews();

  builder.AddRazorViewEngine();

}

Likewise, this configuration won’t let your controller methods return formatted JSON data. However, you need one extra line to add that capability, as well:

builder.AddJsonFormatters();

It’s worth noting that some of the services the call to AddMvc automatically enables are useful to have, though not strictly necessary, only if you’re exposing a Web API. The services you might want to get rid of are API Explorer and Formatter Mappings and, to some extent, CORS.

Enabling the MVC Service

Adding a service to the pipeline isn’t enough: You also have to configure it. This typically happens in the Configure method of the startup class via a method that conventionally takes the name of UseXxx where Xxx is the nickname of the service:

public void Configure(IApplicationBuilder app)

{

  app.UseMvc();

}

At this point, everything in MVC is completely set up and ready to go except conventional routing. If you decide to go with attribute routing, then you’re done. Otherwise, for the MVC service to be effective, you must list the routes the application will recognize and handle. A route is a URL template mapped to a pair made of controller and action names. You can add as many routes as you wish and of nearly any shape you like them to be. ASP.NET MVC, however, provides a default route that serves most of the common scenarios. To enable the default route, you can proceed like this:

public void Configure(IApplicationBuilder app)

{

  app.UseMvcWithDefaultRoute();

}

The default route is defined as follows and, as you can see, it’s nothing more than the old familiar route configuration of classic ASP.NET MVC:

routes.MapRoute(

  name: "default",

  template: "{controller=Home}/{action=Index}/{id?}");

In classic ASP.NET MVC, a routeless application makes little sense as it would be quite problematic to invoke any behavior from the outside. In ASP.NET MVC Core, instead, routes work side-by-side with the terminating middleware—the Run method of IApplicationBuilder:

public void Configure(IApplicationBuilder app)

{

  app.UseMvc(routes => { });

  // Terminating middleware

  app.Run(async (context) =>

  {

    await context.Response.WriteAsync(

      "No configured routes here.");

  })

}

Given the preceding code, the application has no configured routes. However, it still reacts to any call by displaying a message through the terminating middleware. In other words, you can list your routes first and then use the terminating middleware as a sort of catch-all route that gently informs about any mistyped or misunderstood URLs.

MVC Controllers

Routing is the first step of the longer process that takes an HTTP request to produce a response. The ultimate result of routing is identifying the controller/action pair that will process any requests not mapped to a physical static file. In ASP.NET Core a controller is the same as it was in classic ASP.NET, namely a class that encap­sulates the HTTP context of the request and takes action. The work of a controller is governed by a system component known as the action invoker (see Figure 1). The action invoker isn’t a new item in the overall architecture of ASP.NET MVC as it was part of the architecture since the early days of classic ASP.NET MVC.

Figure 1 A Request Flowing Through the ASP.NET Environment

The action invoker injects the HTTP context into the controller’s space and the code running within the controller can access it through the handy HttpContext property. To facilitate the process in classic ASP.NET MVC, any controller class must inherit from a base class that contains all the necessary plumbing. In ASP.NET Core, inheriting a controller from a common base class is no longer necessary. A controller class can be a plain old C# object (POCO), as simple as this:

public class HomeController

{

  // Some code here

}

A POCO controller is a class that can map incoming requests to HTML views, but has no dependency on the HTTP context. In particular, this means that you can’t inspect the raw data being posted, including query string and route parameters. The context information, however, is available as a separate plug-in that you attach only to the controllers where you need it. Ultimately, this is another good example of the extreme granularity of the ASP.NET Core framework. As an example, let’s see what’s required to access route information from within a POCO controller. Interestingly, the feature to leverage is quite general and applicable and recommended for a number of other common programming activities:

public class HomeController 

{

  private IActionContextAccessor _accessor;

  public HomeController(IActionContextAccessor accessor)

  {

    _accessor = accessor;

  }

  ...

}

The constructor of the controller class can declare a parameter of type IActionContextAccessor. The interface is the key to have context information injected into the controller’s space. All that’s required to do from within the controller is to save the received instance of the interface type for later use. The following code snippet shows how to access the RouteData object that contains any data tokenized into the handled route URL:

public IActionResult Index()

{

  var controller =

    _accessor.ActionContext.RouteData.Values["controller"];

  ...

}

Though possible, injecting the IActionContextAccessor service isn’t recommended because it performs poorly and is rarely needed. Another way to access route data from within a POCO controller is using the FromRoute attribute to decorate a parameter in the action:

public IActionResult Index([FromRoute] string controller)

{

  ...

}

However, regardless of effectiveness, who injected a reference to IActionContextAccessor into the controller? That’s where another relevant addition to the ASP.NET Core framework fits in—the internal Inversion of Control (IoC) pattern.

Sharing Global Data

Nearly every Web application holds some data to share globally. In classic ASP.NET, a common practice was to load global data at startup and save it (net of possible threading issues) into some global static variables exposed from the application object. It’s not the perfect solution for everyone, but quite functional if you’re aware of what you were doing. ASP.NET Core provides a better way to do it that guarantees that every application context receives just what it needs, thus reducing the risk of accessing unwanted data in unwanted contexts. Any chunk of global data must be added to the IoC subsystem in the ConfigureServices method. Here’s how you would add the action context, for example:

public void ConfigureServices(IServiceCollection services)

{

  ...

  services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

}

The AddSingleton<TInterface, T> method registers that any requests for an object assignable to the TInterface type must be resolved through an instance of the concrete type T. Once the association has been mapped in the application startup, controllers will gain access to it by simply declaring a parameter of the type in their constructor.

Global information can then be read at the application startup from a variety of sources, including JSON and text files, databases, and remote services and packaged into a custom type. To make the object globally available you just add it to the IoC system via AddSingleton. The operation takes place only once.

An Ad Hoc Framework for Application Options

As far as global data is concerned, ASP.NET Core isn’t limited to basic IoC functions, it also supports options. Options are a feature specifically designed to deal with the initial configuration of the application, namely mostly read-only data to be shared globally:

var builder = new ConfigurationBuilder()

  .SetBasePath(env.ContentRootPath)

  .AddJsonFile("MyAppSettings.json", optional: true, reloadOnChange: true);

Configuration = builder.Build();

In the constructor of the startup class, the previous code sets a given JSON file as the provider of configuration data. The builder uses the provided information to prepare and return an IConfigurationRoot object to be used to access data. You also declare the following in the startup class:

public IConfigurationRoot Configuration { get; }

Global data must be retrieved piece by piece through a query API. The options framework, instead, lets you load it into an aptly defined C# class to be passed around through the IoC system. Add the following code to the ConfigureServices method:

public void ConfigureServices(IServiceCollection services)

{

  services.AddOptions();

  services.Configure<GlobalConfig>(Configuration.GetSection("Globals"));

  ...

}

In the example, GlobalConfig is a custom class you define to be 1:1 with the content of the JSON initial data. The Configure method does a good job of reflection to read the specified segment of JSON into the C# class. As a pleasant side effect of the code, any controller now can be injected options through the following pattern:

public class HomeController : Controller

{

  private GlobalConfig Globals { get; }

  public HomeController(IOptions<GlobalConfig> config)

  {

    Globals = config.Value;

  }

  ...

}

Any global data can now be accessed via the Globals custom property.

Wrapping Up

As in the Johnson fable, with ASP.NET Core the problem isn’t the cheese and its lack thereof. There’s plenty of cheese in ASP.NET Core. The problem is the attitude to find it. Apparently, many things are different and not easily figuring out how to do basic things such as loading global data might be frustrating at first. Some ASP.NET MVC cheese has been definitely moved to a different location, but is now even tastier and more abundant. Next month, I’ll touch on another relevant piece of cheese that’s not where you always get it: forms authentication.

Dino Esposito is the author of “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) and “Modern Web Applications with ASP.NET” (Microsoft Press, 2016). A technical evangelist for the .NET and Android platforms at JetBrains, and frequent speaker at industry events worldwide, Esposito shares his vision of software at software2cents.wordpress.com and on Twitter: @despos.

Thanks to the following Microsoft technical expert for reviewing this article: Doug Bunting
Doug Bunting is a developer working on the ASP.NET team at Microsoft.


About the Author