ASP.NET Core updates in .NET 7 Preview 1

Daniel Roth

.NET 7 Preview 1 is now available!. This is the first preview of the next major version of .NET, which will include the next wave of innovations for web development with ASP.NET Core.

In .NET 7 we plan to make broad investments across ASP.NET Core. Below are some of the areas we plan to focus on:

  • Performance: .NET 6 contained many performance improvements for ASP.NET Core, and we’ll do work to make ASP.NET Core even faster and more efficient in .NET 7.
  • HTTP/3: HTTP/3 support shipped as a preview feature in .NET 6. For .NET 7, we want to finish it and make it a supported feature that’s enabled by default. In future previews, you can expect to see advanced TLS features and more performance improvements in our HTTP/3 support.
  • Minimal APIs: Add support for endpoint filters and route grouping as core primitives for minimal APIs. Also simplify authentication and authorization configurations for APIs in general.
  • gRPC: We’re investing in gRPC JSON transcoding. This feature allows gRPC services to be called like RESTful HTTP APIs with JSON requests and responses.
  • SignalR: Add support for strongly-typed clients and returning results from client invocations.
  • Razor: We’ll make various improvements to the Razor compiler to improve performance, resiliency, and to facilitate improved tooling.
  • Blazor: After finishing Blazor Hybrid support for .NET MAUI, WPF, and Windows Forms, we’ll make broad improvements to Blazor including:
    • New .NET WebAssembly capabilities: mixed-mode AOT, multithreading, web crypto.
    • Enhanced Hot Reload support.
    • Data binding improvements.
    • More flexible prerendering.
    • More control over the lifecycle of Blazor Server circuits.
    • Improved support for micro frontends.
  • MVC: Improvements to endpoint routing, link generation, and parameter binding.
  • Orleans: The ASP.NET Core and Orleans teams are investigating ways to further align and integrate the Orleans distributed programming model with ASP.NET Core. Orleans 4 will ship alongside .NET 7 and focuses on simplicity, maintainability, and performance, including human readable stream identities and a new optimized, version-tolerant serializer.

For more details on the specific ASP.NET Core work planned for .NET 7 see the full ASP.NET Core roadmap for .NET 7 on GitHub.

.NET 7 Preview 1 is the first of many .NET 7 preview releases in preparation for the .NET 7 release in November 2022.

I joined James Montemagno on a recent episode of On .NET to break down all of what is coming in .NET 7 and ASP.NET Core in .NET 7:

Here’s a summary of what’s new in this preview release:

  • Minimal API improvements:
    • IFormFile and IFormFileCollection Support
    • Bind the request body as a Stream or PipeReader
    • JSON options configuration
  • SignalR client source generator
  • Support for nullable models in MVC views and Razor Pages
  • Use JSON property names in validation errors
  • Improved console output for dotnet watch
  • Configure dotnet watch to always restart for rude edits
  • Use dependency injection in a ValidationAttribute
  • Faster header parsing and writing
  • gRPC JSON transcoding

Get started

To get started with ASP.NET Core in .NET 7 Preview 1, install the .NET 7 SDK.

If you’re on Windows using Visual Studio, we recommend installing the latest Visual Studio 2022 preview. Visual Studio for Mac support for .NET 7 previews isn’t available yet but is coming soon.

To install the latest .NET WebAssembly build tools, run the following command from an elevated command prompt:

dotnet workload install wasm-tools

Upgrade an existing project

To upgrade an existing ASP.NET Core app from .NET 6 to .NET 7 Preview 1:

  • Update the target framework for your app to net7.0.
  • Update all Microsoft.AspNetCore.* package references to 7.0.0-preview.1.*.
  • Update all Microsoft.Extensions.* package references to 7.0.0-preview.1.*.

See also the full list of breaking changes in ASP.NET Core for .NET 7.

Minimal API improvements

IFormFile and IFormFileCollection Support

You can now handle file uploads in minimal APIs using IFormFile and IFormFileCollection:

app.MapPost("/upload", async(IFormFile file) =>
{
    using var stream = System.IO.File.OpenWrite("upload.txt");
    await file.CopyToAsync(stream); 
});
app.MapPost("/upload", async (IFormFileCollection myFiles) => { ... });

Using this feature with authentication requires anti-forgery support, which isn’t yet implemented. Anti-forgery support for minimal APIs is on our roadmap for .NET 7. Binding to IFormFile or IFormFileCollection when the request contains an Authorization header, a client certificate, or a cookie header is currently disabled. This limitation will be addressed as soon as we complete the work on anti-forgery support.

Thanks to @martincostello for contributing this feature.

Bind the request body as a Stream or PipeReader

You can now bind the request body as a Stream or PipeReader to efficiently support scenarios where the user has to ingest data and either store it to a blob storage or enqueue the data to a queue provider (Azure Queue, etc.) for later processing by a worker or cloud function. The following example shows how to use the new binding:

app.MapPost("v1/feeds", async (QueueClient queueClient, Stream body, CancellationToken cancellationToken) =>
{
    await queueClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
    await queueClient.SendMessageAsync(await BinaryData.FromStreamAsync(body), cancellationToken: cancellationToken);
});

When using the Stream or PipeReader there are a few things to take into consideration:

  • When ingesting data, the Stream will be the same object as HttpRequest.Body.
  • The request body isn’t buffered by default. After the body is read, it’s not rewindable (you can’t read the stream multiple times).
  • The Stream/PipeReader are not usable outside of the minimal action handler as the underlying buffers will be disposed and/or reused.

JSON options configuration

We’re introducing a new and cleaner API, ConfigureRouteHandlerJsonOptions, to configure JSON options for minimal API endpoints. This new API avoids confusion with Microsoft.AspNetCore.Mvc.JsonOptions.

var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureRouteHandlerJsonOptions(options =>
{
    //Ignore Cycles
    options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; 
});    

SignalR client source generator

We’ve added a new client source generator for SignalR thanks to a contribution by @mehmetakbulut.

The SignalR client source generator generates strongly-typed sending and receiving code based on interfaces that you define. You can reuse the same interfaces from strongly-typed SignalR hubs on the client in place of the loosely-typed .On("methodName", ...) methods. Similarly, your hub can implement an interface for its methods and the client can use that same interface to call the hub methods.

To use the SignalR client source generator:

[AttributeUsage(AttributeTargets.Method)]
internal class HubServerProxyAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method)]
internal class HubClientProxyAttribute : Attribute
{
}
  • Add a static partial class to your project and write static partial methods with the [HubClientProxy] and [HubServerProxy] attributes
internal static partial class MyCustomExtensions
{
    [HubClientProxy]
    public static partial IDisposable ClientRegistration<T>(this HubConnection connection, T provider);

    [HubServerProxy]
    public static partial T ServerProxy<T>(this HubConnection connection);
}
  • Use the partial methods from your code!
public interface IServerHub
{
    Task SendMessage(string message);
    Task<int> Echo(int i);
}

public interface IClient
{
    Task ReceiveMessage(string message);
}

public class Client : IClient
{
    // Equivalent to HubConnection.On("ReceiveMessage", (message) => {});
    Task ReceiveMessage(string message)
    {
        return Task.CompletedTask;
    }
}

HubConnection connection = new HubConnectionBuilder().WithUrl("...").Build();
var stronglyTypedConnection = connection.ServerProxy<IServerHub>();
var registrations = connection.ClientRegistration<IClient>(new Client());

await stronglyTypedConnection.SendMessage("Hello world");
var echo = await stronglyTypedConnection.Echo(10);

Support for nullable models in MVC views and Razor Pages

We enabled defining a nullable page or view model to improve the experience when using null state checking with ASP.NET Core apps:

@model Product?

Use JSON property names in validation errors

When model validation produces a ModelErrorDictionary it will by default use the property name as the error key ("MyClass.PropertyName"). Model property names are generally an implementation detail, which can make them difficult to handle from single-page apps. You can now configure validation to use the corresponding JSON property names instead with the new SystemTextJsonValidationMetadataProvider (or NewtonsoftJsonValidationMetadataProvider when using Json.NET).

services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider())
});

Improved console output for dotnet watch

We cleaned up the console output from dotnet watch to better align with the log out of ASP.NET Core and to stand out with ๐Ÿ˜ฎemojis๐Ÿ˜.

Here’s an example of what the new output looks like:

C:BlazorApp> dotnet watch
dotnet watch ๐Ÿ”ฅ Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload.
  ๐Ÿ’ก Press "Ctrl + R" to restart.
dotnet watch ๐Ÿ”ง Building...
  Determining projects to restore...
  All projects are up-to-date for restore.
  You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
  BlazorApp -> C:UsersdarothDesktopBlazorAppbinDebugnet7.0BlazorApp.dll
dotnet watch ๐Ÿš€ Started
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7148
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5041
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:UsersdarothDesktopBlazorApp
dotnet watch โŒš File changed: .PagesIndex.razor.
dotnet watch ๐Ÿ”ฅ Hot reload of changes succeeded.
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
dotnet watch ๐Ÿ›‘ Shutdown requested. Press Ctrl+C again to force exit.

Configure dotnet watch to always restart for rude edits

Configure dotnet watch to always restart without a prompt for rude edits (edits that can’t be hot reloaded) by setting the DOTNET_WATCH_RESTART_ON_RUDE_EDIT environment variable to true.

Inject services into custom validation attributes in Blazor

You can now inject services into custom validation attributes in Blazor. Blazor will setup the ValidationContext so it can be used as a service provider.

public class SaladChefValidatorAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var saladChef = validationContext.GetRequiredService<SaladChef>();
        if (saladChef.ThingsYouCanPutInASalad.Contains(value.ToString()))
        {
            return ValidationResult.Success;
        }
        return new ValidationResult("You should not put that in a salad!");
    }
}

// Simple class configured as a service for dependency injection
public class SaladChef
{
    public string[] ThingsYouCanPutInASalad = { "Strawberries", "Pineapple", "Honeydew", "Watermelon", "Grapes" };
}

Thank you @MariovanZeist for this contribution!

Faster header parsing and writing

We made several improvements to the performance of header parsing and writing for HTTP/2 and HTTP/3. See the following pull requests for details:

gRPC JSON transcoding

gRPC JSON transcoding allows gRPC services to be used like a RESTful HTTP APIs. Once configured, gRPC JSON transcoding allows you to call gRPC methods with familiar HTTP concepts:

  • HTTP verbs
  • URL parameter binding
  • JSON requests/responses

Of course gRPC can continue to be used as well. RESTful APIs for your gRPC services. No duplication!

ASP.NET Core has experimental support for this feature using a library called gRPC HTTP API. For .NET 7 we plan to make this functionality a supported part of ASP.NET Core. This functionality isn’t included with .NET 7 yet, but you can try out the existing experimental packages. For more information, see the gRPC HTTP API getting started documentation.

Give feedback

We hope you enjoy this preview release of ASP.NET Core in .NET 7 and that you’re as excited about about our roadmap for .NET 7 as we are! We’re eager to hear about your experiences with this release and your thoughts on the roadmap. Let us know what you think by filing issues on GitHub and commenting on the roadmap issue.

Thanks for trying out ASP.NET Core!

32 comments

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

  • Jon Miller 0

    Unfortunately, low-tech Web Forms from 20 years ago is still easier and more productive. This stuff all looks like gibberish to me. You should have just fixed Web Forms and added robust UI controls like what Telerik offers. Instead, you parroted Ruby On Rails and did little than make a lot of busy work and make things more complicated.

    • Daniel RothMicrosoft employee 0

      Web Forms is certainly still a great web framework and is still supported and used by 100s of thousands of developers. But enabling an additional level of flexibility and performance for .NET web apps required a new approach. Thats why we rewrote ASP.NET Core from the ground up. Admittedly, this did come at the cost of some additional complexity, but the cost savings can be huge. For example, check out the efficiency gains Azure Active Directory achieved by moving to .NET 6: https://devblogs.microsoft.com/dotnet/azure-active-directorys-gateway-is-on-net-6-0/.

      • Federico Dutto 0

        hello daniel, could be improved some help so that from the model (or database) a crud component is generated in blazor automatically (scaffolding blazor CRUD?), as it was with web form with formview or gridview. This would further accelerate development. Best regards

        • Daniel RothMicrosoft employee 0

          Hi Federico. Thanks for this suggestion! We’ve had some discussions on how to help Blazor users more easily connect to data but we don’t have any concrete plans at this time. If you have a specific proposal in mind, it would be great if you could propose an approach on GitHub: https://github.com/dotnet/aspnetcore/issues/new

  • Guruprasad K 0

    Nice. I convinced my whole team to be on .NET MAUI + Blazor path recently in a trust that we can do better as we go forward and happy to see the coming up features / road map, in particular Blazor Wasm performance related: multi threading (currently struggling with performance).

    Related question: We have a Blazor Wasm project running and want to reuse them in MAUI app.
    Could not find any good documentation about using MAUI + Blazor Wasm (project structure sharing example). How do we share razor components?
    Tried unsuccessfully with RCL (Razor component lib) as could not figure out if Main.razor or which others can be shared and where to put them? Any guides / suggestions for me and the .NET community? Appreciate your help

    • Daniel RothMicrosoft employee 0

      Hi Guruprasad. I’m happy to hear that our proposed .NET 7 plans resonate with you! To share your Blazor components with .NET MAUI and Blazor WebAssembly you should put them in a shared Razor class library that can be referenced by both apps. It sounds like you tried that but hit some issues. If you can share more details about what issues you hit, I’d be happy to take a look. The easiest way to share these details is probably to just open a GitHub issue: https://github.com/dotnet/aspnetcore/issues/new.

  • Stevie White (Dragnilar) 0

    @Daniel

    Great to see the continued effort on Blazor’s success. At work we have a server side Blazor site in production which we are slowly but surely using to replace our legacy internal CRM/Project Management/etc system. The parts that we have released so far have had incredibly positive feedback and we’ve found Blazor fun to use.

    In addition, I have made a Web Assembly Blazor project (a browser extension that replaces the new tab page for Edge that provides most of the same functionality but with the ability to show a nearly unlimited number of top sites). That alone has been an entertaining endeavor.

    Thanks again for all your hard work, you, Steve and the rest of the team are making the world a better place. ๐Ÿ’–๐Ÿ’ž

    Edit: And I’m interested in seeing the data binding improvements in .NET 7. Hopefully, that can cut some of the spots where we’re always having to call “StateHasChanged()”.

    • Daniel RothMicrosoft employee 0

      Hi Stevie. It’s great to hear that Blazor is working well for you! As for the data binding improvements, take a look at https://github.com/dotnet/aspnetcore/issues/39837, which has a design proposal for get/set/after modifiers for bind. If there are other data binding improvements you’re interested in, be sure to give the corresponding GitHub issue a ๐Ÿ‘ and comment about your scenarios and requirements.

  • David Cuccia 0

    Hi Daniel, exciting times. Can you comment at a high level on if/how there will be alignment between Dapr and the planned .Net 7 integration of Orleans? Was excited to learn about the serverless Azure Container Apps that use Dapr under the hood, and wondering how Orleans dovetails with this stack.

    • Brady GasterMicrosoft employee 0

      I can comment on this, and appreciate the enthusiastic interest. Orleans team and DAPR tooling team have been collaborating on ideas where the two products can overlap and complement .NET cloud-native developers. For example, I have a sample app running a suite of Orleans silos, but one of those silos is a minimal API that clients can call outside of the Orleans cluster context. The REST API essentially provides an interface to external clients, so any language will work. My cluster can scale, my API can scale – whatever. But the front end of this app is written in Blazor WASM or in React, so I need to REST API for the client developer to access the back-end grains. In that case, DAPR is great, as it provides fantastic service discovery mechanisms my front-end container can use to connect to my back-end API-topped silo. My front end calls the DAPR sidecar running alongside it, and that sidecar directs HTTP API calls to the minimal API atop one of those Orleans silos.

      Note – this is all entirely possible on Azure Container Apps today. I have a few sample repositories in my GitHub (@bradygaster) I’ve recently updated that walk through some of those scenarios. You’re welcome to ping me in the issues of any of those repos or on Twitter (also @bradygaster). Thanks!

  • apn pgc 0

    Thank you so much for the detailed post. I was looking for:

    app.MapPost("v1/feeds", async (QueueClient queueClient, Stream body, CancellationToken cancellationTokenapnpgc) =>
    {
        await queueClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
        await queueClient.SendMessageAsync(await BinaryData.FromStreamAsync(body), cancellationToken: cancellationToken);
    });

    and finally got it from here.
    Thanks again

Feedback usabilla icon