It seems like yesterday that Microsoft was announcing the early builds of .NET Core 1.0. Amazingly, we’re already at version 3. In this new version of ASP.NET Core, Microsoft releases some very forward-looking technologies to approach Web development in very different ways. There are now more ways than ever to create great looking Web properties in this newest version of ASP.NET.

If you’ve been paying attention to ASP.NET Core lately, you know that Microsoft has been expanding the ways to build your websites with .NET Core. This version is no different. In order to give us these choices, this version makes some fundamental changes to the underlying plumbing. In this article, I’ll talk about the new features in ASP.NET Core 3.0 as well as the changes to existing features. Let’s start with the new features.

If you’ve been using ASP.NET Core for a while, the biggest change is that .NET Core 3.0 doesn’t run on top of the .NET Framework (e.g., the desktop framework). This is a big change in philosophy that’s been some time coming. Microsoft has announced that .NET Core in general is where the innovation will be. There‘s a plan to converge .NET Core and .NET Framework in .NET 5.0 down the line. But for ASP.NET Core, the time is now.

This has been made possible by the work that the ASP.NET Core team has made in making sure that most of what developers really need is in .NET Core. If you last looked at .NET Core in the 1.x timeframe, look again. The amount of the complete framework that’s now available in .NET Core is really remarkable.

Of course, this isn’t a universal change. Some things aren’t coming. If you’re invested in ASP.NET WebPages or Windows Communication Foundation (WCF), you’re going to have to stay with .NET Framework because they’re not making the move.

The biggest change for previous users of ASP.NET Core is that ASP.NET Core won’t run on top of the .NET Framework. It’s only .NET Core from here on in.

What’s New

Before I delve into changes to .NET, let’s talk about what is new to the platform. Much of this has been available in previews and early bits for a long time and it’s now officially part of ASP.NET Core. With full support by Microsoft, you can now use much of this in your production applications. Let’s take them one-by-one.

Packaging of ASP.NET Core

In previous versions of ASP.NET Core, the separate pieces of ASP.NET Core were available as separate NuGet packages. .NET Core 3.0 changes that. ASP.NET Core is now an SDK, not a set of discernable libraries. Because of this, your csproj file no longer needs to target the metapackage, but does need to be using the Web SDK:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <!--<PackageReference Include="Microsoft.AspNetCore.App"
                      Version="2.2.6" />-->
  </ItemGroup>
</Project>

Note that the SDK specifies the Web SDK, the framework is .NET Core 3.0, and the meta package (e.g., Microsoft.AspNetCore.App) is no longer required.

Endpoints as the New Default

In ASP.NET Core 2.2, Microsoft opted you into a new routing system called Endpoint Routing. Unless you created a new project in 2.2, you probably didn’t even notice that you were using the old routing system.

The key to this new plumbing, which is now the default in 3.0, is that different routes could be aware of each other and not need to always be terminal middleware. For example, if you wanted to use SignalR and MVC, you’d have to be careful about the order of the middleware as nothing after MVC will get called.

Instead, Endpoint Routing takes the form of a shared space for all things that need routing. This means MVC, Razor Pages, Blazor, APIs, SignalR, and gRPC (as well as any third parties that need to use their own routing) can share the routing pipeline. It’s harder to have middleware bump into each other.

If you create a new project in ASP.NET Core 3.0, you’ll see the endpoint system in use in the new templates (in Startup.cs):

app.UseEndpoints(endpoints =>
{
  endpoints.MapControllerRoute(
            name: "default",
            pattern:
        "{controller=Home}/{action=Index}/{id?}");
  endpoints.MapRazorPages();
});

Instead of specifying MVC or Razor Pages as an extension of the IApplicationBuilder, you’ll wire this middleware in UseEndpoints callback instead. I’d suggest moving to this model in your applications that you upgrade.

An implication of this change is that the LinkGenerator class (and by implication the IUrlHelper interface) both use the entirety of the Endpoints to generate URLs. This means that you can generate URLs across the different sub-systems depending on the route values (e.g., you don’t have to differentiate MVC routes from Razor Pages routes any longer).

gRPC

Relatively new to most people is a new messaging system called gRPC (Google remote procedure call). This is a way to write services that are smaller and faster than the REST-based services that many of us have been using for some time. gRPC is based on work at Google and is an open source universally available remote procedure call system. It uses a language called Protocol Buffers to define the interface for a service (and the shapes of the messages):

// WeatherService.proto
syntax = "proto3";
option csharp_namespace = "WeatherMonitorService";
package WeatherService;
import "Protos/weatherMonitor.proto";
service MonitorService {
  rpc GetWeather (WeatherStatusRequest)
    returns (WeatherStatusResult);
}
message WeatherStatusRequest {
  bool allSites = 1;
  repeated int32 siteId = 2;
}
message WeatherStatusResult {
  repeated Monitoring.WeatherData results = 1;
}

Protocol Buffers (or ProtoBuf as it’s usually called) provides a contract-based way to create services somewhat similar to WCF. I think it’s probably where WCF-based projects that are moving to .NET Core will go.

Over the wire, gRPC uses ProtoBuf as a roadmap to a binary serialization. This means that serialization is faster and smaller. And it’s great for scenarios where JSON or XML serialization puts too much stress on a low resource system (e.g., IoT).

In addition, gRPC supports unidirectional and bi-directional streaming to open up more explicit support for scenarios where REST just isn’t a good fit. gRPC is designed for remote procedure calls but not remote objects or CRUD messaging. There are limitations.

gRPC relies on HTTP/2 to work. This means that it’s secure by default and uses the HTTP/2 streaming facilities to make that work. You’ll need HTTP/2 on both sides of the pipe to be actionable, but most Web servers (and .NET Core in general) support this.

These limitations mean that it’s not a good fit for Web development. Calling gRPC from browsers isn’t a good fit right now. There are workarounds but I’d stick with REST for browser-server communication. I see several scenarios that I think are important:

  • Service-to-Service: In a microservice architecture, communication between services is a great fit for gRPC. The support for streaming and synchronous communication fits well in conjunction with asynchronous communication (e.g., message bus/queues).
  • IoT-to-Service: When you’re dealing with small, resource-limited IoT; gRPC is a great fit for communication, especially when you don’t want to broadcast the existence of the service.
  • TCP/WCF Migration: Services using a WCF with TCP bindings will find the contract-based services in gRPC a natural fit for bringing these services to .NET Core.

When you develop clients for gRPC services, you can use "Add Service Reference…" in Visual Studio to generate the client for gRPC. This makes calling gRPC services that much easier.

Local Tools

In ASP.NET Core 2.2, the team enabled global tools in .NET Core. The obvious fit for this is the Entity Framework Core tools but lots of organizations have benefited from the installation of global tools to allow for the dotnet {toolname}-style usage.

Sometimes you need to create a tool that’s only used locally within your own project. This is where local tool command comes in. You can now install local tools using the dotnet tool command:

> dotnet tool install toolname

Tool installation is local by default (and adds the tool to the package.json file). You’ll need to add the -g flag to get the tool to install globally. If you’ve worked with NPM tooling, it’s a similar experience.

Other Improvements

There are some less than obvious changes that should be highlighted too:

  • Tool for generating REST Clients (similar to the way that gRPC client generation works)
  • New Worker Service (for creating Services that are cross-platform using the background worker).

Changes in ASP.NET Core

Although ASP.NET Core is pretty stable, there are some changes to how you wire up the plumbing for ASP.NET Core. ASP.NET Core 3.0 keeps most of the basic details the same, but in some cases, Microsoft moved your cheese.

In some cases, Microsoft moved your cheese.

MVC/Routing

As explained above, the new Endpoint routing means that you’ll probably want to move to the new routing system. They’ve also changed who you register for the services for APIs and for MVC with Views to be a little clearer:

public void ConfigureServices(
              IServiceCollection services)
{
  // ...
  // Add MVC with Views
  services.AddControllersWithViews();
  // Add Controllers without Views
  // (usually for APIs)
  services.AddControllers();
}

Note that instead of using AddMvc or AddMvcCore, you’ll use AddControllers to support MVC/API controllers and AddControllersWithViews when you want to use MVC and Razor to build your site.

When you need to add the middleware for this, you’ll need to add routing and controllers separately. For example:

public void Configure(IApplicationBuilder app,
                      IWebHostEnvironment env)
{
  // ...
  app.UseRouting();
  app.UseEndpoints(endpoints =>
  {
    endpoints.MapControllerRoute(
              name: "default",
              pattern:
      "{controller=Home}/{action=Index}/{id?}");
  });
}

Much like how you’d specify routes in UseMvc, you’ll now see it in MapControllerRoute instead. Notice that the nomenclature is changing a bit to controllers instead of MVC. If you’re using attributed routing, you’ll need to add support for them as well:

app.UseEndpoints(endpoints =>
{
  endpoints.MapControllers();
}

Razor Pages

Razor Pages haven’t changed a lot but their registration is a bit different. In the past, using AddMvc in the ConfigureServices added Controllers and Razor Pages, and now they’ve been separated:

public void ConfigureServices(
              IServiceCollection services)
{
  services.AddControllersWithViews();
  services.AddRazorPages();
}

It’s pretty typical that you’d need to add Controllers and Razor Pages separately. For the Configure method in Startup.cs, the separation exists there too. You’ll want to add it to your UseEndpoints:

app.UseEndpoints(endpoints =>
{
  endpoints.MapRazorPages();
});

Other than this plumbing, the changes in Razor Pages are beneath the surface and you can continue to write Razor Pages without any changes.

Changes to JSON Support

One goal of ASP.NET Core was to remove requirements for third-party libraries. The new approach is to allow third parties to easily integrate (as seen with announcements by Identity Server). One place that this is really obvious is that Microsoft has replaced JSON.NET with System.Text.Json. This new JSON support is built with better memory handling and C# 8 types. You’ll get this support by default, but if you’re using code that requires JSON.NET to exist, you’ll need to reference the Microsoft.AspNetCore.Mvc.NewtonsoftJson package from NuGet. This allows you to opt into JSON.NET support like so:

      services.AddControllersWithViews()
        .AddNewtonsoftJson();
      services.AddRazorPages()
        .AddNewtonsoftJson();

It works on Controllers and Razor Pages.

ASP.NET Core SignalR

In ASP.NET Core 3.0, make sure you’re using the Core version of SignalR but this hasn’t changed much from the 2.2 version. It’s a prevailing theme but the new Endpoint Routing changes how you wire up SignalR as well. So, you’ll still AddSignalR to the DI layer, but you’ll wire up your hubs via the Endpoint Routing:

app.UseEndpoints(endpoints =>
{
  endpoints.MapControllers();
  endpoints.MapHub<MyHub>();
}

If you’re using Blazor with SignalR, make sure you map the Blazor Hub separately:

app.UseEndpoints(endpoints =>
{
  endpoints.MapControllers();
  endpoints.MapHub<MyHub>()
  endpoints.MapBlazorHub<MyBlazorHub>();
}

Entity Framework

In ASP.NET Core, there are two options: Entity Framework Core 3.0 and Entity Framework 6. This might be a bit confusing but when porting applications already using Entity Framework 6, you don’t have to rewrite all code. Entity Framework Core is certainly the future and has a lot of benefits over EF6, but for compatibility with older code, EF6 is a great option (you can even use both in the same project if needed).

Entity Framework Core is improving on the prior version by adding several important features/improvements:

  • Reworked LINQ implementation is more robust and map better to SQL Queries
  • Introduced Cosmos DB support
  • You can opt into dependent types with the new [Owned] attribute.
  • Supports C# 8 types (including nullables)
  • Allows reverse engineering of database views as query types
  • Property bag entities are being introduced as a stepping-stone to many-to-many without join tables.

For most apps, you won’t notice a big difference, but being aware of the new features will help you take advantage of them.

When you install the ASP.NET Core SDK, Entity Framework Core isn’t part of that. You’ll need to make reference to the EFCore packages as well as registering the EFCore tooling. I usually install it globally on a new computer by simply adding this at the command-line:

> dotnet tool install dotnet-ef -g

IWebHostingEnvironment

The old IHostingEnvironment that most people used to access whichever environment they were using has been depreciated. Your ASP.NET Core 2.2 code might look like this:

public void Configure(IApplicationBuilder app,
                      IHostingEnvironment env)
{
  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
  }
}

Instead you should use IWebHostingEnvironment (instead of IHostingEnvironment) as a complete replacement. The reason this changed was that there are different kinds of hosts in ASP.NET Core (desktop apps for example). This was meant to be clearer. You can just replace it like so:

public void Configure(IApplicationBuilder app,
                      IWebHostEnvironment env)
{
  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
  }
}

The IHostEnvironment is the base interface for IWebHostingEnvironment and contains everything *but* the WebRoot information. Sometimes it’s better to ask for exactly what you need instead of the entire IWebHostingEnvironment, especially when building shared libraries.

Security in ASP.NET Core

Security, like many things in ASP.NET, Core offers another plumbing change, but this one is really minor. You have to opt into Authentication and Authorization separately:

public void Configure(IApplicationBuilder app,
                      IWebHostEnvironment env)
{
  app.UseRouting();
  app.UseAuthentication();
  app.UseAuthorization();
  app.UseEndpoints(endpoints =>
  {
    endpoints.MapControllers();
    endpoints.MapRazorPages();
  });
}

Just add UseAuthentication and UseAuthorization as separate middleware for each need.

If you’re using JSON Web Tokens, you’ll need to include a separate NuGet package for that: Microsoft.AspNetCore.Authentication.JwtBearer. Wiring up the services is the same except for what I mentioned earlier.

ASP.NET Core 3.0 also has some smaller features including:

  • Newly added AES-GCM and AES-CCM cyphers
  • HTTP/2 support
  • TLS 1.3 & OpenSSL 1.1.1 on Linux
  • Import/Export asymmetrical keys without need for an X.509 certificate

The support for CORS has been simplified as well. If you need to create a CORS policy, you can do this during adding CORS to your DI layer:

services.AddCors(opt =>
{
  opt.AddDefaultPolicy(bldr =>
  {
    bldr.WithOrigins("https://www.microsoft.com")
    .AllowAnyMethod()
    .AllowAnyHeader();
  });
});

You can have named policies as well, but to enable them, you still need to add support for CORS as middleware:

public void Configure(IApplicationBuilder app,
                      IWebHostEnvironment env)
{
  app.UseStaticFiles();
  app.UseRouting();
  app.UseCors();
  app.UseAuthentication();
  app.UseAuthorization();
}

Building/Publishing Changes

The last piece of changes that I think are important to highlight is how changes to building and publishing will affect you. Changes include:

  • Executable builds are now the default (they used to be only on platform independent builds).
  • Publish can build single-file executables (where they link all the framework and third-party into a single file to be published).
  • You can opt into trimmed IL so that it removes all libraries that aren’t necessarily in your publishing.
  • You can also opt into Ready2Run(R2R) images, which means they’re ahead-of-time (AOT) compiled.
  • The memory footprint of apps has been reduced by half.

Where Are We?

As with any major release, changes can be uncomfortable. But I’m happy to say, the cheese that’s been moved is pretty minor. I found that working with the new versions, the benefits seem to all be under the covers. That means that you’ll be able to better support your customers and developers without a lot of trouble.

If you’ve been waiting for some of these new features, they’re good to go. By looking at gRPC, Blazor, and other features…you’ll be setting yourself up to be future-proof and to build better solutions for your clients.