August 2018

Volume 33 Number 8

[ASP.NET Core]

What's New in ASP.NET Core 2.1

By Steve Smith

Microsoft recently released ASP.NET Core 2.1 along with .NET Core 2.1 and Entity Framework (EF) Core 2.1. Combined, these releases offer some great improvements in performance, as well as additional features for .NET Core developers. Microsoft is also offering Long-Term Support (LTS) with this release, meaning it will remain supported for three years. This article provides an overview of the improvements in ASP.NET Core 2.1. To learn more about what’s new in EF Core 2.1, check out this month’s Data Points column by Julie Lerman, “Deep Dive into EF Core HasData Seeding” and her column last month (msdn.com/magazine/mt847184) that  delves into the new EF Core 2.1 Query Type feature, which lets you more easily query a database without needing true entities with key properties to consume the results.

Razor Pages Improvements

I’ll start by talking about improvements to Razor Pages, a new feature introduced in ASP.NET Core 2.0 that I wrote about in the September 2017 issue (msdn.com/magazine/mt842512). Version 2.1 adds a couple features that didn’t make it into the 2.0 release, such as support for Pages-specific folders to search for shared assets. The most common shared assets are layout files and partials. By default, these were located in the root /Pages folder in ASP.NET Core 2.0, although ASP.NET Core MVC would discover them if they were placed in a /Views/Shared folder. In version 2.1, Razor Pages now searches for these shared files by looking for them in the following locations (in order):

  1. The current Pages folder
  2. /Pages/Shared
  3. /Views/Shared

This allows you to easily override shared assets where desired, but if you’re adding Razor Pages to an existing MVC-based application, you can continue to leverage any existing shared assets it has in its /Views/Shared folder.

Another feature that was missing when Razor Pages initially shipped was support for Areas. With ASP.NET Core 2.1, you can now add a /Pages folder with Razor Pages to any area located in the /Areas folder of your ASP.NET Core application. Microsoft has also updated the default Visual Studio Web Application template to use Areas for identity functionality when you choose “Individual user accounts.”

Together, these features make it much easier to organize Razor Pages in the project system.

Shared Razor Libraries

Another new feature in 2.1 is support for loading Razor assets from separate libraries or packages. Separate ASP.NET Core apps frequently share common assets, such as Identity features (login, register, forgot password and the like). Typically, these common features resulted in a lot of duplicate code across individual projects, leading to increased technical debt. The new Razor Class Library (RCL) feature supports building Razor files and deploying them as associated projects or NuGet packages that any number of ASP.NET Core apps can consume.

With the addition of this feature, the compiler will build Razor assets, automatically searching for related assets in referenced libraries and packages. Previously, Razor assets weren’t built until after they were deployed and requested. ASP.NET Core 2.1 integrates Razor compilation into the build process, which also results in faster app start times.

Razor assets in RCLs can be overridden, so if you use them to share common assets between projects, you don’t lose the abil­ity to customize certain aspects on a per-project basis. RCLs shine for cross-cutting app concerns like layout, navigation and authentication. In fact, the built-in ASP.NET Core Identity feature is able to leverage this support to become more reusable between projects, as well.

Project Template Updates

The default Web Application project template includes a few changes not previously mentioned. The New Project dialog now includes a checkbox to specify whether to enforce HTTPS. New support for General Data Protection Regulation (GDPR) is included by default, so projects now include default privacy pages and cookie consent Razor partials. Note that these documents are simply placeholders—it’s your responsibility to add your organization’s actual policy. Figure 1 shows a brand-new ASP.NET Core 2.1 project, which has been configured to use Identity for individual user accounts.

New Web Application Project Structure in ASP.NET Core 2.1
Figure 1 New Web Application Project Structure in ASP.NET Core 2.1

Identity as an Add-On

If you wanted to take advantage of the ASP.NET Core Identity functionality in your app prior to version 2.1, you generally had to make the decision to add support for it when you were creating your Web app project. If you wanted to add Identity support later, typically the process would be to create a brand-new Web app project with the appropriate support (such as “Individual user accounts”), and then copy the files from the new app into your existing app. This was not an ideal solution.

It was always a tough technical challenge for ASP.NET Core (and ASP.NET, too) to support adding Identity support to an existing app, in part because this support included front-end Razor assets that couldn’t be packaged separately, and deploying new assets into an existing app could fail for a host of reasons. With the addition of RCL support, adding Identity to an existing app becomes much easier, as the process no longer needs to add new Razor files to the existing app.

You can use scaffolding support in Visual Studio to add Identity to existing apps. New apps built with the latest templates will leverage shared Razor assets rather than including Identity-related pages/­views in the project itself. The benefit of this approach is fewer boilerplate files in your new projects, but with no loss in functionality or your ability to customize behavior. You saw in Figure 1 that by default Identity support only adds an Area with a single _ViewStart.cshtml file in it. You can customize the behavior of certain files by right-clicking on the project, clicking Add | New Scaffolded Item and then choosing Identity. Select the pages you want to scaffold and specify the DbContext class and any other options.

You’ll see how I’m customizing these files when I get to SignalR.

Improved HTTPS Support

If you’re not already using HTTPS for your Web apps, you probably should be. Search engines and browsers are now actively promoting sites that use HTTPS and treating those that don’t as potentially insecure. GDPR requires that sites use HTTPS to protect user privacy. ASP.NET Core 2.1 bolsters support for developing and testing apps using HTTPS, including making it much easier to use HTTPS locally when building apps.

After you install the .NET Core 2.1 SDK, the first time you run it, it will install a development certificate. You can manage this certificate using the new dotnet dev-certs tool, which is installed with the SDK. Once the development certificate is trusted, you’ll be able to develop and test your ASP.NET Core 2.1 apps locally using HTTPS as your default protocol, more closely mirroring your production environment (where of course you’re using HTTPS).

Your application can further improve its security by signaling to browsers that it supports HTTP Strict Transport Security (HSTS). HSTS helps prevent certain kinds of attacks, such as “man in the middle” attacks, by signaling to browsers that all responses for a given request must use HTTPS connections instead of plain HTTP. Without HSTS, even a page that’s served via HTTPS might include resources that still use HTTP. These resources could easily be replaced or modified by routers in between the user and the servers hosting the content, because they’re unprotected by encryption. HSTS prevents this attack vector.

HSTS is implemented with a response header in the HTTPS response of the original resource. For example:

Strict-Transport-Security: max-age=16070400; includeSubDomains

HSTS is enabled for your ASP.NET Core applications using middleware, configured by default in the application template’s Startup.cs file. It’s not recommended to use HSTS on localhost, so the default behavior only includes the middleware when the project is running in a production environment.

In addition to HSTS, you can also require HTTPS for your application using the new UseHttpsRedirection middleware. At its simplest, you enable this by adding the following line to your Configure method in Startup.cs:

App.UseHttpsRedirection();

Microsoft now recommends this for all ASP.NET Core apps, and it is the default in the Web application templates. This middleware will automatically redirect requests that come in over HTTP to HTTPS. It uses conventions to discover the appropriate HTTPS port, assuming only one is being used by the app. Alternately, you can configure by setting the ASPNETCORE_HTTPS_PORT environment variable (or http_port configuration key) or by specifying options in code in ConfigureServices:

services.AddHttpsRedirection(options => options.HttpsPort = 5555);

Updated SPA Templates

The application templates for Single Page Applications (SPAs) have been updated to use the latest recommended approaches for Angular and React apps. Specifically, the Angular template is now based on the Angular command-line interface (CLI), and the React templates are based on create-react-app (CRA). These SPA frameworks ship updates frequently, so updating the built-in templates to the latest approaches helps ensure new apps built with them will use current best practices for each associated framework.

SignalR

SignalR is a popular library that makes it very simple to add real-­time Web functionality to ASP.NET applications. ASP.NET Core SignalR is a new version of SignalR that ships with ASP.NET Core 2.1. It features a number of improvements over previous versions:

  • No client-side dependency on jQuery
  • MessagePack-based binary protocol
  • Based on Microsoft.AspNetCore.Sockets (not Http)
  • Supports multiple formats (from same endpoint)

The server-side components of SignalR are included in Microsoft.AspNetCore.SignalR NuGet package. This package is included in the Microsoft.AspNetCore.App metapackage, so you typically should not need to add it separately to your ASP.NET Core project (assuming you’re referencing Microsoft.AspNetCore.App version 2.1 or greater). SignalR supports multiple clients, including JavaScript for Web pages and a .NET client for .NET applications. The recommended way to add the JavaScript client to your project is through npm. Assuming you have npm installed, you can run the following commands to add the client to your project:

npm init –y
npm install @aspnet/signalr

The first command initializes a packages.config for your project—you only need to run this command if you’re not already using npm. The second command downloads the SignalR JavaScript client to your node_modules folder. You’ll need to copy the signalr.js file from node_modules to an appropriate location in your ASP.NET Core app’s wwwroot folder in order to reference it from your app.

SignalR Demo: Toast Notifications

To demonstrate how easy it is to get set up with SignalR, while also showing how to customize the behavior of the new Identity package, I’ve created a simple demo. It pops up a notification in the browser whenever a user registers or signs in or out of the app. This notification should appear anywhere on the site, so I’m going to modify the _Layout.cshtml file to include the client-side scripts necessary. Add the following to the bottom of the _Layout.cshtml file:

<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/
  toastr.min.js"></script>
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>

The first reference is to a simple notification library called toastr that I’ll use to display the notifications. The second is the signalr.js file copied from node_modules into the app’s wwwroot/lib/signalr folder. The last is the existing site.js file that’s created as part of a new ASP.NET Core project. The order of these scripts is important.

The next step is to modify site.js to add the JavaScript code necessary to display messages when a request is received from the server. There are three steps involved in this process. First, a connection must be created, using the signalR.HubConnectionBuilder. This type uses the builder design pattern to configure the connection with any necessary parameters, and then the connection is returned from the build method. Next, you configure message handlers using the .on method on the connection. Create one named handler for each behavior you want the server to be able to initiate. Finally, you initiate the connection by calling its start method:

const userUpdatesConnection = new signalR.HubConnectionBuilder()
  .withUrl("/userUpdates")
  .build();
userUpdatesConnection.on("ReceiveSignIn", (user) => {
  toastr.options.escapeHtml = true;
  const message = user + " logged in.";
  toastr.info(message, "New Login!");
});
// Additional handlers omitted
userUpdatesConnection.start().catch(err => console.error(err.toString()));

This code will run on every page that uses _Layout.cshtml. Now I’ll configure the server end of the connection, starting in Startup.cs. You need to modify the ConfigureServices method to include SignalR (typically after calling services.AddMvc):

services.AddSignalR();

Next, in the Configure method, you need to set up the SignalR routes, just before calling app.UseMvc:

app.UseSignalR(routes =>
{
  routes.MapHub<UsersHub>("/userUpdates");
});
app.UseMvc();

Now it’s time to add the UsersHub class that I referenced previously. You can put this class in a Hubs folder, which is a common approach, or you can think about using more of a feature-folder approach, as I described in my September 2016 article, “Feature Slices for ASP.NET Core MVC” (msdn.com/magazine/mt763233), and put the Hub with the Pages/Controllers with which it works. In any case, for this scenario, because the client isn’t making calls to the hub, the class doesn’t need any actual implementation. It just needs to inherit from Microsoft.AspNetCore.SignalR.Hub:

public class UsersHub : Hub
{
}

Finally, to use the Hub to communicate with connected clients from elsewhere in the app, you use dependency injection to inject IHubContext<THub> into the class that needs it. In this case, the PageModel classes for Login, Logout and Register each have an instance of IHubContext<UsersHub> injected into their constructors.

To send a message, use the HubContext instance to access its Clients property and send a message to a particular named handler. Figure 2 shows the implementation for the Logout PageModel OnPost method.

Figure 2 Implementing the Logout PageModel OnPost Method

public async Task<IActionResult> OnPost(string returnUrl = null)
{
  string username = User.Identity.Name;
  await _signInManager.SignOutAsync();
  _logger.LogInformation("User logged out.");
  await _usersHubContext.Clients.All.SendAsync("ReceiveSignOut", username);
  if (returnUrl != null)
  {
    return LocalRedirect(returnUrl);
  }
  else
  {
    return Page();
  }
}

With these pieces in place, you can run the application and open up several different browsers to display the app. In one, register and sign in and out of the app. You should see notifications appearing in the other browsers. The left half of Figure 3 shows notifications appearing in a Chrome browser, after a user has signed in and out of an Edge browser on the right.

ASP.NET Core SignalR Sending Notifications from Server to Connected Browsers
Figure 3 ASP.NET Core SignalR Sending Notifications from Server to Connected Browsers

Integration Test Improvements

ASP.NET Core has had great support for integration testing the full stack in-memory since 1.0. However, one thing that required some custom setup to achieve was configuring the TestServer with the appropriate content root path, so that it could properly locate resources like views within the Web application. ASP.NET Core 2.1 introduces a new type, WebApplicationFactory<T>, which makes it easier to create a TestServer and an HttpClient that connects to it. To use the factory within an Xunit test class, you implement the IClassFixture<WebApplicationFactory<Startup>> interface, where Startup is the entry point for the ASP.NET Core app you wish to test. Then, in the class constructor, you inject a WebApplicationFactory<Startup> and use its CreateClient method to get an instance of a client. In your tests, you can then use the client to make requests to the application and verify that the correct response is returned, like so:

[Fact]
public async Task Get_HomePageReturnSuccessAndCorrectContentType()
{
  var response = await _client.GetAsync("/");
  response.EnsureSuccessStatusCode(); // Status Code 200-299
  Assert.Equal("text/html; charset=utf-8",
    response.Content.Headers.ContentType.ToString());
}

If you need to customize the application for testing, such as changing which services are used or adding seed data that will be used for testing, you can inherit from WebApplicationFactory<T>, and then use your custom factory in your tests.

Additional Improvements

This release also sees improvements to several other parts of ASP.NET Core 2.1, including the built-in Kestrel server. Kestrel now uses managed sockets for its default transport layer, instead of libuv. This change should be seamless, but if it causes issues, developers can still configure libuv for use with Kestrel.

Another new addition to the hosting component of ASP.NET Core is that of the HostBuilder type, which you would use to configure non-Web parts of a host. HostBuilder is very similar to the existing WebHostBuilder, but doesn’t allow you to spec­ify a Startup class from a Web project. It’s designed to let you configure common concerns like dependency injection, configuration, and logging for non-Web scenarios like hosted services or console applications.

Finally, if you’re writing API endpoints in your ASP.NET Core app, you can take advantage of some improvements added in 2.1. First, you can add the [ApiController] attribute to any of your controllers that expose APIs. This adds a number of features to endpoints defined on these controllers, such as:

  • Model validation errors will automatically return BadRequest(ModelState)
  • Binding sources (such as [FromBody]) automatically inferred for action parameters
  • File uploads using [FromForm] automatically infer multipart/form-data content type
  • Attribute routing is required

Another addition is a new return type, ActionResult<T>. This return type is used in place of IActionResult and allows type information to be included in the method signature. Tools like Swashbuckle can use this information to generate OpenAPI/Swagger documentation. Without this return type, you would need to annotate methods that simply returned IActionResult with the [ProducesResponseType] attribute to expose this information.

Next Steps

If you’re not already using ASP.NET Core 2.1, I recommend upgrading as soon as possible. You can get the SDK at microsoft.com/net/download/windows or run the tools using Docker from one of the images at dockr.ly/2MAaiEF. The updated source code for this sample is available at bit.ly/2JXdHeV.


Steve Smith is an independent trainer, mentor and consultant. He blogs at ardalis.com and provides developer tips via e-mail and podcast at WeeklyDevTips.com. Check out his courses on Pluralsight to learn how to develop cleaner, higher quality applications. Follow him on Twitter: @ardalis.


Discuss this article in the MSDN Magazine forum