My First Look at ASP.NET Core 3.0

I’m finally getting around to looking at updating my examples and courses to 3.0. This post is based on .NET Core Preview 8 so this might change in the future.

There are a number of great walkthroughs for moving your ASP.NET Core 2.x projects to 3.0. So I don’t want to repeat that. Instead, I want to talk about what’s happening, not just a list of things to do.

To discuss these changes, i’ll talk about a project I’m current converting (CoreCodeCamp which is the basis for the Atlanta Code Camp), though it’s a branch that likely won’t be deployed until after the event.

First thing that you need to make this change is to move to Visual Studio 2019 Preview. But I’m going to focus on the source code changes.

Changes to CSProj file

There are really only two changes of interest here. The first one is the most obvious:


Targetting the new framework is pretty simple. The naming convention helps us just change 2.2 to 3.0.

The next one is puzzling:

    <!-- PackageReference Include="Microsoft.AspNetCore.App" / -->
    <!-- ... -->

In 3.0, you don’t need the magic meta-package anymore. The meta package is a concept that Microsoft introduced in 2.0 and worked well. The lack of this metapackage might be a part of the growing pains. But I have found that much of ASP.NET Core is already available without the meta-package, but some things need to be specifically added as Nuget packages including EntityFramework, Identity Framework, Jwt tokens, etc. I suspect they went too far with the meta-package and trimmed it down, but we’ll see on release.

That’s all really. You will need to update any other packages that are in the .NET Core framework to the 3.0 versions if you need.


This is where most of the interesting changes happen. I really haven’t had to make many changes at all in the majority of my code, just making some overall changes in startup.cs seemed to get me about 95% there for breaking changes. Let’s enumerate them by walking through the Startup class. First, the constructor:

  public class Startup
    IWebHostEnvironment _env;

    public Startup(IWebHostEnvironment env)
      _env = env;

You might not recognize this interface, but it’s a replacement for IHostingEnvironment. In fact, if you’re still using IHostingEnvironment, it’ll work but you’ll get a compilation error as it’s been marked obsolete and will be removed. It works like the old interface, but has a base interface for other types of host environments. It’s a simple, in-place replacement.

Next up, using Entity Framework Core:


This looks the same as previous versions but it’s not included as the default out of the box. So you’ll need to add a package reference to two Entity Framework Core. In addition, since I’m using Identity, i’ll also need those references too. Here are all the references I had to manually add (again, this might be more about the state of the preview than the expectation at release):

<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer"
                      Version="3.0.0-preview8.19405.11" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools"
        runtime; build; native; 
        contentfiles; analyzers; 
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore"
                      Version="3.0.0-preview8.19405.7" />

This assumes you’re using SqlServer of course. In addition, with these these references, finally the Entity Framework stuff in ConfigureServices just works. But we’re not done there yet. At the bottom of ConfigureServices, there is the call to add and configure MVC:

  svcs.AddMvc(opt =>
    if (_env.IsProduction())
      opt.Filters.Add(new RequireHttpsAttribute());
    opt.EnableEndpointRouting = false;

This still works, but note that I was turning off EnableEndpointRouting (mostly not to break some of the versioning code I’m using). But in changing the code, I want to opt into the new way to do this. First, instead of adding Mvc, what we really want to support is to enable using controllers and views so this changed to:

  // Could have used AddControllers() if I was just building APIs

We don’t need to turn off endpoint routing because we want to move towards this new plumbing model. We no longer need to add a filter for Https because that not really about services, we’ll do that in the middleware configuration below.

Now onto the Configure (where we’ll register middleware). This is where lots of changes happen. In my code, I just have two important pieces of middleware in the 2.2 project (there is more, but most of it is about error handling and that hasn’t changed):


UseAuthentication doesn’t change, but this doesn’t handle Authorization any longer, so you’ll need to add Authorization if you’re using Authorization (e.g. Authorize attribute or other code):

      app.UseAuthorization();  // <--

We’ll also need to handle the Https redirection here as middleware (where it belongs):

      app.UseHttpsRedirection() // <--

Since we’re using endpoint routing (the default now), we have to configure it without UseMvc (which only works without it):

      app.UseEndpoints(endpoints =>

Note that using endpoints allows you to configure what endpoints are supported. This allows you to have endpoints for a variety of sub-systems (e.g. MVC, SignalR, GRPC, Razor Pages, Blazor, etc.). It’s a more adaptable model.

Inside the configuration, i’m calling the same CreateRoutes method I was using. Of course, the old version used IRouteBuilder:

    // Old Version
    void CreateRoutes(IRouteBuilder routes)
        name: "Events",
        template: string.Concat("{moniker}/{controller=Root}/{action=Index}/{id?}")

        name: "Default",
        template: "{controller=Root}/{action=Index}/{id?}"


Registration of the routes hasn’t changed, but the interface is not IEndpointRoutingBuilder:

    void CreateRoutes(IEndpointRouteBuilder enpoints) // <--
      enpoints.MapControllerRoute( // <--

      enpoints.MapControllerRoute( // <--


The only change is that that the name of the parameter has been changed from template to pattern, but since it’s the first two parameters, it was easier to just remove the parameter names.

I also needed to register for attribute routing, so I added this to the UseEndpoints callback:

      app.UseEndpoints(endpoints =>
        endpoints.MapControllers(); // <--

I also show the mapping of Razor Pages since a lot of people use them, but we’re not in this project.

None of these changes make big change to the business logic I’ve written so once I made these changes, the project compiled and seemed to work.

The only other change I needed to make was to install the Entity Framework tools globally on my machine. In ASP.NET Core 3.0, it’s not installed by default or at the project level. To do this, open a console and type:

C:/>dotnet tool install --global dotnet-ef --version 3.0.0-*

Now you’ll be able to manage the tooling as you need it.

Let me know what you think about these changes (and any I missed that I’ve not run into yet)!