Stackify is now BMC. Read theBlog

What’s New in .NET Core 2.1

By: Ricardo
  |  May 27, 2024
What’s New in .NET Core 2.1

NET Core 2.1 was officially released on May 30. I will summarize what’s new for all its parts – .NET Core itself, Entity Framework Core, and ASP.NET Core. You can also check out our article on the .NET Ecosystem to fully understand your options before you start your next project.

.NET Core 2.1

First, you will need either Visual Studio 2017 15.7 (or higher), Visual Studio Code, or Visual Studio for Mac in order to fully leverage this version. Docker images have been published at the Microsoft Docker repo. It is important that you upgrade since .NET Core 2.0 will reach end of life for Microsoft support in October 2018. There are no breaking changes, only a couple of useful additions.

Global tools

One of these additions is global tools. Before, you could create extensions for the dotnet command – such as Entity Framework scaffolding, for example – but these could only run in the context of a folder where their binaries where installed. This is no longer the case, global tools can be published as NuGet packages and installed globally on the machine as easy as this:

dotnet tool install -g SomeTool

You can specify an installation path, but that may likely be rarely used. There is no base class or whatever, a global tool is just a program with a Main method that runs. Do remember, though, that global tools are usually added to your path and run in full trust. Please do not install .NET Core global tools unless you trust the author!

Finally, the watch, dev-certs, user-secrets, sql-cache and ef tools have been converted to global tools, so you will no longer need to used DotNetCliReferenceTool in your .csproj file. I bet this will be handy for you, Entity Framework Core migrations users!

.NET Core 2.1 Performance

One of the biggest highlights was performance, both build-time and execution performance. There is an ongoing Microsoft initiative that aims to squeeze every bit of lag from the code. The following chart shows build-time improvements of 2.1 in relation to 2.0, for two typical web applications, a small and a large one:

.NET Core 2.1 Incremental Build-time performance improvements

Image taken from, please refer to this page to learn about the actual details.

Runtime performance improvement occurs in many different areas and is hard to get a single value, but these have benefited a great deal:

  • devirtualization, where the JIT compiler is able to statically determine the target for virtual method invocations; this affects collections a lot
  • optimization of boxing, avoiding, in some cases, allocating objects in the heap at all;
  • reducing lock contention in the case of low-level threading primitives, and also the number of allocations
  • reducing allocations by introducing new APIs that don’t need them such as Span<T> and Memory<T>, and changing existing APIs to support these; the String class, for example, yields much better performance in typical scenarios
  • networking was also optimized, both low-level (IPAddress) and high-level (HttpClient); some of the improvements also had to do with reducing allocations
  • file system enumeration
  • operating system-specific operations, such as Guid generation

In general, the Just In Time (JIT) compiler is much smarter now and can optimize common scenarios, and a new set of APIs provides much more efficient resource usage then before, mostly by reducing allocations.

HttpClient and friends

HttpClient got a whole-new implementation based on .NET sockets and Span<T>. It also got a new factory class that assists in creating pre-configured instances that plays nicely with dependency injection:

public void ConfigureServices(IServiceCollection services)
    services.AddHttpClient("MyAPI", client =>
        client.BaseAddress = new Uri("");
        client.DefaultRequestHeaders.Add("Accept", "application/json");

public class MyController : Controller
    private readonly HttpClient _client;

    public MyController(IHttpClientFactory factory)
        _client = factory.CreateClient("MyAPI");

Span<T> and Memory<T>

These classes are used to represent contiguous memory chunks, without copying them. By contiguous memory, I mean arrays, pointers to unmanaged memory or stack-allocated memory. Classes such as String, Stream and others now offer methods that work with these new types in a more efficient way, without making copies, and allowing the slicing of it. The difference between Span<T> and Memory<T> is that the former needs to be declared on the stack (in a struct), but not the contents it points to (of course!).

var array = new byte[10];
Span bytes = array;
Span slicedBytes = bytes.Slice(5, 2);
slicedBytes[0] = 0;


string str = "hello, world";
ReadOnlySpan slicedString = str.AsReadSpan().Slice(0, 5);

Neither of these calls (creation of Span<T> or ReadOnlySpan<T>) allocates any memory on the heap. Both these types have cast operators to and from arrays of generic types, so they can be directly assigned and converted.

Entity Framework Core 2.1

EF Core finally got some of its most demanded features:

CosmosDB provider

The first NoSQL provider for EF Core that is made available by Microsoft. Still in preview, but you can already use it in most cases, to access your CosmosDB (formerly Azure DocumentDB) databases.

Lazy loading

Not a big fan myself, but, hey, it’s here, and in an extensible way, which means developers can provide more efficient implementations. For those unaware, it means that properties that point to related entities (one-to-one, one-to-many, many-to-one) can be loaded only if they are actually needed, meaning, if any code accesses them:

public class Document
    public int Id { get; set; }
    public string Title { get; set; }
    public virtual Author Author { get; set; } // not loaded by default

Document doc = ...;
var author = doc.Author; //loaded here

Alternatively, it can be eagerly loaded with the rest of the entities:

var docs = ctx.Documents.Include(x => x.Author).ToList();

Server-side grouping

LINQ’s GroupBy now can run on the database. I stress can because actually not all scenarios work, but at least we get warnings about that if we look at the logger output. When it works, it is a big saver, because in the past data had to be brought to the client and processed there. It goes like this:

var jacketsBySize = ctx.Jackets.GroupBy(x => x.Size).Select(x => new { Size = x.Key, Count = x.Count() }).ToList();

The problem is, it’s not fully implemented, namely, we can’t group (yet) by a property of a reference property, like this:

var jacketsByBrand = ctx.Jackets.GroupBy(x => x.Brand.BrandId).Select(x => new { BrandId = x.Key, Count = x.Count() }).ToList();

Mind you, this will work, but on the client-side, meaning, EF will bring all the data into the client and then perform the grouping in memory – not something you would generally want!

Constructor injection

Entities instantiated by EF Core can now take parameters in their constructors, that is, no need for public parameterless constructors in entities. These parameters can either be property values or services from the dependency injection tool, although I would recommend you keep your entities unaware of these services, in general. This works now:

public class Product
    public int ProductId { get; }
    public string Name { get; }

    public Product(int productId, string name)
        this.ProductId = productId;
        this.Name = name;

This is useful if you wish to have read-only properties, for example.

Value conversions

Another popular one is value conversions. With this feature you can, for example, specify how an enumeration should be stored (as its string representation or as its underlying type, typically int), or that a Binary Large Object (BLOB) in the database is actually an Image, or even store encrypted values in the database. For example, to store enumerations as strings, use this:

protected override void OnModelCreating(ModelBuilder modelBuilder)
        .Property(e => e.State)
        .HasConversion(v => v.ToString(), v => (State) Enum.Parse(typeof(State), v));

Notice how we need to supply both direction conversions – the ToString() call in the HasConversion is the value to store in the database, and the Enum.Parse is to convert the value from the database.

Data seeding

This one is back from the pre-Core days, although in a slightly different format. Initial data is configured when the model is defined, either in OnModelCreating or when registering a DbContext in the dependency injector:

modelBuilder.Entity().HasData(new Product { ProductId = 1, Name = "Jacket" });

You can pass any number of parameters in the HasData call.

Query types

Query types mean that you can instantiate your queries into any classes of your liking automatically, not just known entity classes. Instances generated this way have no primary key properties and are not change-tracked or persistable:

protected override void OnModelCreating(ModelBuilder modelBuilder)

Here, the vJacketSize view is populated using a SQL GROUP BY query that returns the count of jackets per size. Another option is to use a LINQ query:

    .ToQuery(() => this.Jackets.GroupBy(x => x.Size).Select(x => new JacketSize { Size = x.Key, Count = x.Count() }));

To query, one must use the Query method instead of Set:

var jacketsAndSizes = ctx.Query<JacketSize>().ToList();

Ambient transactions support

Very popular too, EF Core can now automatically participate in ambient transactions such as those created by TransactionScope, of course, for providers that support it, like SQL Server:

using (new TransactionScope())
    //do modifications

    //do more modifications

Owned entity attribute

Owned entities are not new, they were introduced in EF Core 2.0, but then they had to be manually configured:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    modelBuilder.Entity().OwnsOne(x => x.Product);

Think of an owned entity as a value type in Domain Driven Design terminology. It is similar to an entity but does not have an id and you can declare properties of owned entities, thereby making reuse easier.

Now we have the [Owned] attribute that is used for just that. Just add it to your owned class:

public class User
    public Address HomeAddress { get; set; }
    public Address WorkAddress { get; set; }

public class Address
    public string City { get; set; }
    public string PostCode { get; set; }
    public string Street { get; set; }

Include for derived types

You may be aware that EF Core 2.1 (as before) supports Single Table Inheritance / Table Per Class Hierarchy mapping. This means that a single table can be used to persist values for a class hierarchy, a column will be used to tell the specific class of each record. Sometimes, a relation of one-to-one or many-to-one may point to an abstract base class, but obviously what will be stored is a concrete class instance. If we want to eager load it, before 2.1, we had no luck, but, fortunately, we have it now:

var admins = ctx.User.Include(u => ((Admin) u).Privileges).ToList();

The syntax is a bit awkward, but it works!

ASP.NET Core 2.1

Big changes coming with this version:


SignalR is finally released for ASP.NET Core. In case you don’t know about it, it’s a real-time library that permits communication from the server to the client and it just works in almost any browser out there, including mobile ones. Try it to believe it!

Razor Class Libraries

It is now possible to deploy .cshtml files in assemblies, which means it can also be deployed to NuGet. Very handy, and is the basis for the next feature.

Razor pages improvements

Razor Pages, introduced in version 2.0, now support areas and shared folders.

New partial tag helper

Essentially it is a new syntax to render partial views.

Identity UI library and scaffolding

The ASP.NET Core Identity library for authentication brings along its own user interface, which, starting with ASP.NET Core 2.1, is deployed on the same NuGet package as included .cshtml files. What this means is that you can select the parts you want of it, and provide your own UI for the others. Visual Studio 2017 now knows about this and will guide you through the process, when you add support for Identity.

Virtual authentication schemes

You can now mix different authentication providers, like bearer tokens and cookies, in the same app, very easily. Virtual here means that you can specify a moniker name, and then deal with it in the way you exactly want.

HTTPS by default

What’s there to say? It’s here by default, together with HTTP, which you can now probably disable. This is actually pretty good as it forces you to use HTTPS from the start, thereby avoiding typical pitfalls that arrive at deployment to production time.

GDPR-related template changes

For sites generated using the built-in template, a new GDPR-compliant cookie-consent message is displayed when one accesses the site for the first time. This message is configurable, of course. There’s also support for specifying cookies that are needed by the infrastructure and those that it can live without (this is an API change).

MVC functional test improvements

There’s a NuGet package called Microsoft.AspNetCore.Mvc.Testing that contains the infrastructure classes to perform functional tests of your MVC apps. These tests are different from unit tests, for example, because you actually test your classes in pretty much the same way as if they were running in a web app, this includes filters, model binding, and all that. Now, this package was already available previously, but now you no longer need to write some boilerplate code to allow your tests to locate the view files. It relies on convention and some magic to automatically find these, and makes your tests much simpler to write.

API conventions

In the old days, Swagger, now called OpenAPI, was a standard to define REST API endpoints. Using it you could describe your web methods, their acceptable HTTP verbs, content types, and return structures. This is useful if we wish to use UI tools for generating test requests or for generating client proxies for our web APIs. The new [ApiController] attribute, when applied to your controller classes, causes a couple things to occur:

  • Automatic model validation, using the registered validator (by default, using Data Annotations)
  • [FromBody] will be the default for non-GET requests, for complex types, [FromRoute] and [FromQuery] will be used in sequence for any other parameters, and also [FromFile], if you have parameters of type IFormFile
  • ApiExplorer will know very early about your controllers, which means you can also apply conventions or otherwise make changes to them

All of these can be controlled by configuration:

services.Configure(options =>
    options.SuppressModelStateInvalidFilter = true;
    options.SuppressConsumesConstraintForFormFileParameters = true;

Additionally, it is common for developers to just declare action methods as returning IActionResult, but unfortunately, this is quite opaque to OpenAPI, as it doesn’t say anything at all about what result we can expect from it. Now we have the ActionResult<T> type, which makes the usage of [Produces] attribute unnecessary. This class has a bit of magic, in particular, it does not implement IActionResult, but IConvertToActionResult instead. Don’t worry too much about it, it just works!

Generic host builder

A host is what runs your web app. In this version, a new HostBuilder class was introduced that allows you to configure the many aspects of your app, from the dependency injection, logging and configuration provider, from the very start. This will make your Startup class much leaner. It will also allow non-HTTP scenarios, because this is not tied to web/HTTP in any way. Before this we had WebHostBuilder, and as of now we still have, but in the future HostBuilder will eventually supersede it.

Updated SPA templates

New Angular, React and Redux templates are now available that are GDPR-friendly.

ASP.NET Core module

The ASP.NET Core module is what Internet Information Services (IIS) uses to process requests for .NET Core applications in Windows. Its performance has been improved roughly 6x as it runs .NET Core in-process, avoiding proxying. Its usage is transparent to the developer, and, of course, does not apply if you’re working with non-Windows operating systems.

Migrating to .NET Core 2.1

It’s as simple as updating the TargetFramework property of the .csproj files to contain netcoreapp2.1 instead of netcoreapp2.0 and replacing any references to Microsoft.AspNetCore.All for Microsoft.AspNetCore.App. It is also safe to remove any references to DotNetCliToolReference, as it was replaced by global tools. Of course, when Visual Studio asks you to update the NuGet packages of your solution, you should do it to use the latest features.


This new version brings lots of interesting features to the .NET Core world. Entity Framework Core seems to be moving fast and will hopefully reach the level of maturity of pre-Core versions pretty soon. ASP.NET Core continues to include more and more features on a steady pace too. It is good to see Microsoft addressing performance in a very serious manner and the results seem most impressive.

Stackify’s Application Peformance Management tool, Retrace keeps .NET Core applications running smoothly with APM, server health metrics, and error log integration.  Download your free two week trial today!


Improve Your Code with Retrace APM

Stackify's APM tools are used by thousands of .NET, Java, PHP, Node.js, Python, & Ruby developers all over the world.
Explore Retrace's product features to learn more.

Learn More

Want to contribute to the Stackify blog?

If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]