This file/repository is aimed to group all the breaking changes and new features that got introduced in version 3.0 for a quick read/search.
- ASP.NET Core 3.0 and Blazor
- Table of conten$$t
- SignalR
- ASP.NET Core
- System.IO.Pipelines on HttpContext
- Generic host templates
- Endpoint Routing
- Worker Service
- gRPC
- AddMvc() was split
- New JSON Serialization
- Certificate and Kerberos authentication
- EventCounters
- Razor Pages support for
@attribute
- New networking primitives for non-HTTP Servers
- Unix domain socket support for the Kestrel Sockets transport
- Blazor
- Culture aware data binding
- Case-sensitive component binding
- Accepting arbitrary parameters
- Authentication & Authorization
- Static assets
- Forms & Validation
- Razor Components in Razor Class Libraries
- Prerendering
- Reconnecting in Blazor (server-side)
- [Render stateful interactive components from Razor pages and views](#render-stateful-interactive-components-from-razor-pages-and-view
- Breaking Changes
- ASP.NET Core 3.0 will only run on .NET Core
Microsoft.AspNetCore.All
has been deprecatedMicrosoft.AspNetCore.App
is now a FrameworkReference- The MvcPrecompilation tool has been deprecated
Microsoft.AspNetCore.Server.Kestrel.Https
was removed- Obselete Session APIs removed
- Authentication changes
AllowSynchronousIO
is now set to false by default- Runtime complication of Razor Views/Pages is not available by default anymore
- IHostingEnvironment's and IApplicationLifetime's marked obsolete and replaced
ResourceManagerWithCultureStringLocalizer
class andWithCulture
interface member are now obselete- Generic Host only supports specific constructor injections in Startup
- Changes to ResponseCaching
UseSignalR
andUseConnections
are now obseleteDefaultHttpContext
is not extensible anymore- AspNetCoreModule V1 removed from Windows Hosting Bundle
In Preview2, a new feature has been added to SignalR which gives the ability for the server to listen to streams.
Example (source)
Server (Hub):
public async Task StartStream(string streamName, ChannelReader<string> streamContent)
{
// read from and process stream items
while (await streamContent.WaitToReadAsync(Context.ConnectionAborted))
{
while (streamContent.TryRead(out var content))
{
// process content
}
}
}
Client:
let subject = new signalR.Subject();
await connection.send("StartStream", "MyAsciiArtStream", subject);
subject.next("example");
subject.complete();
For more information, please check the documentation page.
We added long polling support to the Java client which enables it to establish connections even in environments that do not support WebSockets. This also gives you the ability to specifically select the long polling transport in your client apps.
SignalR now provides a custom resource to authorization handlers when a hub method requires authorization. The resource is an instance of HubInvocationContext. The HubInvocationContext includes the HubCallerContext, the name of the hub method being invoked, and the arguments to the hub method.
Example source
public class DomainRestrictedRequirement :
AuthorizationHandler<DomainRestrictedRequirement, HubInvocationContext>,
IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
DomainRestrictedRequirement requirement,
HubInvocationContext resource)
{
if (IsUserAllowedToDoThis(resource.HubMethodName, context.User.Identity.Name) &&
context.User != null &&
context.User.Identity != null &&
context.User.Identity.Name.EndsWith("@jabbr.net", StringComparison.OrdinalIgnoreCase))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
private bool IsUserAllowedToDoThis(string hubMethodName,
string currentUsername)
{
return !(currentUsername.Equals("bob42@jabbr.net", StringComparison.OrdinalIgnoreCase) &&
hubMethodName.Equals("banUser", StringComparison.OrdinalIgnoreCase));
}
}
[Authorize]
public class ChatHub : Hub
{
public void SendMessage(string message)
{
}
[Authorize("DomainRestricted")]
public void BanUser(string username)
{
}
[Authorize("DomainRestricted")]
public void ViewUserHistory(string username)
{
}
}
// Startup.cs
services
.AddAuthorization(options =>
{
options.AddPolicy("DomainRestricted", policy =>
{
policy.Requirements.Add(new DomainRestrictedRequirement());
});
});
The body request and response pipes on the HttpContext
are now exposed to users looking to write high performance code.
These pipes are using the new System.IO.Pipelines API.
Example (source)
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting(routes =>
{
routes.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World");
});
routes.MapPost("/", async context =>
{
while (true)
{
var result = await context.Request.BodyPipe.ReadAsync();
var buffer = result.Buffer;
if (result.IsCompleted)
{
break;
}
context.Request.BodyPipe.AdvanceTo(buffer.End);
}
});
});
}
ASP.NET Core now uses the Generic Host by default.
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Note that this comes with a breaking change.
Quoting Daniel from this blog post:
Endpoint Routing is made up of the pair of middleware created by app.UseRouting() and app.UseEndpoints(). app.UseRouting() marks the position in the middleware pipeline where a routing decision is made – where an endpoint is selected. app.UseEndpoints() marks the position in the middleware pipeline where the selected endpoint is executed. Middleware that run in between these can see the selected endpoint (if any) or can select a different endpoint.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Middleware that run before routing. Usually the following appear here:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
// Runs matching. An endpoint is selected and set on the HttpContext if a match is found.
app.UseRouting();
// Middleware that run after routing occurs. Usually the following appear here:
app.UseAuthentication();
app.UseAuthorization();
app.UseCors();
// These middleware can take different actions based on the endpoint.
// Executes the endpoint that was selected by routing.
app.UseEndpoints(endpoints =>
{
// Mapping of endpoints goes here:
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapHub<MyChatHub>();
endpoints.MapGrpcService<MyCalculatorService>();
});
// Middleware here will only run if nothing was matched.
}
ASP.NET Core adds a new template for worker services for long running tasks such as Windows Services or Linux Systemd daemons.
A new project template has been added for users interested in creating gRPC servers, which use HTTP/2 for transport and protocol buffers, and supports HTTPs by default.
A package Grpc.Net.ClientFactory
is available for developers interested in a gRPC Client Factory. It's intented to use in scenarios that don't include ASP.NET Core.
The package also gives the possibility to include interceptors.
services
.AddGrpcClient<GreeterClient>(options =>
{
options.BaseAddress = new Uri("https://localhost:5001");
})
.AddInterceptor<CallbackInterceptor>();
gRPC also supports CallCredentials
, allowing for interoperability with existing libraries like Grpc.Auth
that rely on CallCredentials
.
Links:
In order to give developpers more option in what they include in their projects, AddMvc
was split into 3 top level extension methods:
- Controllers
- Model Binding
- API Explorer (OpenAPI integration)
- Authorization [Authorize]
- CORS [EnableCors]
- Data Annotations validation [Required]
- Formatter Mappings (translate a file-extension to a content-type)
- Controllers
- Model Binding
- API Explorer (OpenAPI integration)
- Authorization [Authorize]
- CORS [EnableCors]
- Data Annotations validation [Required]
- Formatter Mappings (translate a file-extension to a content-type)
- Antiforgery
- Temp Data
- Views
- Tag Helpers
- Memory Cache
- Pages
- Controllers
- Model Binding
- Authorization [Authorize]
- Data Annotations validation [Required]
- Antiforgery
- Temp Data
- Views
- Tag Helpers
- Memory Cache
The .NET Core team introduced a new JSON Serialization library, found in the System.Text.Json
namespace.
For the most common payload sizes, System.Text.Json offers about 20% throughput increase during input and output formatting with a smaller memory footprint.
It is now used by default in ASP.NET Core projects and SignalR Hubs.
Preview 6 brings Certificate and Kerberos authentication to ASP.NET Core.
Certificate authentication requires you to configure your server to accept certificates, and then add the authentication middleware in Startup.Configure and the certificate authentication service in Startup.ConfigureServices.
Example source
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
// All the other service configuration.
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
// All the other app configuration.
}
In place of Windows perf counters, .NET Core introduced a new way of emitting metrics via EventCounters.
- requests-per-second
- total-requests
- current-requests
- failed-requests
- connections-started
- connections-stopped
- connections-timed-out
- connections-duration
- total-calls
- current-calls
- calls-failed
- calls-deadline-exceeded
- messages-sent
- messages-received
- calls-unimplemented
dotnet counters monitor -p <PID> Microsoft.AspNetCore.Hosting System.Runtime
This is a feature that was added to Blazor that lets developers include attributes (e.g. Authorize) on their components. It is now also available in Razor Pages!
As part of the effort to decouple the components of Kestrel, we are introducing new networking primitives allowing you to add support for non-HTTP protocols.
You can bind to an endpoint (System.Net.EndPoint) by calling Bind on an IConnectionListenerFactory. This returns a IConnectionListener which can be used to accept new connections. Calling AcceptAsync returns a ConnectionContext with details on the connection. A ConnectionContext is similar to HttpContext except it represents a connection instead of an HTTP request and response.
Example source
public class TcpEchoServer : BackgroundService
{
private readonly ILogger<TcpEchoServer> _logger;
private readonly IConnectionListenerFactory _factory;
private IConnectionListener _listener;
public TcpEchoServer(ILogger<TcpEchoServer> logger, IConnectionListenerFactory factory)
{
_logger = logger;
_factory = factory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_listener = await _factory.BindAsync(new IPEndPoint(IPAddress.Loopback, 6000), stoppingToken);
while (true)
{
var connection = await _listener.AcceptAsync(stoppingToken);
// AcceptAsync will return null upon disposing the listener
if (connection == null)
{
break;
}
// In an actual server, ensure all accepted connections are disposed prior to completing
_ = Echo(connection, stoppingToken);
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
await _listener.DisposeAsync();
}
private async Task Echo(ConnectionContext connection, CancellationToken stoppingToken)
{
try
{
var input = connection.Transport.Input;
var output = connection.Transport.Output;
await input.CopyToAsync(output, stoppingToken);
}
catch (OperationCanceledException)
{
_logger.LogInformation("Connection {ConnectionId} cancelled due to server shutdown", connection.ConnectionId);
}
catch (Exception e)
{
_logger.LogError(e, "Connection {ConnectionId} threw an exception", connection.ConnectionId);
}
finally
{
await connection.DisposeAsync();
_logger.LogInformation("Connection {ConnectionId} disconnected", connection.ConnectionId);
}
}
Kestrel now supports Unix domain sockets (on Linux, macOS, and Windows 10, version 1803 and newer).
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureKestrel(o =>
{
o.ListenUnixSocket("/var/listen.sock");
})
.UseStartup<Startup>();
});
Data-binding support (@bind) for
<input>
elements is now culture-aware. Data bound values will be formatted for display and parsed using the current culture as specified by the System.Globalization.CultureInfo.CurrentCulture property. This means that @bind will work correctly when the user’s desired culture has been set as the current culture, which is typically done using the ASP.NET Core localization middleware (see Localization).
You can also manually specify the culture to use for data binding using the new @bind:culture parameter, where the value of the parameter is a CultureInfo instance. For example, to bind using the invariant culture:
<input @bind="amount" @bind:culture="CultureInfo.InvariantCulture" />
Components in .razor files are now case-sensitive. This enables some useful new scenarios and improves diagnostics from the Razor compiler.
For example, the Counter has a button for incrementing the count that is styled as a primary button. What if we wanted a Button component that is styled as a primary button by default? Creating a component named Button in previous Blazor releases was problematic because it clashed with the button HTML element, but now that component matching is case-sensitive we can create our Button component and use it in Counter without issue.
Components can now capture and render additional attributes in addition to the component’s declared parameters. Additional attributes can be captured in a dictionary and then “splatted” onto an element as part of the component’s rendering using the new @attributes Razor directive. This feature is especially valuable when defining a component that produces a markup element that supports a variety of customizations. For instance if you were defining a component that produces an
<input>
element, it would be tedious to define all of the attributes<input>
supports like maxlength or placeholder as component parameters.
<input class="form-field" @attributes="Attributes" type="text" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
Dictionary<string, object> Attributes { get; set; }
}
In the above example:
- If
Attributes
contains a class then it will be used instead ofform-field
. - If
Attributes
contains a type then it will be overwritten bytext
since it comes after. - All other attributes are simply added.
Blazor now has built-in support for handling authentication and authorization. The server-side Blazor template now supports options for enabling all of the standard authentication configurations using ASP.NET Core Identity, Azure AD, and Azure AD B2C. We haven’t updated the Blazor WebAssembly templates to support these options yet, but we plan to do so after .NET Core 3.0 has shipped.
<AuthorizeView>
<Authorized>
<a href="Identity/Account/Manage">Hello, @context.User.Identity.Name!</a>
<a href="Identity/Account/LogOut">Log out</a>
</Authorized>
<NotAuthorized>
<a href="Identity/Account/Register">Register</a>
<a href="Identity/Account/Login">Log in</a>
</NotAuthorized>
</AuthorizeView>
Razor class libraries can now include static assets like JavaScript, CSS, and images. These static assets can then be included in ASP.NET Core apps by referencing the Razor class library project or via a package reference.
Static assets must be in the wwwroot
folder.
Blazor now supports forms & validation!
Example source
Model:
public class Person
{
[Required(ErrorMessage = "Enter a name")]
[StringLength(10, ErrorMessage = "That name is too long")]
public string Name { get; set; }
[Range(0, 200, ErrorMessage = "Nobody is that old")]
public int AgeInYears { get; set; }
[Required]
[Range(typeof(bool), "true", "true", ErrorMessage = "Must accept terms")]
public bool AcceptsTerms { get; set; }
}
UI:
<EditForm Model="@person" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p class="name">
Name: <InputText bind-Value="@person.Name" />
</p>
<p class="age">
Age (years): <InputNumber bind-Value="@person.AgeInYears" />
</p>
<p class="accepts-terms">
Accepts terms: <InputCheckbox bind-Value="@person.AcceptsTerms" />
</p>
<button type="submit">Submit</button>
</EditForm>
@functions {
Person person = new Person();
void HandleValidSubmit()
{
Console.WriteLine("OnValidSubmit");
}
}
You can now create Razor Components in Razor Class Libraries and use them from ASP.NET Core projects.
The Razor Components project template now does server-side prerendering by default. This means that when a user navigates to your application, the server will perform an initial render of your Razor Components and deliver the result to their browser as plain static HTML. Then, the browser will reconnect to the server via SignalR and switch the Razor Components into a fully interactive mode. This two-phase delivery is beneficial because:
- It improves the perceived performance of the site, because the UI can appear sooner, without waiting to make any WebSocket connections or even running any client-side script. This makes a bigger difference for users on slow connections, such as 2G/3G mobiles.
- It can make your application easily crawlable by search engines.
Since Blazor (server-side) requires SignalR to run, it will now attempt to reconnect whenever it gets disconnected.
The reconnection timings can be changed, for example:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.withAutomaticReconnect([0, 0, 2000, 5000]) // defaults to [0, 2000, 10000, 30000]
.build();
Upong reconnecting, the onreconnecting
callback is fired. When the reconnection is successfull, the onreconnected
callback is fired.
You can now add stateful interactive components to a Razor page or View. When the page or view renders the component will be prerendered with it. The app will then reconnect to the component state once the client connection has been established as long as it is still in memory.
<h1>My Razor Page</h1>
<form>
<input type="number" asp-for="InitialCount" />
<button type="submit">Set initial count</button>
</form>
@(await Html.RenderComponentAsync<Counter>(new { InitialCount = InitialCount }))
@functions {
[BindProperty(SupportsGet=true)]
public int InitialCount { get; set; }
}
Quoting natemcmaster:
As announced on the .NET Blog earlier this month, .NET Framework will get fewer of the newer platform and language features that come to .NET Core moving forward, due to the in-place update nature of .NET Framework and the desire to limit changes there that might break existing applications. To ensure ASP.NET Core can fully leverage the improvements coming to .NET Core moving forward, ASP.NET Core will only run on .NET Core starting from 3.0. Moving forward, you can simply think of ASP.NET Core as being part of .NET Core.
Links:
A new meta-package was introduced: Microsoft.AspNetCore.App
, which doesn't include Json.NET, EF Core and CodeAnalysis.
Links:
This is such a neat feature, being able to easily reference the framework without worrying about the version and without searching in NuGet is such a good thing.
Quoting natemcmaster:
Most NuGet packages provide both compilation and runtime assets. Microsoft.NETCore.App and Microsoft.AspNetCore.App effectively only provide the first - compilation references. Users must install other runtime assets to make .NET Core apps work but this is not obvious or intuitive, and not always possible: for example, Azure Web Apps, AWS, Google Cloud, etc. This violates a reasonable expectation of using a NuGet package, and has been a continual source of confusion for users.
Usage:
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
Links:
Links:
Quoting Tratcher:
In ASP.NET Core 2.1 the contents of Microsoft.AspNetCore.Server.Kestrel.Https.dll were moved to Microsoft.AspNetCore.Server.Kestrel.Core.dll. This was done in a non-breaking way using TypeForwardedTo attributes. In the next major release (3.0) this empty assembly will be removed.
Links:
public void ConfigureServices(ServiceCollection services)
{
services.AddSession(options =>
{
// Removed obsolete APIs
options.CookieName = "SessionCookie";
options.CookieDomain = "contoso.com";
options.CookiePath = "/";
options.CookieHttpOnly = true;
options.CookieSecure = CookieSecurePolicy.Always;
// new API
options.Cookie.Name = "SessionCookie";
options.Cookie.Domain = "contoso.com";
options.Cookie.Path = "/";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
}
Links:
- The old 1.x stack, which was obselete, has been removed.
Newtonsoft.Json
dependecy inMicrosoft.AspNetCore.Authentication
was removed, it now usesSystem.Text.Json
.
Links:
- Announcements: 1 and 2
- Pull requests: 1, 2 and 3
- Discussions: 1, 2 and 3
- Migration guide: Use HttpContext authentication extensions
AllowSynchronousIO
is an option added to servers (Kestrel, IIS, ...) in order to allow or not synchronous IO API calls such as HttpRequest.Body.Read(?)
.
Starting 3.0, this options is set to false by default and so all synchronous calls will throw an InvalidOperationException
with the message: Synchronous IO APIs are disabled, see AllowSynchronousIO.
Links:
Since the ASP.NET Core framework doesn't depend on Roslyn anymore, compiling views and pages at runtime is not possible anymore.
A new package has been added later, Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
, which is the same behaviour. To use it, call AddMvcRazorRuntimeCompilation
on an IMvcBuilder
.
Links:
Quoting Tratcher:
When Microsoft.Extensions.Hosting was introduced in 2.1 some types like IHostingEnvironment and IApplicationLifetime were copied from Microsoft.AspNetCore.Hosting. Some 3.0 changes cause apps to include both the Microsoft.Extensions.Hosting and Microsoft.AspNetCore.Hosting namespaces. Any use of those duplicate types causes an "ambiguous reference" compiler error when both namespaces are referenced.
Obsolete types (warning):
- Microsoft.Extensions.Hosting.IHostingEnvironment
- Microsoft.AspNetCore.Hosting.IHostingEnvironment
- Microsoft.Extensions.Hosting.IApplicationLifetime
- Microsoft.AspNetCore.Hosting.IApplicationLifetime
- Microsoft.Extensions.Hosting.EnvironmentName
- Microsoft.AspNetCore.Hosting.EnvironmentName
New types:
- Microsoft.Extensions.Hosting.IHostEnvironment
- Microsoft.AspNetCore.Hosting.IWebHostEnvironment : IHostEnvironment
- Microsoft.Extensions.Hosting.IHostApplicationLifetime
- Microsoft.Extensions.Hosting.Environments
Links:
Since these are becoming obselete, users are encouraged to use CurrentCulture
and CurrentUICulture
.
Links:
In 3.0, ASP.NET Core uses the new generic host, which you can see in Program.cs
. With the old web host, users were able to add constructor dependencies in the Startup class since it was using two containers. With this new implementation, only IHostEnvironment
, IWebHostEnvironment
and IConfiguration
are injectable since it only uses a single container.
Note that dependencies can still be injected in the Startup.Configure
method.
Links:
"pubinternal" types are now fully internal:
- Microsoft.AspNetCore.ResponseCaching.Internal.CachedResponse
- Microsoft.AspNetCore.ResponseCaching.Internal.CachedVaryByRules
- Microsoft.AspNetCore.ResponseCaching.Internal.IResponseCache
- Microsoft.AspNetCore.ResponseCaching.Internal.IResponseCacheEntry
- Microsoft.AspNetCore.ResponseCaching.Internal.IResponseCachingKeyProvider
- Microsoft.AspNetCore.ResponseCaching.Internal.IResponseCachingPolicyProvider
- Microsoft.AspNetCore.ResponseCaching.Internal.MemoryResponseCache
- Microsoft.AspNetCore.ResponseCaching.Internal.ResponseCachingContext
- Microsoft.AspNetCore.ResponseCaching.Internal.ResponseCachingKeyProvider
- Microsoft.AspNetCore.ResponseCaching.Internal.ResponseCachingPolicyProvider
Since these types are now internal, AddResponseCaching
doesn't add a default implementation of IResponseCachingPolicyProvider
nor IResponseCachingKeyProvider
.
Links:
With the introduction of the new endpoints system, SignalR should now be added there and so these methods/classes are now obselete:
UseSignalR
UseConnections
ConnectionsRouteBuilder
HasRouteBuilder
Links :
As part of the performance improvements in ASP.NET Core 3.0, see this and this, DefaultHttpContext
is now sealed.
Links:
Links: