.NET 8 Preview 7 is now available and includes many great new improvements to ASP.NET Core.
Here’s a summary of what’s new in this preview release:
- Servers & middleware
- Antiforgery middleware
- API authoring
- Antiforgery integration for minimal APIs
- Native AOT
- Request Delegate Generator supports interceptors feature
- Full TrimMode is used for web projects compiled with trimming enabled
WebApplication.CreateEmptyBuilder
- Blazor
- Antiforgery integration
- Server-side form handling improvements
- Auto render mode
- Register root-level cascading values
- Improved integration of interactive components with server-side rendering
- New
EmptyContent
parameter forVirtualize
- Identity
- New bearer token authentication handler
- New API endpoints
- Single page apps (SPA)
- New Visual Studio templates
For more details on the ASP.NET Core work planned for .NET 8 see the full ASP.NET Core roadmap for .NET 8 on GitHub.
Get started
To get started with ASP.NET Core in .NET 8 Preview 7, install the .NET 8 SDK.
If you’re on Windows using Visual Studio, we recommend installing the latest Visual Studio 2022 preview. If you’re using Visual Studio Code, you can try out the new C# Dev Kit. If you are on macOS, you can now develop using Visual Studio for Mac 17.6.1 after enabling the preview feature for .NET 8 in Preferences.
Upgrade an existing project
To upgrade an existing ASP.NET Core app from .NET 8 Preview 6 to .NET 8 Preview 7:
- Update the target framework of your app to
net8.0
. - Update all Microsoft.AspNetCore.* package references to
8.0.0-preview.7.*
. - Update all Microsoft.Extensions.* package references to
8.0.0-preview.7.*
.
See also the full list of breaking changes in ASP.NET Core for .NET 8.
Servers & middleware
Antiforgery middleware
This release adds a middleware for validating antiforgery tokens, which are used to mitigate cross-site request forgery attacks. When antiforgery services are registered via the AddAntiforgery
method, the antiforgery middleware is automatically enabled in the the target application.
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
// Implicitly added by WebApplicationBuilder
// app.UseAntiforgery();
app.Run();
The antiforgery middleware itself does not short-circuit the execution of the rest of the request pipeline. Instead the middleware sets the IAntiforgeryValidationFeature
in the HttpContext.Features
of the current request. The middleware expects that each framework implementation (minimal APIs, MVC, Blazor, etc.) will react to this feature and short-circuit execution as expected.
The antiforgery token is only validated if:
- The endpoint contains metadata implementing
IAntiforgeryMetadata
whereRequiresValidation=true
. - The HTTP method associated with the endpoint is a relevant HTTP method (not TRACE, OPTIONS, HEAD, GET).
- The request is associated with a valid endpoint.
Note that the antiforgery middleware must run after the authentication and authorization middleware to avoid inadvertently reading form data when the user is unauthenticated.
API authoring
Antiforgery integration for minimal APIs
Minimal APIs that accept form data now require antiforgery token validation by default.
In the code sample below:
- Antiforgery services are registered in DI so the antiforgery middleware is automatically enabled.
- Two endpoints are provided for displaying a form:
/antiforgery
which renders a form with a hidden antiforgery token field and/no-antiforgery
which render a form without the antiforgery token field. - The
/todo
endpoint processes aTodo
object from the form and will automatically require antiforgery token validation.
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.MapGet("/antiforgery", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
var html = $"""
<html>
<body>
<form action="/todo" method="POST" enctype="multipart/form-data">
<input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}" />
<input type="text" name="name" />
<input type="date" name="dueDate" />
<input type="checkbox" name="isCompleted" />
<input type="submit" />
</form>
</body>
</html>
""";
return Results.Content(html, "text/html");
});
app.MapGet("/no-antiforgery", () =>
{
var html = """
<html>
<body>
<form action="/todo" method="POST" enctype="multipart/form-data">
<input type="text" name="name" />
<input type="date" name="dueDate" />
<input type="checkbox" name="isCompleted" />
<input type="submit" />
</form>
</body>
</html>
""";
return Results.Content(html, "text/html");
});
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));
app.Run();
class Todo
{
public string Name { get; set; }
public bool IsCompleted { get; set; }
public DateTime DueDate { get; set; }
}
submitting the form at /antiforgery
will result in a successful response. On the other hand, submitting the form at /no-antiforgery
will produce an exception at runtime because no valid antiforgery token was presented. In Production
environments, this will produce a log instead of throwing an exception:
Microsoft.AspNetCore.Http.BadHttpRequestException: Invalid antiforgery token found when reading parameter "Todo todo" from the request body as form.
An unhandled exception has occurred while executing the request.
Microsoft.AspNetCore.Http.BadHttpRequestException: Invalid antiforgery token found when reading parameter "Todo todo" from the request body as form.
---> Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken".
at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryMiddleware.InvokeAwaited(HttpContext context)
Native AOT
Request Delegate Generator supports interceptors feature
The Request Delegate Generator introduced in .NET 8 Preview 3 has been updated to use the new C# 12 interceptors compiler feature to support intercepting calls to minimal API’s Map
action methods with statically generated variants at runtime. As a result of this change, users can expect to see improvements in startup performance for apps compiled with PublishAot
enabled.
The table below outlines the changes in startup time – the time it takes for routing to process all endpoints in an application – across three scenarios:
- The baseline, where
RequestDelegate
s for endpoints are generated at runtime using reflection and dynamic code generation. - Endpoints generated at compile-time without the interceptors feature, the default between Preview 3 and Preview 6.
- Endpoints generated at compile-time with the interceptors feature, the default in Preview 7.
Iteration | Baseline (runtime-timed generated endpoints) | Request Delegate Generator (without interceptors) | Request Delegate Generator (with interceptors) |
---|---|---|---|
1 | 716.2313ms | 372.5172ms | 31.5255ms |
2 | 747.279ms | 355.1435ms | 64.188ms |
3 | 730.975ms | 350.5353ms | 32.9315ms |
4 | 729.2775ms | 345.8684ms | 34.1169ms |
5 | 711.0555ms | 351.2683ms | 38.7152ms |
Full TrimMode is used for web projects compiled with trimming enabled
In this preview, we introduce a breaking change that will impact Web projects compiled with trimming enabled via PublishTrimmed=true
. Prior to this release, projects used the partial
TrimMode by default. Moving forward, TrimMode=full
will be enabled for all projects that target .NET 8 or above. For more information on this breaking change, see the announcement.
WebApplication.CreateEmptyBuilder
We’ve added a new WebApplicationBuilder
factory method for building small apps that only contain necessary features: WebApplication.CreateEmptyBuilder(WebApplicationOptions options)
. This WebApplicationBuilder
is created with no built-in behavior. Your app will contain only the services and middleware that you configure.
Here’s an example of using this API to create a small web application:
var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions());
builder.WebHost.UseKestrelCore();
var app = builder.Build();
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello, World!");
await next(context);
});
Console.WriteLine("Running...");
app.Run();
Publishing this code with native AOT using .NET 8 Preview 7 on a linux-x64 machine results in a self-contained, native executable of about 8.5 MB
.
Blazor
Antiforgery integration
Blazor endpoints now require antiforgery protection by default. You can enable antiforgery support using the new antiforgery middleware and using the AntiforgeryToken
component to generate tokens for rendered forms. The EditForm
component will add the antiforgery token automatically for you.
Use the [RequireAntiforgeryToken]
attribute in a Blazor component page to indicate if the component requires antiforgery projection or not. For example, you can disable the antiforgery requirement for a page (not recommended!) like this:
@using Microsoft.AspNetCore.Antiforgery;
@attribute [RequireAntiforgeryToken(required: false)]
Server-side form handling improvements
You can now build standard HTML forms in Blazor when using server-side rendering and without using EditForm
. Create a form using the normal HTML form
tag and specify an @onsubmit
handler for handling the submitted form request.
<form method="post" @formname="contact" @onsubmit="AddContact">
<div>
<label for="name">Name</label>
<InputText id="name" @bind-Value="NewContact.Name" />
</div>
<div>
<label for="email">Email</label>
<InputText id="email" @bind-Value="NewContact.Email" />
</div>
<div>
<InputCheckbox id="send-me-deals" @bind-Value="NewContact.SendMeDeals" />
<label for="send-me-deals">Send me deals</label>
</div>
<button>Submit</button>
<AntiforgeryToken />
</form>
@code {
[SupplyParameterFromForm]
public Contact NewContact { get; set; } = new();
public class Contact
{
public string Name { get; set; }
public string Email { get; set; }
public bool SendMeDeals { get; set; }
}
private async Task AddContact()
{
// Add contact...
NewContact = new();
}
}
All server-side rendered forms now require a name, which is used to map submitted requests to the appropriate form handler method and for model binding. To specify the name of a plain HTML form, use the new @formname
attribute. When using EditForm
, specify the name using the FormName
parameter.
By default, form names need to be unique, but you can define a scope for form names using the FormMappingScope
component.
<FormMappingScope Name="parent-context">
<ComponentWithFormBoundParameter />
</FormMappingScope>
Inputs based on InputBase<TValue>
will generate form value names that match the names Blazor uses for model binding. You can specify the name Blazor should use to bind form data to the model using the Name
property on [SupplyParameterFromForm]
. You can also specify the name of the form whose data should be bound using the Handler
property (in earlier previews the Name
property was used for this purpose).
To break a form up into multiple child components, derive your child components from Editor<T>
. This will ensure that your child components generate the correct form field names based on the model.
Index.razor
<EditForm Model="Customer" method="post" OnSubmit="DisplayCustomer" FormName="customer">
<div>
<label>Name</label>
<InputText @bind-Value="Customer.Name" />
</div>
<AddressEditor @bind-Value="Customer.BillingAddress" />
<button>Send</button>
</EditForm>
@if (submitted)
{
<!-- Display customer data -->
<h3>Customer</h3>
<p>Name: @Customer.Name</p>
<p>Street: @Customer.BillingAddress.Street</p>
<p>City: @Customer.BillingAddress.City</p>
<p>State: @Customer.BillingAddress.State</p>
<p>Zip: @Customer.BillingAddress.Zip</p>
}
@code {
public void DisplayCustomer()
{
submitted = true;
}
[SupplyParameterFromForm] Customer? Customer { get; set; }
protected override void OnInitialized() => Customer ??= new();
bool submitted = false;
public void Submit() => submitted = true;
}
AddressEditor.razor
@inherits Editor<Address>
<div>
<label for="street">Street</label>
<InputText id="street" @bind-Value="Value.Street" />
</div>
<div>
<label for="state">State</label>
<InputText id="state" @bind-Value="Value.State" />
</div>
<div>
<label id="city">City</label>
<InputText for="city" @bind-Value="Value.City" />
</div>
<div>
<label for="zip">Zip</label>
<InputText id="zip" @bind-Value="Value.Zip" />
</div>
Model binding in Blazor now supports binding to the following additional types:
- Recursive types
- Types with constructors
- Enums
You can also now use the [DataMember]
and [IgnoreDataMember]
attributes to customize model binding for a type you are authoring. You can use these attributes to rename properties, ignore properties, and mark properties as required. Additional model binding options are available from RazorComponentOptions
when calling AddRazorComponents
.
Auto render mode
The new Auto
interactive render mode for Blazor web apps combines the strengths of the Server
and WebAssembly
render modes into a single dynamic option. The Auto
render mode uses WebAssembly-based rendering if the .NET WebAssembly runtime can be loaded quickly (within 100ms). This typically is the case when the runtime was previously downloaded and cached, or when using a high speed network. Otherwise, the Auto
render mode falls back to using the Server
render mode while the .NET WebAssembly runtime is downloaded in the background.
To use the Auto
render mode, specify the @rendermode="@RenderMode.Auto"
attribute on the component instance, or @attribute [RenderModeAuto]
on the component definition. Note that the component will need to be setup to run from both the server and the browser, so it will need to live in your client project and its implementation must not be tied to either Server
or WebAssembly
. Check out the Blazor Auto render mode sample to see how to set this up correctly.
Register root-level cascading values
Cascading values are a convenient way in Blazor to make state available to a subtree of the component hierarchy. You can now register root-level cascading values so they’re available for the entire component hierarchy.
// Registers a fixed cascading value
services.AddCascadingValue(sp => new MyCascadedThing { Value = 123 });
// Registers a fixed cascading value by name
services.AddCascadingValue("thing", sp => new MyCascadedThing { Value = 123 });
// Registers a cascading value using CascadingValueSource<TValue>
services.AddCascadingValue(sp =>
{
var thing = new MyCascadedThing { Value = 456 };
var source = new CascadingValueSource<MyCascadedThing>(thing, isFixed: false);
return source;
});
Improved integration of interactive components with server-side rendering
Blazor in .NET 8 has advanced server-side rendering capabilities, like enhanced navigation and form handling. This preview improves the integration of interactive components with server-side rendering. Enhanced navigation, enhanced form handling, and streaming rendering can now add and remove interactive components and set parameters on them.
New EmptyContent
parameter for Virtualize
The Virtualize
component now has an EmptyContent
property that you can use to define what content should be shown when there are no items or when the ItemsProvider
returns a result with a TotalItemCount
equal to zero.
Thank you @etemi for this contribution!
Identity
Previously in .NET 8 Preview 4 we added new Identity API endpoints to register and login users to simplify self-hosted identity management and make it easier to implement and customize identity in Single Page Apps (SPA) and Blazor apps. The default experience is cookie-based, so it “just works” for single domain apps. For scenarios that require tokens, such as accessing your web app from a mobile client, we now provide support for tokens “in the box.” These tokens are self-contained and use the same techniques for generation as cookie authentication. It is important to note these are not JWTs but self-contained and optimized for first-party apps with no delegated authentication. In multiple-server environments, you will need to configure data protection to use shared storage.
Bearer token authentication handler
The new bearer token authentication handler integrates seamlessly with ASP.NET Core’s built-in authentication system. It can be used standalone (without relying on ASP.NET Core identity). It supports issuing and validating tokens. As of Preview 6, it also supports refresh tokens. ASP.NET Core Identity uses the AddBearerToken
extension to integrate the handler with identity.
Identity API endpoints
The new .NET 8 set of identity API endpoints provide the HTTP-based APIs to:
- Register a new user in the identity system
- Login and exchange validated credentials for a cookie or token
- Refresh credentials using a refresh token to keep the user logged in without having to re-enter their credentials
- Confirm email for extra validation during the registration process
- Resend confirmation email if it wasn’t received or expired
- Reset password to support “forgot password” functionality
There are also protected endpoints that require the user is authenticated to:
- Manage two-factor authentication (2FA).
- Retrieve or update information in the user’s identity profile, including claims.
Get started
The identity endpoints sample by David Fowler shows how to configure identity to use the new handler and endpoints. It also contains an .http file to test the new endpoints inside of Visual Studio.
First, enable the new handler and add authentication and authorization to the app.
builder.Services.AddAuthentication().AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddAuthorizationBuilder();
Next:
- Map the identity model
- Specify a DbContext for the identity store
- Opt-in to use the new endpoints
builder.Services.AddIdentityCore<MyUser>()
.AddEntityFrameworkStores<AppDbContext>()
.AddApiEndpoints();
After calling Build()
, map the identity endpoints to routes in the application.
app.MapIdentityApi<MyUser>();
Now you can configure your APIs to use identity. This endpoint accesses identity to return the signed-in user’s name and is only available to authenticated users.
app.MapGet("/", (ClaimsPrincipal user) => $"Hello {user.Identity!.Name}").RequireAuthorization();
This is what an example session might look like:
-
POST to the
/register
endpoint to register the user.{ "user" : "test", "password" : "@T35t!", "email" : "test@notadomain.xyz" }
-
POST to the
/login?cookieMode=false&persistCookies=false
endpoint to login the user.{ "user" : "test", "password" : "@T35t!" }
-
Receive the
access_token
, expiration, andrefresh_token
.{ "token_type": "Bearer", "access_token": "CfDJ9NHobblyWobblyGobblyGoop...", "expires_in": 3600, "refresh_token": "TokenBabbelYabbaDabbaDoo..." }
-
Call a protected API using the token by setting the
Authorization
header of the request toBearer xxx
wherexxx
is theaccess_token
. -
When the user’s credentials expire or are about to expire, POST to the
/refresh
endpoint and pass therefresh_token
.{ "refreshToken": "TokenBabbelYabbaDabbaDoo..." }
-
This will generate a new
access_token
, expiration, andrefresh_token
.
These new building blocks make it easier to build authenticated apps that don’t delegate authentication.
Single page apps
New Visual Studio templates
We’ve been working closely with the Visual Studio team to ensure the Visual Studio JavaScript & TypeScript development experience works great for ASP.NET Core developers. Visual Studio includes new project templates for Angular, React, and Vue that are built on the new JavaScript project system (.esproj) and integrate with ASP.NET Core backend projects.
These Visual Studio templates come loaded with functionality for both .NET & JavaScript developers:
- Get started fast with a JavaScript frontend and an ASP.NET Core backend.
- Stay up-to-date with the latest frontend framework versions.
- Integrate with the latest frontend framework command-line tooling.
- Templates for both JavaScript & TypeScript.
- Rich JavaScript & TypeScript code editing experience.
- Clean project separation for the frontend and backend.
- Integrate JavaScript build tools with your .NET build.
- npm dependency management UI.
- Compatible with Visual Studio Code debugging and launch configuration.
- Run frontend unit tests in test explorer using your favorite JavaScript test frameworks.
To focus our efforts on providing the best possible development experience for using frontend JavaScript frameworks with ASP.NET Core, we’ve removed the existing Angular and React template from the .NET 8 SDK in favor of the new Visual Studio templates. We’re working with the Visual Studio team to further improve the new Visual Studio JavaScript with ASP.NET Core templates to support cross-platform development, integration with ASP.NET Core client web assets, simplified publishing, and targeting all supported .NET versions.
You can give the new Visual Studio JavaScript templates a try by installing the latest Visual Studio preview and then following one of the tutorials for Angular, React, and Vue in the Visual Studio docs. If you have feedback on the new templates you can share it with us using the Visual Studio Send Feedback tool.
Give feedback
We hope you enjoy this preview release of ASP.NET Core in .NET 8. Let us know what you think about these new improvements by filing issues on GitHub.
Thanks for trying out ASP.NET Core!
Great stuff. However, I noticed that in this preview, just creating a project from the Blazor Web App template and running it, it seems there are two web sockets, even if I comment out the tag in App.Razor. What’s the secret to using pure server-side rendering?
You might be seeing websockets that are setup by Visual Studio tooling. For example, there’s one that’s used for dealing with Hot Reload. Do you still see these websocket connections if you run from the command-line?
That’s it! Thanks
Hello Daniel, I found a chance to fiddle with the new Identity around a bit. But I couldn't find any way to customize the endpoints (aıutomatically created by MapIdentityApi)
For example, the /register endpoint is only accepting the following payload,
<code>
But I don't know how can I add another property belongs to TUser to this payload like "birthday". Should we create another API wrapper around this?
The introduction of the new Identity API in .NET 8 represents a significant change, in my opinion. I’ve been frustrated with the Duende identity framework. I hope you guys make add more features to it, similar to what’s available in Next.js and other frameworks.
Excellent work – this really puts Blazor at the forefront of full stack tech.
creating new “Blazor WebAssembly App” from visual studio with dot net 8 doesn’t have the option for “Asp .Net Core Hosted”
Yes, that was an intentional change because we’re trying to handle all the Blazor web scenarios with the Blazor Web App template. There are some gaps in that experience still, but we are working to address them by the time we ship .NET 8. To see how to setup an ASP.NET Core hosted Blazor WebAssembly app with .NET 8 Preview 7 see https://github.com/danroth27/Net8BlazorWebAssembly.
thank you
and while indeed the project show Blazor WASM with hosted asp core app
i was trying it to see how did you handle the new “Identity API” and was it implemented when using the “Individual Account” option with WASM also scaffolding of Identity pages
sadly nothing about them so far
Is there any plan to support data annotation localization in blazor?
See: https://learn.microsoft.com/en-us/aspnet/core/blazor/globalization-localization?view=aspnetcore-8.0
Hi Sevin. Not currently. If you think this should be added, please open a GitHub issue feature suggestion with details on what support you’d like to see.
Good work on the features.
I have a quick question re. container.
In the GitHub example about alpine/ICU support, previously in .NET 7 with root user, we could directly add the following to install ICU libs.
<code>
Now with .NET 8 container, I have to switch users to root when installing icu and then switch back to non-root user app. Is this a good practice? Any suggestions about how to handle this? Thanks.
<code>
You should not need to switch users. We added a new user, but have not changed the user. The user in the Alpine images we ship is still root. If you find you need to change users multiple times, please share a full Dockerfile.
See:
Here is the full dockerfile. I need to switch to root to install ICU as app user has no permission to do that.
<code>
This error happens on publishing my blazor project (Translation: An element with the same key was already inserted):
<code>
From the error log:
<code>
The project compiles and executes correctly. Only publishing fails.
Here is a detailed build protocol:
<code>
Hi Sevin. Thank you for sharing this feedback. Could you please open a GitHub issue in the dotnet/aspnetcore repo with details on how to reproduce the error so that we can investigate?
Hi Daniel, thanks. I opened a new issue at https://github.com/dotnet/aspnetcore/issues/50360. The publishing error happens if you have multiple resource files for translation in a separate class library.
It would be nice to have EmptyContent on quickgrid as well
Thanks Tomas for this suggestion! It would be great if you could suggest this on GitHub by opening an issue in the dotnet/aspnetcore repo. That way it will get tracked as part of our backlog and the community can comment on it.
Using the new project template Blazor Web App, I’m unable to get a proper 404 page. Whatever I add to
in App.razor is ignored and I just get a generic browser 404.
Using the old Blazor Server template upgraded to preview 7, it works fine. The only difference I’m able to spot is the removal of _Host.
So what to do?
Hi Simen. Please see https://github.com/dotnet/aspnetcore/issues/48983 for a discussion of this issue.