BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News .NET 5 Breaking Changes for ASP.NET Core

.NET 5 Breaking Changes for ASP.NET Core

This item in japanese

Bookmarks

In part 3 of our .NET 5 Breaking Changes series, we look at ASP.NET Core. Previous reports covered the Base Class Library and historic technologies.

Serialization

Numbers that appear as quoted strings can now be deserialized into numeric properties as if they were normal JSON numbers. Previously this would have thrown a JsonException.

Replaced Packages

Various packages have been renamed or replaced. This list summarizes the majority of these packages and their replacements.

  • AzureAD.UI => Microsoft.Identity.Web
  • AzureADB2C.UI APIs => Microsoft.Identity.Web
  • Microsoft.AspNetCore.DataProtection.AzureKeyVault => Azure.Extensions.AspNetCore.DataProtection.Keys
  • Microsoft.AspNetCore.DataProtection.AzureStorage => Azure.Extensions.AspNetCore.DataProtection.Blobs
  • Microsoft.Extensions.Configuration.AzureKeyVault => Azure.Extensions.AspNetCore.Configuration.Secrets

Blazor Changes for Performance

Insignificant whitespace will be trimmed from .razor files at compile time. This can easily result in the elimination of hundreds of whitespace nodes that consume memory and network bandwidth despite not affecting the rendered HTML. In Microsoft’s testing, whitespace nodes represented up to 40% of the rendering time in benchmarks.

In the RenderTreeFrame struct, the various readonly public fields have been replaced with various readonly public properties in order to “implement high-impact performance improvements in Blazor component rendering”. This change is binary-breaking but not source-breaking. Which means code will not have to be altered, but it will need to be recompiled, in order to work with the new version. For most developers, this is a non-issue.

For library authors supporting both .NET Core 3.x and .NET 5, you will need to use a conditional Microsoft.AspNetCore.Components package reference to account for both versions. Microsoft provides this sample:

<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0" Condition="'$(TargetFramework)' == 'netstandard2.0'" />

<PackageReference Include="Microsoft.AspNetCore.Components" Version="5.0.0-rc.1.*" Condition="'$(TargetFramework)' != 'netstandard2.0'" />

This illustrates why adhering to the .NET Framework Design Guidelines continues to be important. Had Microsoft followed the Field Design rules, a breaking change would not have been necessary.

Blazor Drops Support for IE and Edge Legacy

Blazor in .NET 5 requires additional functionality that isn’t available in older browsers. As such, Internet Explorer will not be supported at all. Previously, IE 11 could access Blazor-based websites via polyfills and asm.js. But according to Daniel Roth it had “lots of issues and the experience wasn't great” so Microsoft dropped the asm.js route and focused on WebAssembly.

Edge Legacy is likewise not supported by Blazor with .NET 5. No specific reason was given, but presumably it was deemed unnecessary because this browser will no longer be supported as of March 9, 2021. Edge Legacy was officially replaced by a Chromium-based version of Edge in January 2020.

HttpClient Logging

Sometimes rather minor changes can have significant impacts on a project. One such case is how HttpClient instances created by IHttpClientFactory log integer status codes. In .NET Core, logs were written with HTTP status code names. For example:

Received HTTP response after 56.0044ms - OK
End processing HTTP request after 70.0862ms – OK

In order to be more consistent with other parts of .NET, this was changed to use HTTP status code integers.

Received HTTP response after 56.0044ms - 200
End processing HTTP request after 70.0862ms - 200

The purpose of the change was to remove the inconsistency that made it “difficult to query via structured logging systems such as Elasticsearch”. But if your monitoring tools were already designed to look for status code names, this could break alerts.

If the old behavior is necessary, Microsoft recommends you obtain the source code for the .NET Core 3.1 version of four logging classes and incorporate them into your project. This may be the first time Microsoft used open source to address a backwards compatibility issue.

BadHttpRequestException Was Replaced with BadHttpRequestException

This odd sounding change is the result of .NET having three BadHttpRequestException classes. Their full names are Microsoft.AspNetCore.Server.Kestrel.BadHttpRequestException, Microsoft.AspNetCore.Server.IIS.BadHttpRequestException, and Microsoft.AspNetCore.Http.BadHttpRequestException. As they all mean the same thing, this was considered to be redundant.

Moving forward, the Http version is the officially sanctioned class. The other two have been marked as obsolete. In order to preserve some backwards compatibility, they will also inherit from the Http version.

HttpSys Certificate Renegotiation Disabled

In order to improve performance, prevent deadlocks, and maintain HTTP/2 compatibility, HttpSys will default to not renegotiate client certificates. Which this can be re-enabled, Microsoft recommends that developers do not do so for the following reasons:

  • Are a TLS feature, not an HTTP feature.
  • Are negotiated per-connection and must be negotiated at the start of the connection before any HTTP data is available. At the start of the connection, only the Server Name Indication (SNI) is known. The client and server certificates are negotiated prior to the first request on a connection and requests generally aren't able to renegotiate.
  • In HTTP/1.1, renegotiating during a POST request could cause a deadlock where the request body filled up the TCP window and the renegotiation packets can't be received.
  • HTTP/2 explicitly prohibits renegotiation.
  • TLS 1.3 has removed support for renegotiation.

Kestrel: Default TLS Protocol Versions Changed

Instead of having a hard-coded list of supported TLS protocols, Kestrel will now defer to the operating system. This means if a flaw in an existing protocol is found, it can be disabled via an OS setting or patch without changing the application. For a list of currently supported protocols on Windows, see Ensuring support for TLS 1.2 across deployed operating systems.

This behavior can be explicitly overridden by setting HttpsOptions.SslProtocols value in the Kestrel startup options.

Pubternal APIs Removed

If the term “pubternal” is unfamiliar to you, you’re not alone. This unusual word refers to APIs which are marked as public, but are in a namespace that implies they were meant to be internal to the library. A couple examples of this in ASP.NET Core are:

  • Microsoft.Extensions.Localization.Internal.AssemblyWrapper
  • Microsoft.Extensions.Localization.Internal.IResourceStringProvider

These classes were originally marked as public so Microsoft could use them as “extension points for the team's internal testing”. As .NET already provides better ways to handle this such as the InternalsVisibleTo attribute, this should not have been done. So, to avoid future problems with non-Microsoft developers using these classes, they have been marked as internal.

MVC: Objectmodelvalidator Calls a New Overload of Validationvisitor.Validate

Usually adding new methods to a class does not result in a breaking change. But in the case of ValidationVisitor.Validate, things are a bit more complicated.

The original signature for this method was:

public virtual bool Validate(ModelMetadata metadata, string key, object model, bool alwaysValidateAtTopLevel)

As a virtual method, developers can overload it to perform custom validation logic. This logic would then be invoked by MVC’s ObjectModelValidator.

In .NET 5, a new virtual overload was introduced and the original was redirected to call it.

public virtual bool Validate(ModelMetadata metadata, string key, object model, bool alwaysValidateAtTopLevel)
=> Validate(metadata, key, model, alwaysValidateAtTopLevel, container: null);

public virtual bool Validate(ModelMetadata metadata, string key, object model, bool alwaysValidateAtTopLevel, object container)

If these were not virtual methods, there wouldn’t have been a problem. Developers simply wouldn’t have known the new overload was being called. Unfortunately, that’s not the case here.

If a developer overrides the original Validate method, then in .NET 5 that override would be ignored. The code would still compile, but the ObjectModelValidator will call the new overload instead and things would simply stop working correctly without any explanation.

The mitigating factor here is it doesn’t appear anyone has actually overridden this method other than FluentValidation. In both discussion threads concerning the impact of this change, there has been no response from the community. This implies the method should have never been marked virtual in the first place.

Cookie Name Encoding Removed

In the HTTP standard, cookie names were a limited subset of ASCII characters. As a convenience to developers, many frameworks including ASP.NET Core would allow additional characters to be included and would simply encode them. This capability has been removed due to the risk it poses.

An issue was discovered in multiple web frameworks where this encoding/decoding could allow an attacker to bypass a security feature called cookie prefixes by spoofing the reserved prefixes like __Host- with encoded values like __%48ost-. This attack requires a secondary exploit in order to inject the spoofed cookies, such as an XSS vulnerability in the web site. These prefixes are not used by default in ASP.NET Core or Microsoft.Owin libraries or templates.

In .NET 5, cookie name encoding/decoding will no longer occur. If a non-ASCII character is placed in the name, an exception can be thrown. The cookie values will continue to be encoded/decoded as usual.

SignalR: MessagePack Hub Protocol Now Uses MessagePackSerializerOptions

Previously, SignalR used the MessagePack 1 to handle message serialization. With .NET 5, it was updated to use MessagePack 2. Since MessagePack 2 had a breaking change to switch from IFormatterResolver to MessagePackSerializerOptions, SignalR needed to make a similar change in how it handles its configuration.

This change only affects those who modify the MessagePackHubProtocolOptions for SignalR.

SignalR Now Requires Endpoint Routing

Endpoint routing as we now know it has only been around since ASP.NET Core 3. In his article titled Understanding ASP.NET Core Endpoint Routing, Areg Sarkissian why it was introduced:

Prior to endpoint routing the routing resolution for an ASP.NET Core application was done in the ASP.NET Core MVC middleware at the end of the HTTP request processing pipeline. This meant that route information, such as what controller action would be executed, was not available to middleware that processed the request before the MVC middleware in the middleware pipeline.

It is particularly useful to have this route information available for example in a CORS or authorization middleware to use the information as a factor in the authorization process.

Endpoint routing also allows us to decouple the route matching logic from the MVC middleware, moving it into its own middleware. It allows the MVC middleware to focus on its responsibility of dispatching the request to the particular controller action method that is resolved by the endpoint routing middleware.

At that time, SignalR added the capability to use endpoint routing, but it was optional. The older, custom routing mechanism was still available though marked as obsolete. With .NET 5, the old method has been removed entirely and endpoint must be used.

Fortunately, the physical change to the code is very minor:

app.UseSignalR(routes =>
    {
        routes.MapHub<SomeHub>("/path");
    });

becomes

app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<SomeHub>("/path");
    });

Static Files: CSV Content Type Changed to Standards-Compliant

The final change we’ll discuss today is standards compliance for CSV files. In a strange oversight, previous versions of ASP.NET Core serve static CSV files as “application/octet-stream” instead of “text/csv”. This may affect how browsers interact with the files, necessitating developers to use a FileExtensionContentTypeProvider to override the behavior.

In the unlikely event developers using ASP.NET Core 5 need the old behavior, they can use FileExtensionContentTypeProvider to change back to application/octet-stream.

In part 4 we’ll look at the GUI frameworks WPF and Windows Forms.

Rate this Article

Adoption
Style

BT