ASP.NET Core updates in .NET 6 Preview 7

Daniel

.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

image

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; }
}

Blazor search query

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 DistributedContextPropagators 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!

83 comments

Comments are closed. Login to edit/delete your existing comments

  • Tal Orlinsky

    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)

    • David FowlerMicrosoft employee

      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.

      • Tal Orlinsky

        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

        • David FowlerMicrosoft employee

          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.

  • Yadel Lopez

    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 onprogress event, at least this is async and non-blocking, but I’m not sure about other limitations this approach can have. What do you think the best approach is? The built-in InputFile, Syncfusion -out of the picture for now-, or XHR?

    • Daniel RothMicrosoft employee

      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.

      • Yadel Lopez

        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 uses WebSockets -let’s call it SocketClient- for streaming could have better throughput?

        • Daniel RothMicrosoft employee

          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.

          • Yadel Lopez

            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.

  • Tal Orlinsky

    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 🙂

  • Bhrugen Patel

    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?

    • Daniel RothMicrosoft employee

      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.

  • Taum

    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!

  • Diego

    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.

  • Jake C

    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>?