.NET 6 Preview 7 is now available and includes many great new improvements to ASP.NET Core.
Here’s what’s new in this preview release:
- Parity with existing experiences for minimal APIs
- Added
IResult
implementations for producing common HTTP responses - Support Request, Response and User for minimal actions
- Minimal host and template improvements
- Supply Blazor component parameters from the query string
- Replace the current URI in the browser history from Blazor
- New
DynamicComponent.Instance
property - Blazor streaming interop from JavaScript to .NET
- Large file upload & faster file uploads with Blazor
- Modify HTML
<head>
content from Blazor components - Support for the
multiple
attribute on<select>
elements in Blazor - Support for HTTP/3 in Kestrel
- QUIC support moved to the shared framework
- Allow control over
Activity
creation - Support for non-ASCII characters in Kestrel response headers
- Add W3CLogger
- Add authentication expiration option to SignalR
Get started
To get started with ASP.NET Core in .NET 6 Preview 7, install the .NET 6 SDK.
If you’re on Windows using Visual Studio, install the latest preview of Visual Studio 2022. .NET 6 will be supported in a future public release of Visual Studio 2022 for Mac.
To get setup with .NET MAUI & Blazor for cross-platform native apps, see the latest instructions in the .NET MAUI getting started guide. Be sure to also check out the Announcing .NET MAUI Preview 7 blog post for all the details on what’s new in .NET MAUI in this release.
To install the latest .NET WebAssembly tools for ahead-of-time (AOT) compilation and runtime relinking, uninstall the earlier microsoft-net-sdk-blazorwebassembly-aot
workload and install the new wasm-tools
workload by running the following commands from an elevated command prompt:
dotnet workload uninstall microsoft-net-sdk-blazorwebassembly-aot
dotnet workload install wasm-tools
Upgrade an existing project
To upgrade an existing ASP.NET Core app from .NET 6 Preview 6 to .NET 6 Preview 7:
- Update all Microsoft.AspNetCore.* package references to
6.0.0-preview.7.*
. - Update all Microsoft.Extensions.* package references to
6.0.0-preview.7.*
.
To upgrade a .NET MAUI Blazor app from .NET 6 Preview 6 to .NET 6 Preview 7 we recommend starting from a new .NET MAUI Blazor project created with the .NET 6 Preview 7 SDK and then copying code over from your original project.
See the full list of breaking changes in ASP.NET Core for .NET 6.
What’s new with minimal APIs?
Since we announced minimal APIs in .NET 6 Preview 4 we’ve been focused on enabling a more robust set of features. For .NET 6 Preview 7 we’re happy to announce that we’re lighting up more of your favorite ASP.NET experiences with a minimal twist. Let’s take a look at what you can expect.
Parity with existing experiences
Minimal APIs fundamentally change how the app startup code is structured. This meant that some of the tools and libraries we ship that interact with the app needed to be updated to handle this new pattern. The following tools and libraries have now been updated accordingly:
Added IResult
implementations for producing common HTTP responses
In earlier previews, we extended IActionResult
implementations from MVC to support the new IResult
type introduced for minimal APIs. In this release, we’ve removed the dependency between IActionResult
and IResult
and added a new static Results
utility class for producing common HTTP responses. Here’s an example:
app.MapPut("/todos/{id}", async (TodoDbContext db, int id, Todo todo) =>
{
if (id != todo.Id)
{
return Results.BadRequest();
}
if (!await db.Todos.AnyAsync(x => x.Id == id))
{
return Results.NotFound();
}
db.Update(todo);
await db.SaveChangesAsync();
return Results.Ok();
});
Thank you Juan Barahona for contributing these IResult
implementations!
Support Request, Response and User for minimal actions
In this preview, we added support for binding HttpRequest
, HttpResponse
and ClaimsPrincipal
parameters in minimal actions. These parameters come from the Request
, Response
and User
properties on HttpContext
respectively. This is in addition to the pre-existing support for binding the HttpContext
directly and a CancellationToken
from HttpContext.RequestAborted
.
The example below accepts the current user directly into the request handler method.
app.MapGet("/api/items", async (ITodoService service, ClaimsPrincipal user) =>
{
return await service.GetListAsync(user.GetUserId());
})
.RequireAuthorization();
Thank you Martin Costello for contributing this feature!
Minimal host and template improvements
Minimal APIs introduced new hosting APIs and, combined with new C# features like global usings and top-level statements, enabled us to streamline the app startup experience. In this preview, we’re extending these changes to the other ASP.NET Core project templates.
For example, when you create a new ASP.NET Core Web API using the template you’ll now notice the following:
- No
Startup.cs
- Leveraging implicit
usings
- New hosting model using
WebApplication.CreateBuilder
- Top-level statements in
Program.cs
(no namespace, class, or method declarations) - Nullable reference types
These changes reduce the amount of boilerplate code required to configure and start a new app. Note that the existing hosting model and Startup
pattern will continue to be supported for existing apps.
ASP.NET Core Web API in .NET 5
ASP.NET Core Web API in .NET 6
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "WebApplication22", Version = "v1" });
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication22 v1"));
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Check out the the .NET blog for more details on how we’re modernizing the project templates that ship with the .NET SDK.
Note about implicit using statements: Based on early feedback during implementation, changes to the implicit usings feature are being made as part of the next release, including the requirement to opt-in to implicit
usings
in the project file, rather than them being included by default based on the project targeting .NET 6. This will ensure they don’t impact existing projects being migrated to .NET 6 until the author is ready to enable the feature.
Supply Blazor component parameters from the query string
Blazor components can now receive parameters from the query string. To specify that a component parameter of a routable component can come from the query string, apply the [SupplyParameterFromQuery]
attribute in addition to the normal [Parameter]
attribute:
@page "/search"
<h1>Search</h1>
<p>Filter: @Filter</p>
<p>Page: @Page</p>
<p>Assignees:</p>
<ul>
@foreach (var assignee in Assignees)
{
<li>@assignee</li>
}
</ul>
@code {
// Handles URL patterns like /search?filter=some+stuff&page=3&assignee=Monica&assignee=Chandler
[Parameter]
[SupplyParameterFromQuery]
public string Filter { get; set; }
[Parameter]
[SupplyParameterFromQuery]
public int? Page { get; set; }
[Parameter]
[SupplyParameterFromQuery(Name = "assignee")]
public string[] Assignees { get; set; }
}
Component parameters supplied from the query string may be of the following types:
String
,bool
,DateTime
,decimal
,double
,float
,Guid
,int
,long
.- Nullable variants of the above types (except
string
– not applicable). - Arrays of the above types, nullable or not.
Replace the current URI in the browser history from Blazor
When calling NavigationManager.NavigateTo
in a Blazor app, you can now specify that you want to replace the current URI in the browser history instead of pushing a new URI onto the history stack. To replace the current URI in the browser history, specify true
for the new replace
parameter:
@NavigationManager.NavigateTo("Other", replace: true)
New DynamicComponent.Instance
property
DynamicComponent
now has an Instance
property that provides a convenient way to get access to the dynamically created component instance:
<DynamicComponent Type="typeof(MyComponent)" @ref="dc" />
<button @onclick="Refresh">Refresh</button>
@code {
DynamicComponent dc;
Task Refresh()
{
return (dc.Instance as IRefreshable)?.Refresh();
}
}
Blazor streaming interop from JavaScript to .NET
Blazor now supports streaming data directly from JavaScript to .NET. Streams are requested using the new IJSStreamReference
interface:
JavaScript
function jsToDotNetStreamReturnValue() {
return new Uint8Array(10000000);
}
C#
var dataReference = await JSRuntime.InvokeAsync<IJSStreamReference>("jsToDotNetStreamReturnValue");
using var dataReferenceStream = await dataReference.OpenReadStreamAsync(maxAllowedSize: 10_000_000);
// Write JS Stream to disk
var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");
using var outputFileStream = File.OpenWrite(outputPath);
await dataReferenceStream.CopyToAsync(outputFileStream);
Large file upload & faster file uploads with Blazor
Using the new Blazor streaming interop support mentioned above, we now support uploading files larger than 2GB using the InputFile
component. The updated InputFile
component is also much more efficient at uploading files thanks to native byte[]
streaming, which avoids expensive Base64 encoding.
Modify HTML <head>
content from Blazor components
Blazor now has built-in support for modifying HTML <head>
element content from components, including setting the <title>
and adding <meta>
elements.
To specify the page’s title from a component, use the new PageTitle
component. The HeadContent
component can be used to render other content to the <head>
.
<PageTitle>@title</PageTitle>
<HeadContent>
<meta name="description" content="@description">
</HeadContent>
@code {
private string description = "Description set by component";
private string title = "Title set by component";
}
To enable the functionality provided by PageTitle
and HeadContent
, you need to add a HeadOutlet
root component to your application. In Blazor WebAssembly, this can be done by adding the following line in Program.Main
:
builder.RootComponents.Add<HeadOutlet>("head::after");
In Blazor Server, the setup is slightly more involved. In order to support prerendering, the App
root component needs to be rendered before the HeadOutlet
. A convenient way to achieve this is to move most of the content in _Host.cshtml
to _Layout.cshtml
, as shown below.
_Layout.cshtml
@using Microsoft.AspNetCore.Components.Web
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Blazor Server App</title>
<base href="~/" />
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
@RenderBody()
<script src="_framework/blazor.server.js"></script>
</body>
</html>
_Host.cshtml
@page "/"
@namespace BlazorServerWeb_CSharp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_Layout";
}
<component type="typeof(App)" render-mode="ServerPrerendered" />
A future release will include updated Blazor project templates, so performing these changes to enable <head>
modification won’t be necessary for new projects.
Support for the multiple
attribute on <select>
elements in Blazor
If you specify the multiple
attribute in a <select>
elements from a Blazor component, the onchange
event will now supply an array of the selected elements via ChangeEventArgs
. Likewise, array values can be bound to the value
attribute when multiple
is specified.
<p>
Select one or more cars:
<select @onchange="SelectedCarsChanged" multiple>
<option value="audi">Audi</option>
<option value="jeep">Jeep</option>
<option value="opel">Opel</option>
<option value="saab">Saab</option>
<option value="volvo">Volvo</option>
</select>
</p>
<p>
Select one or more cities:
<select @bind="SelectedCities" multiple>
<option value="bal">Baltimore</option>
<option value="la">Los Angeles</option>
<option value="pdx">Portland</option>
<option value="sf">San Francisco</option>
<option value="sea">Seattle</option>
</select>
</p>
@code {
public string[] SelectedCars { get; set; } = new string[] { };
public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };
void SelectedCarsChanged(ChangeEventArgs e)
{
SelectedCars = (string[])e.Value;
}
}
When using <InputSelect>
, the multiple
attribute is inferred if the bound value has an array type.
<EditForm EditContext="@editContext">
Select one or more classifications (Minimum: 2, Maximum: 3):
<InputSelect @bind-Value="starship.SelectedClassification">
<option value="@Classification.Exploration">Exploration</option>
<option value="@Classification.Diplomacy">Diplomacy</option>
<option value="@Classification.Defense">Defense</option>
<option value="@Classification.Research">Research</option>
</InputSelect>
</EditForm>
@code {
private EditContext editContext;
private Starship starship = new();
protected override void OnInitialized()
{
editContext = new(starship);
}
private class Starship
{
[Required, MinLength(2), MaxLength(3)]
public Classification[] SelectedClassification { get; set; } =
new[] { Classification.Diplomacy };
}
private enum Classification { Exploration, Diplomacy, Defense, Research }
}
Preview support for HTTP/3 in Kestrel
Preview 7 introduces early support for HTTP/3 and QUIC in Kestrel to try out and give feedback on.
HTTP/3 is the third and upcoming major version of HTTP. HTTP/3 uses the same semantics as HTTP/1.1 and HTTP/2: the same request methods, status codes, and message fields apply to all versions. The differences are in the underlying transport. Both HTTP/1.1 and HTTP/2 use TCP as their transport. HTTP/3 uses a new transport technology developed alongside HTTP/3 called QUIC.
HTTP/3 and QUIC have a number of benefits compared to older HTTP versions:
- Faster response time of the first request. QUIC and HTTP/3 negotiates the connection in fewer round-trips between the client and the server. The first request reaches the server faster.
- Improved experience when there is connection packet loss. HTTP/2 multiplexes multiple requests via one TCP connection. Packet loss on the connection would affect all requests. This problem is called “head-of-line blocking”. Because QUIC provides native multiplexing, lost packets only impact the requests where data has been lost.
- Supports transitioning between networks. This feature is useful for mobile devices where it is common to switch between Wi-Fi and cellular networks as a mobile device changes location. Today HTTP/1.1 and HTTP/2 connections will fail with an error and force an app or web browser to retry. HTTP/3 allows the app or web browser to seamlessly continue when a network changes. Kestrel doesn’t support network transitions in .NET 6, but we’ll explore adding it in a future .NET release.
To start using HTTP/3, configure the QUIC transport, and modify ListenOptions
to add an HTTP/3 binding. For use with browser-based clients, you’ll also need to enable sending the alt-svc
header.
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseKestrel()
// Set up Quic options
.UseQuic(options =>
{
options.Alpn = "h3-29";
options.IdleTimeout = TimeSpan.FromMinutes(1);
})
.ConfigureKestrel((context, options) =>
{
options.EnableAltSvc = true;
options.Listen(IPAddress.Any, 5001, listenOptions =>
{
// Use Http3
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
listenOptions.UseHttps();
});
});
HTTP/3 is not supported everywhere. See use HTTP/3 with the ASP.NET Core Kestrel web server for information on getting started with HTTP/3 in Kestrel.
QUIC support moved to the shared framework
We’re no longer shipping the Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
package, and are instead including the assembly in the ASP.NET Core shared framework. Customers with a PackageReference
to Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
should remove the PackageReference
from their project.
Allow control over Activity
creation
We’ve added support for DistributedContextPropagator
s in ASP.NET Core. Prior to this change, ASP.NET Core had knowledge of the W3C TraceContext and W3C Baggage specifications and we created an Activity
only if the tracestate
and traceparent
HTTP headers were present on the incoming HTTP request. This made it impossible to support other tracing specifications. It’s now possible to support other distributed tracing specifications by implementing a custom DistributedContextPropagator
and registering it in the DI container.
Support for non-ASCII characters in Kestrel response headers
Kestrel now has support for sending non-ASCII characters in HTTP response headers. To opt-in custom encoding on per-header basis, you can provide a ResponseHeaderEncodingSelector
:
builder.WebHost.ConfigureKestrel(options =>
{
options.ResponseHeaderEncodingSelector = (_) => Encoding.ASCII;
});
Note: While use of custom response header encoding may be needed in some cases, we discourage the use of non-ASCII encodings to avoid compatibility issues with other HTTP clients.
Add W3CLogger
ASP.NET Core is now capable of generating server access logs in the W3C Extended Log File Format. To start emitting server access logs, you need to add the logger to DI and add the W3C logging middleware to your middleware pipeline:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddW3CLogging(options =>
{
options.LogDirectory = @"C:\logs";
options.LoggingFields = W3CLoggingFields.Request | W3CLoggingFields.ConnectionInfoFields;
});
var app = builder.Build();
app.UseW3CLogging();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.MapGet("/", () => "Hello World!");
app.Run();
This will produce a log file that resembles this output:
#Version: 1.0
#Start-Date: 2021-08-10 01:07:40
#Fields: c-ip s-ip s-port cs-method cs-uri-stem cs-uri-query cs-version cs-host cs(User-Agent) cs(Referer)
::1 ::1 5001 GET / - HTTP/2 localhost:5001 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/92.0.4515.131+Safari/537.36+Edg/92.0.902.67 -
::1 ::1 5001 GET /favicon.ico - HTTP/2 localhost:5001 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/92.0.4515.131+Safari/537.36+Edg/92.0.902.67 https://localhost:5001/
::1 ::1 5001 GET / - HTTP/2 localhost:5001 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/92.0.4515.131+Safari/537.36+Edg/92.0.902.67 -
::1 ::1 5001 GET / - HTTP/2 localhost:5001 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/92.0.4515.131+Safari/537.36+Edg/92.0.902.67 -
::1 ::1 5001 GET / - HTTP/2 localhost:5001 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/92.0.4515.131+Safari/537.36+Edg/92.0.902.67 -
Add authentication expiration option to SignalR
There is a new option that will enable SignalR to track the expiration of an authentication token and close the connection if the token expires. This option can be enabled with CloseOnAuthenticationExpiration
on HttpConnectionDispatcherOptions
as shown below.
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MyHub>("/hub", options =>
{
options.CloseOnAuthenticationExpiration = true;
});
});
Give feedback
We hope you enjoy this preview release of ASP.NET Core in .NET 6. We’re eager to hear about your experiences with this release. Let us know what you think by filing issues on GitHub.
Thanks for trying out ASP.NET Core!
Are there any plans to allow streaming from .NET to JavaScript? This would make cancellable downloading using HttpClient a lot easier, and reduce the unavoidable multiple buffering needed to save data retrieved using HttpClient.
The problem described at https://stackoverflow.com/questions/69119763/blazor-webassembly-cancellationtoken-on-jsruntime/69129751 would be a lot easier to handle
Yup: https://github.com/dotnet/aspnetcore/pull/34817. The functionality will preview for .NET 6 soon.
How do we approach using WebApplicationFactory<T> for testing now that Startup.cs has been removed? Is there a way to link that directly to the existing app configuration for startup within Program.cs? Do I need to simply duplicate any code from there into the standard overrides when using WebApplicationFactory<T>?
I am really looking forward for all these features coming to Asp.Net, keep going with the good workguys.
One feedback I would like to make is to keep updates for Blazor and Asp.Net into their own posts, I think it would make much easier to follow the updates for each.
I’ve been having some trouble with making amendments to the splash screen, I’m not sure if its bug related but when changing the SVG its producing build errors. If anyone knows an article for customizing the splash page that would be appreciated. Good job with the updates!
would it be just the API project or all projects will not have a startup class file moving onwards? and the for DI we would directly be using builder.Services to build on right?
Hi Bhrugen. We’ve updated all of the ASP.NET Core project templates (MVC, Razor Pages, Blazor, Web API, etc) to use the new minimal hosting APIs instead of a Startup class. But using a Startup class is still fully supported. When using the new minimal hosting APIs you use builder.Services to configure services for dependency injection, just like you would in Startup.ConfigureServices.
Hi,
Someone knows in .Net 6 / C# 10, essentially, which method or implementation will replace the .Net core 3.1 Model validation?
How do I make my property model a Required parameter? And how do I check it?
Before minimal API, i did: if(ModelState.IsValid){ //do something }
Now, in my app.MapGet/MapPost/etc, what is the recommended way to do this things?
Thanks a lot 🙂
There’s no support for validation in minimal APIs, you can use something like https://fluentvalidation.net/ or https://www.nuget.org/packages/MinimalValidation/0.1.0-pre to do validation in your APIs.
Thanks again!!!!
Dan awesome news as always!! Very excited about the InputFile built-in support. In the past I've tried few approaches but not very happy with the outcome. Syncfusion just recently added support to asynchronous, thus UI gets frozen for quite some time depending on the file size. Also chunking was facing the size limitations Steve addressed in his blog. Someone suggested to use XHR to upload the file -even in chunks- and subscribe to the ...
Hi Yadel. I recommend giving the InputFile component another try in .NET 6 if you haven’t already. It has been completely revamped to be speedier and more reliable. Chunking shouldn’t be necessary either and we’ve designed it such the UI remains responsive even for large uploads. Let us know though if you still run into issues.
Hi Dan. Thanks for the quick response. I've started using it already. I also checked the code in GitHub and I see chunking is done out-of-the-box. I do have one question though.
- If HttpClient component is a .NET abstraction -not sure if abstraction is the right word- over the fetch API which uses HTTP messages
- WebSockets is faster than HTTP
- Blazor hosted model uses WebSockets
Do you think a .NET client that...
I wouldn’t necessarily say that WebSockets are faster than HTTP. The main benefit of WebSockets is they provide a duplex channel with the server. If you’re just uploading a file to the server, having a duplex channel isn’t really relevant. You can stream binary data over HTTP just fine. We’ve hopefully made that easier to do with the InputFile improvements.
You guys certainly did. I was coming across this blog post where according to some benchmarks is 50% faster. Is it true or not? Maybe yet to be determined.
Hi, thanks for this great article.
I've already created a minimal API project integrated with swagger and EF.
Once I create a simple API without "async (http)" I see the endpoint in swagger UI.
But, when I use async (http) like the example below:
app.MapGet("/getEmplyeeById/{id}", async (http) =>{}
It doesn't exist in swagger UI,
but it works when I open the API through chrome I get the JSON result (http://localhost:5000/getEmplyeeById/2)
There’s a slight nuance here that we haven’t resolved as yet. Change app.MapGet(“/getEmplyeeById/{id}”, async (http) =>{}) to app.MapGet(“/getEmplyeeById/{id}”, () =>{}) and it will work. The reason is that the former is binding to a different overloaad of MapGet that doesn’t support OpenAPI.
Thanks, David, but I need to use the HTTP in order to update StatusCode & Headers, is there any workaround? or other suggestions?
I did something like this:
app.MapGet(“/getEmplyeeById/{id}”, (HttpContext http) =>
It looks like it works for me
That works too, you can also inject the HttpRequest and HttpResponse directly as another workaround. Also you should be using the Result helpers to produce responses.
Hi Dan!
Awesome work!
I have a problem with
when running my Blazor WASM project with preview 7.
I does not detect changes when pressing CTRL + S.
It does detect changes when I do a change and then revert it using VS Git “Undo changes” button.
Do you know anything about this?
It does also detect changes when using .NET 5 (latest supported)
Hi Erik. In .NET 6 running
dotnet watch run
will attempt to hot reload change by default instead of rebuilding and restarting the app. You may be hitting an issue with .NET Hot Reload and Blazor WebAssembly apps. Could you please open a GitHub issue with details on how to reproduce the behavior you are seeing?: https://github.com/dotnet/aspnetcore/issues/new. Thanks!Late question about Blazor AOT and plugins in general.. Does Blazor WASM AOT still support lazy loading assemblies? Is there a more complete plugin story shaping up for this release(even if it’s just “here are the integration points but you have to build your own system on top”)?
Cheers!
Hi Greg. Lazy loading of different parts of a Blazor WebAssembly app isn't supported when using AOT compilation. Using AOT compilation with a Blazor WebAssembly app will create a single WebAssembly bundle. We don't have planned a broader plugin story for Blazor WebAssembly in .NET 6. If that's something you have ideas on how to achieve, please let us know on GitHub