ASP.NET Core updates in .NET 8 Preview 7

Daniel Roth

.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 for Virtualize
  • 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 where RequiresValidation=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 a Todo 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 RequestDelegates 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

Chart showing Request Delegate Generator with interceptors performance

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:

  1. POST to the /register endpoint to register the user.

    {
        "user" : "test",
        "password" : "@T35t!",
        "email" : "test@notadomain.xyz"
    }
  2. POST to the /login?cookieMode=false&persistCookies=false endpoint to login the user.

    {
        "user" : "test",
        "password" : "@T35t!"
    }
  3. Receive the access_token, expiration, and refresh_token.

    {
        "token_type": "Bearer",
        "access_token": "CfDJ9NHobblyWobblyGobblyGoop...",
        "expires_in": 3600,
        "refresh_token": "TokenBabbelYabbaDabbaDoo..."
    }
  4. Call a protected API using the token by setting the Authorization header of the request to Bearer xxx where xxx is the access_token.

  5. When the user’s credentials expire or are about to expire, POST to the /refresh endpoint and pass the refresh_token.

    {
        "refreshToken": "TokenBabbelYabbaDabbaDoo..."
    }
  6. This will generate a new access_token, expiration, and refresh_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.

Visual Studio JavaScript templates

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!

66 comments

Discussion is closed. Login to edit/delete existing comments.

  • Lindsay Mathieson 0

    Publishing to a local IIS appears to be broken at the moment. Publish itself works ok, but the site can’t be loaded.

    The “Blazor Server App” Template generates a 503 error

    The “Blazor Web App” loads a simple text version, with lots of 404 error for the bootstrap css files, it is attempting to load them from http://localhost rather than http://localhost/sitename. If you publish to the website root, it works ok.

    Both templates run fine in the VS ide.

    nb. Preview hosting installed, using VS Preview.

    • Artak MkrtchyanMicrosoft employee 1

      Thanks for sharing this feedback, @Lindsay Mathieson.
      I’d like to clarify the Blazor Server App statement. In 8.0, we’ve removed the Blazor Server project template, so if you see Blazor Server project template as an option, then it should be coming from some earlier .NET SDK version (7.0 or 6.0). In 8.0 we expect customers who would otherwise use Blazor Server project template, to use Blazor Web App project template. That said, it would be great to get more details about the scenario that is failing for you. Would you mind creating a new issue in our GitHub repository at https://github.com/dotnet/aspnetcore and provide details about your setup and the repro steps, so that we can make sure that we don’t regress any scenarios unintentionally.

      As for the 404 errors with the Blazor Web App template, that’s also an interesting one and getting a separate issue filed for that would also help us to make sure whatever problems you are facing are being addressed.

      Thank you for being an early adopter of the ASP.NET Core framework.

      • Lindsay Mathieson 1

        Thanks for the reply and info, yup I had the Server templates installed for 7. Will raise the issues on github.

        Loving the direction Blazor is taking, game changing stuff for C# server\web dev teams.

  • Lukas Veteska 0

    Will new vue/angular/react templates support SSR? If not how should devs deal with SEO?

    • Daniel RothMicrosoft employee 0

      Hi Lukas. The Angular, React, and Vue templates do not support server-side rendering. Server-side rendering with JavaScript frameworks requires running JavaScript on the server.

  • Michael Price 0

    Is the default hosting model for apps with a mix of render modes or configured for auto Azure App Services?

  • Daniel Kruse 0

    Microsoft try not to change the fundamentals and structure of a framework every update challenge (impossible)

    In all seriousness, I cannot keep up and re-learn the structure and systems of Blazor every .NET release, not even my Ritalin helps me there.

  • Sevin Ulukut 0

    Hi Daniel,
    There is version conflict between Microsoft.EntityFrameworkCore.Design 8.0.0-preview.7.23375.4 (requiring Microsoft.CodeAnalysis.Common 4.5.0) and Microsoft.VisualStudio.Web.CodeGeneration.Design 8.0.0-preview.6.23360.4 (requiring Microsoft.CodeAnalysis.Common 4.4.0). Unfortunately there is no Microsoft.VisualStudio.Web.CodeGeneration.Design 8.0.0-preview.7.* version available.

    Here is the error message in German:
    NU1107 Versionskonflikt entdeckt für “Microsoft.CodeAnalysis.Common”. Installieren bzw. referenzieren Sie “Microsoft.CodeAnalysis.Common 4.5.0” direkt in Projekt “Mp3_Search.Mvc.App”, um dieses Problem zu beheben.
    Mp3_Search.Mvc.App -> Microsoft.EntityFrameworkCore.Design 8.0.0-preview.7.23375.4 -> Microsoft.CodeAnalysis.CSharp.Workspaces 4.5.0 -> Microsoft.CodeAnalysis.Common (= 4.5.0)
    Mp3_Search.Mvc.App -> Microsoft.VisualStudio.Web.CodeGeneration.Design 8.0.0-preview.6.23360.4 -> Microsoft.DotNet.Scaffolding.Shared 8.0.0-preview.6.23360.4 -> Microsoft.CodeAnalysis.CSharp.Features 4.4.0 -> Microsoft.CodeAnalysis.Common (= 4.4.0).

    Any recommendation how I can get the project compiled?

    Thanks and best regards,
    Sevin

    • Simen Arntsen 0

      Do as the error instructs you to, add CodeAnalysis v 4.5.0 manually to your csproj file. You need some additional references too

        PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.5.0"
        PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0"
        PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.5.0"
        PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.5.0"
      
      • Sevin Ulukut 0

        Thanks!!

    • Daniel RothMicrosoft employee 0

      The 8.0.0-preview.7 version of the Microsoft.VisualStudio.Web.CodeGeneration.Design package is now available on NuGet.

  • Simen Arntsen 0

    Hello
    I’m struggling with the sample implementaion of the new Identity api in Blazor.
    Using the github as provided, with bearer token configured, register and login works fine (response code 200) but trying to access / and account/info only results in 401.
    It’s also not possible to login using cookies, gives 500 error saying something about AddAuthentication().AddCookie(“Identity.Application”) missing

    So I change the program.cs to AddDefaultIdentity instead of AddIdentityCore and I can login using cookies, and I get correct 200 response on / and account/info, but if I login using bearer token I get 404 from / and /account/info….

    I’m good with cookies for now, just want to mention it.

    Also when registering the confirm email sendt a link like this “http://confirmemail/?userId=1557b ….”, the localhost:port is missing. Also how do I customize this email? Previously I would scaffold the Identity system and edit Register.cshtml.cs await _emailSender.SendEmailAsync(Input.Email, “Confirm your email”,
    $”Please confirm your account by clicking here.”);

    And last, how to logout?

    • Stephen HalterMicrosoft employee 0

      Using the github as provided, with bearer token configured, register and login works fine (response code 200) but trying to access / and account/info only results in 401.

      Did you take the access_token from the 200 response code and send it as bart of the Authorization header? This isn’t necessary if you use cookieMode=true, have the cookie authentication handler enabled, and the client supports cookies.

      4. Call a protected API using the token by setting the Authentication [sic] header of the request to Bearer xxx where xxx is the access_token.

      It’s also not possible to login using cookies, gives 500 error saying something about AddAuthentication().AddCookie(“Identity.Application”) missing

      Calling AddCookie(“Identity.Application”) (or better yet AddIdentityCookies() so you also get security stamp invalidation) should work, the easier thing to do would probably just be to call builder.Services.AddIdentityApiEndpoints<TUser>() as demonstrated in https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-4/#authentication-and-authorization which adds both the cookie and bearer auth handlers for you along with all the identity related services you need so you don’t need to call AddIdentityCore either then.

      Also when registering the confirm email send a link like this “http://confirmemail/?userId=1557b ….”, the localhost:port is missing.

      This is a bug. It’s incorrectly using a relative URL for the email. Thanks for reporting it. It should be using the Host header to generate the link. See https://github.com/dotnet/aspnetcore/pull/50297

      Also how do I customize this email?

      Right now, using the prerelease, your best option is to check for the “Confirm your email” or “Reset your password” subjects and extract the link or code from the htmlMessage. This is fragile, but will hopefully be addressed by release with the addition of new default interface methods like SendConfirmationLinkAsync and SendPasswordResetCodeAsync. See https://github.com/dotnet/aspnetcore/issues/50298

      And last, how to logout?

      If you’re using the access_token, you simply stop sending it as part of the Authorization header to log out. Like cookies, there is no mechanism to revoke it other than invalidating the security stamp or waiting for it to expire.

      If you are using cookies, and you want to clear them, you can create your own endpoint that calls SignOutAsync(IdentityConstants.ApplicationScheme). For example:

      app.MapGet("/logout", context =>
      {
          return context.SignOutAsync(IdentityConstants.ApplicationScheme);
      });

      If a client were to ignore the Set-Cookie header on the “/logout” response and continue to send the old cookie afterwards, it would continue to work just as with the access_token, but well behaved clients should stop sending the cookie.

  • Simen Arntsen 0

    Using the new project template Blazor Web App, I’m unable to get a proper 404 page. Whatever I add to

    NotFound

    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?

  • Tomas Lopez 0

    It would be nice to have EmptyContent on quickgrid as well

    • Daniel RothMicrosoft employee 0

      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.

  • Sevin Ulukut 0

    This error happens on publishing my blazor project (Translation: An element with the same key was already inserted):

    System.ArgumentException: Ein Element mit dem gleichen Schlüssel wurde bereits hinzugefügt.
       bei System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
       bei System.Collections.Generic.Dictionary2.Insert(TKey key, TValue value, Boolean add)
       bei Microsoft.NET.Sdk.WebAssembly.ComputeWasmPublishAssets.GroupExistingStaticWebAssets(Dictionary2 assemblyAssets, Dictionary2 nativeAssets, Dictionary2 satelliteAssemblyAssets, Dictionary2 symbolAssets, Dictionary2 compressedRepresentations)
       bei Microsoft.NET.Sdk.WebAssembly.ComputeWasmPublishAssets.Execute() Mp3_Search.Blazor.App

    From the error log:

    System.AggregateException: Mindestens ein Fehler ist aufgetreten. ---> Microsoft.WebTools.Shared.Exceptions.WebToolsException: Fehler beim Erstellen. Weitere Details finden Sie im Ausgabefenster.
       --- Ende der internen Ausnahmestapelüberwachung ---
    ---> (Interne Ausnahme #0) Microsoft.WebTools.Shared.Exceptions.WebToolsException: Fehler beim Erstellen. Weitere Details finden Sie im Ausgabefenster.<---

    The project compiles and executes correctly. Only publishing fails.

    • Sevin Ulukut 0

      Here is a detailed build protocol:

              obj\Debug\net8.0\linked\System.Private.CoreLib.dll
                      AssemblyVersion=8.0.0.0
                      AssetType=native
                      CopyLocal=true
                      CopyToPublishDirectory=PreserveNewest
                      DestinationSubPath=System.Private.CoreLib.dll
                      DropFromSingleFile=true
                      FileVersion=8.0.23.37506
                      IsTrimmable=
                      NuGetPackageId=Microsoft.NETCore.App.Runtime.Mono.browser-wasm
                      NuGetPackageVersion=8.0.0-preview.7.23375.6
                      PostprocessAssembly=true
                      PublicKeyToken=7cec85d7bea7798e
                      RelativePath=System.Private.CoreLib.dll
                      RuntimeIdentifier=browser-wasm
                      Aufgabenparameter:PublishPath=bin\Debug\net8.0\publish\
                      Aufgabenparameter:TimeZoneSupport=True
                      Aufgabenparameter:CopySymbols=False
                      Aufgabenparameter:InvariantGlobalization=False
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\dotnet-legacy.d.ts' because 'dotnet type definition is not used by Blazor'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\dotnet.d.ts' because 'dotnet type definition is not used by Blazor'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\dotnet.js.map' because 'source map file is not published'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\dotnet.native.js.symbols' because 'extension .symbols is not required.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\dotnet.runtime.js.map' because 'source map file is not published'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libSystem.Globalization.Native.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libSystem.IO.Compression.Native.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libSystem.Native.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libicudata.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libicui18n.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libicuuc.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-component-debugger-static.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-component-debugger-stub-static.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-component-diagnostics_tracing-static.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-component-diagnostics_tracing-stub-static.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-component-hot_reload-static.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-component-hot_reload-stub-static.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-component-marshal-ilgen-static.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-component-marshal-ilgen-stub-static.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-ee-interp.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-icall-table.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-profiler-aot.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-profiler-browser.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-wasm-eh-js.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmono-wasm-eh-wasm.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\libmonosgen-2.0.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\package.json' because 'package.json is not used by Blazor'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\wasm-bundled-timezones.a' because 'extension is .a is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\include\wasm\gc-common.h' because 'extension is .h is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\include\wasm\pinvoke.h' because 'extension is .h is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\include\wasm\wasm-config.h' because 'extension is .h is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\corebindings.c' because 'extension is .c is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\driver.c' because 'extension is .c is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\emcc-default.rsp' because 'extension is .rsp is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\emcc-link.rsp' because 'extension is .rsp is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\emcc-props.json' because 'emcc-props.json is not used by Blazor'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\pal_random.lib.js' because 'pal_random.lib.js is not used by Blazor'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\pinvoke.c' because 'extension is .c is not supported.'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\es6\dotnet.es6.extpost.js' because 'dotnet.es6.extpost.js is not used by Blazor'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\es6\dotnet.es6.lib.js' because 'dotnet.es6.lib.js is not used by Blazor'
                      ComputeWasmPublishAssets: Skipping asset 'C:\Users\sevin\.nuget\packages\microsoft.netcore.app.runtime.mono.browser-wasm\8.0.0-preview.7.23375.6\runtimes\browser-wasm\native\src\es6\dotnet.es6.pre.js' because 'dotnet.es6.pre.js is not used by Blazor'
      C:\Users\sevin\.nuget\packages\microsoft.net.sdk.webassembly.pack\8.0.0-preview.7.23375.6\build\Microsoft.NET.Sdk.WebAssembly.Browser.targets(400,5): Fehler : System.ArgumentException: Ein Element mit dem gleichen Schlüssel wurde bereits hinzugefügt.
         bei System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
         bei System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
         bei Microsoft.NET.Sdk.WebAssembly.ComputeWasmPublishAssets.GroupExistingStaticWebAssets(Dictionary`2 assemblyAssets, Dictionary`2 nativeAssets, Dictionary`2 satelliteAssemblyAssets, Dictionary`2 symbolAssets, Dictionary`2 compressedRepresentations)
         bei Microsoft.NET.Sdk.WebAssembly.ComputeWasmPublishAssets.Execute()
                      Die Ausführung der ComputeWasmPublishAssets-Aufgabe ist abgeschlossen -- FEHLER.
                      Die Erstellung des Ziels "ProcessPublishFilesForWasm" im Projekt "Mp3_Search.Blazor.UI.csproj" ist abgeschlossen -- FEHLER.
              Die Erstellung des Projekts "Mp3_Search.Blazor.UI.csproj" ist abgeschlossen -- FEHLER.
              Die Ausführung der MSBuild-Aufgabe ist abgeschlossen -- FEHLER.
      • Daniel RothMicrosoft employee 0

        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?

        • Sevin Ulukut 1

          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.

  • Raymond Tang 0

    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.

    ENV \
        DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \
        LC_ALL=en_US.UTF-8 \
        LANG=en_US.UTF-8
    
    RUN apk add --no-cache \
        icu-data-full \
        icu-libs
    

    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.

    # final stage/image
    FROM base AS final
    
    ENV \
        DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \
        LC_ALL=en_US.UTF-8 \
        LANG=en_US.UTF-8 
    
    
    USER root
    RUN apk add --no-cache \
        icu-data-full \
        icu-libs
    
    USER app
    WORKDIR /app
    COPY --from=publish /app/publish .
    
    ENTRYPOINT ["dotnet","./MyWeb.dll"]
    • Richard LanderMicrosoft employee 0

      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:

      $ docker run --rm mcr.microsoft.com/dotnet/aspnet:8.0-preview-alpine whoami
      root
      • Raymond Tang 0

        Here is the full dockerfile. I need to switch to root to install ICU as app user has no permission to do that.

        # https://hub.docker.com/_/microsoft-dotnet
        FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0-preview-alpine AS base
        USER app
        WORKDIR /app
        EXPOSE 8080
        EXPOSE 8081
        
        # Build the web application
        FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-preview-alpine AS build
        ARG BUILD_CONFIGURATION=Release
        WORKDIR /source
        
        # copy csproj and restore as distinct layers
        COPY src/*/*.csproj ./
        RUN for file in $(ls *.csproj); do mkdir -p "src/${file%.*}/" && mv $file "src/${file%.*}/"; done
        COPY test/*/*.csproj ./
        RUN for file in $(ls *.csproj); do mkdir -p "test/${file%.*}/" && mv $file "test/${file%.*}/"; done
        COPY *.sln ./
        RUN dotnet restore --use-current-runtime ./Kontext.sln
        COPY . ./
        RUN dotnet build ./src/Kontext.Web.Portals/Kontext.Web.Portals.csproj -c $BUILD_CONFIGURATION -o /app/build --self-contained false --no-restore
        
        
        # Publish 
        FROM build AS publish
        RUN dotnet publish ./src/Kontext.Web.Portals/Kontext.Web.Portals.csproj -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false --self-contained false --no-restore
        
        # final stage/image
        FROM base AS final
        
        ENV \
            DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \
            LC_ALL=en_US.UTF-8 \
            LANG=en_US.UTF-8 \
            ASPNETCORE_HTTP_PORT=8080
        
        ENV ASPNETCORE_URLS="http://*:${ASPNETCORE_HTTP_PORT}"
        
        USER root
        RUN apk add --no-cache \
            icu-data-full \
            icu-libs
        
        USER app
        WORKDIR /app
        COPY --from=publish /app/publish .
        
        ENTRYPOINT ["dotnet","./Kontext.Web.Portals.dll"]

Feedback usabilla icon