ASP.NET Core 10 – What’s new!

Welcome back to my blog,

Today we’re diving deep into the key highlights from .NET Conf covering version 10 (LTS) of Microsoft’s web-focused framework.

This will be a multi-part series—this article focuses exclusively on ASP.NET Core 10 updates, while subsequent posts will cover C#, Entity Framework, and other areas.

For additional details, check out the official Microsoft documentation page.

Automatic memory pool eviction

Every year, during .NET announcement events, significant improvements in performance and resource consumption are highlighted. This release is no exception, and in this context, automatic memory management (Automatic memory pool eviction) has been enhanced for long-running applications: previously, objects that were no longer in use would sometimes be retained in memory, and this new version of the framework addresses this issue.

Blazor

This section will significantly expand this article, as Blazor has received numerous incremental improvements. In a Node.js-dominated landscape, can Blazor carve out its niche? Hard to say, but Cupertino’s vision is clear: build a complete ecosystem from frontend all the way through backend orchestration via Aspire.

Let’s cut to the chase and see what’s new!

General behaviors

Scripts as static assets

Prior to .NET 10, JavaScript files were embedded within DLLs during compilation. With .NET 10, they’ve been decoupled from this mechanism and now exist as external JavaScript files (automatically compressed with fingerprinted filenames for cache-busting).

Static asset preloading

All links in the page header will now be preloaded by the browser before the initial page is fully downloaded and parsed, enabling better page load performance. This applies specifically to Server-Side-Rendering applications.

For WebAssembly applications, resources now have higher download and caching priority compared to other assets in two scenarios:

  • When using the rel="preload" attribute in the resource link:
    • <link rel="preload" [...] />
  • When the OverrideHtmlAssetPlaceholders property (in the .csproj file) is set to true:
    • <PropertyGroup> <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders> </PropertyGroup>

Forms and components

Validation

Validation has been enhanced with support for nested objects and element collections. To enable it, add validation in program.cs (builder.Services.AddValidation()) and apply the [ValidatableType] attribute to classes you want to validate. Example:

C#
//Program.cs
builder.Services.AddValidation();

...

[ValidatableType]
public class BeltAccessories {
  public int Id { get; set; }
  public Lightsaber LightSaber { get; set; }
}

public class Lightsaber
{
    [Required(ErrorMessage = "ID of lightsaber is required.")]
    public Guid Id { get; set; }

    [Range(0,10000)]
    public int NumberOfFights { get; set; } = 0;
 
    //Ignore validation for this specific item
    [SkipValidation]
    public KyberCristal mainCristal { get; set; }
}

Additionally, you can now build custom validation methods by extending the IValidatableObject.Validate interface (even placing it in a separate assembly from your app—perhaps in a corporate validation library shared across multiple projects).

Hidden fields

Finally! 😅 Instead of writing hidden fields using HTML tags, we can now use the <InputHidden/> tag:

C#
<EditForm Model="BeltAccessories" OnValidSubmit="Submit" FormName="BeltAccessoriesForm">
  [...]
  <InputHidden id="my-belt-id" @bind-Value="Id" />
  <button type="submit">Submit</button>
</EditForm>

Session management

In previous Blazor versions, user sessions would drop in various scenarios—prolonged connection losses, throttled browser tabs going dormant, switching apps on mobile devices, etc.

This new version addresses these issues (Server-Side-Rendering mode only) by maintaining sessions longer and restoring user state even in these situations.

Data persistence

.NET 8 introduced “enhanced navigation” for Server-Side-Rendering, delivering a user experience similar to Single Page Applications. In SSR mode, each page must be processed server-side before being returned to the client, whereas enhanced navigation reloads only the necessary portions of the page.

With that context established, let’s discuss data persistence: enhanced navigation now includes the ability to persist data—essentially a session cache for maintaining certain states or loaded objects. By default, this occurs only on initial page load, but can be customized via the RestoreBehavior parameter of the PersistentState data annotation:

C#
//his list is saved and persisted—relatively static data, avoiding regeneration each time
[PersistentState(AllowUpdates = true)]
public Jedi[]? AvailableJedi{ get; set; }

//No pre-rendered data when opening the page
[PersistentState(RestoreBehavior = RestoreBehavior.SkipInitialValue)]
public BecomeJediForm[]? BecomeJediForm{ get; set; }

//Persisted data but reloaded on each page load
[PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)]
public BecomeJediForm[]? BecomeJediFormExample{ get; set; }

protected override async Task OnInitializedAsync()
{
    Forecasts ??= await JediService.GetavailablesJedis();
}

Beyond these out-of-the-box approaches, you can extend functionality with custom callbacks via PersistentComponentState.RegisterOnRestoring.

This approach proves useful for preserving form state, shopping cart items, or user-specific calculated lists.

Blazor WebAssembly (WASM)

When we reference WASM, we’re talking about Blazor’s WebAssembly mode, which executes C#-written applications directly in the browser. Beyond the application itself, a .NET runtime is also downloaded, requiring no backend to function.

Hot reload

WebAssembly’s biggest news is undoubtedly Hot Reload support (the ability to modify page code and see changes live without stopping and restarting the application).

New projects will have this enabled by default for Debug configuration. To modify this setting, adjust the .csproj property (setting true/false):

project.csproj
<PropertyGroup>
  <WasmEnableHotReload>true</WasmEnableHotReload>
</PropertyGroup>

Environment variable

Continuing with changes, another notable shift: appsettings.json is no longer used for environment variable configuration. You’ll now set this in the .csproj file:

Project.csproj
...
<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>
...

Fingerprint javascript

.NET 9 introduced JavaScript file fingerprinting (randomized filenames), similar to other frameworks. Version 10 extends this mechanism to WebAssembly applications via a tag inserted in the wwwroot directory’s index.html and a .csproj property:

wwwroot/index.html
<body>
    //...
    
    <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
    <script src="js/my-script#[.{fingerprint}].js"></script>
    
    //...
</body>

</html>
Project.csproj
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
  </PropertyGroup>
</Project>

Quickgrid

QuickGrid is a tabular component introduced experimentally in .NET 7, designed for performant data visualization with IQueryable interface support for database data manipulation.

As a relatively new component, there are some updates. First, you can now specify a CSS class directly on table rows via the RowClass parameter:

C#
<QuickGrid [...] RowClass="GetCustomCssClassFunction">
    //...
</QuickGrid>

@code {
    private string GetCustomCssClassFunction(MyGridItem lightsaber) =>
        lightsaber.IsAssigned? "lsbr-assigned" : "lsbr-not-assigned";
}

Another addition concerns the column settings menu—you can now hide it using the HideColumnOptionsAsync() function.

OpenApi

OpenAPI 3.1 support

The OpenAPI standard is a specification for describing, designing, and documenting REST APIs. It’s a file (YAML or more commonly JSON) that describes API structure, endpoints, parameters, authentication, etc.

The transition from version 3.0 to 3.1 introduces significant improvements with full compatibility with the latest JSON Schema version, resolving the 3.0 disconnect between “what’s permitted in JSON Schema” and “what’s permitted in OpenAPI.”

When adding OpenAPI to our services, we can now specify the API version:

C#
builder.Services.AddOpenApi(options => {
     options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1; 
});

Additionally, ASP.NET now supports OpenAPI document generation in YAML. Simply change the file extension in the .MapOpenApi() method (in program.cs):

Program.cs
if (app.Environment.IsDevelopment()) {   
    app.MapOpenApi("/openapi/{documentName}.yaml"); 
}

Response descriptions

You can now describe endpoint meanings via the [Produces], [ProducesResponseType], and [ProducesDefaultResponseType] attributes, with descriptions exported to the OpenAPI document:

C#
[HttpGet(Name = "get-status")]
[ProducesResponseType<AstroDroidStatus>(StatusCodes.Status200OK,Description = "Status of mechanical astrodroid")]
public AstroDroidStatus Get()
{
    ...
}

AOT

AOT applications are natively compiled for the deployment environment, with big advantages in terms of size and performance. OpenAPI support has been added to the webapiaot template.

Minimal API

Minimal APIs are emerging as the preferred approach for writing endpoints more simply, cleanly, and concisely, centralizing logic in services and avoiding the temptation to embed business logic in traditional endpoints.

Validation

You can now validate API input parameters via attributes. First, enable the feature in program.cs:

Program.cs
builder.Services.AddValidation();

Then apply your attributes:

C#
[HttpPost(Name = "lightsaber")]
public AstroDroidActionResponse ThrowLightsaber(
    [Required] string securityCode,
    [Range(1, 100)] int ThrowForce
    )
)
{
    ...
}

The same logic applies when parameters are classes or records with various data annotations on properties: passing a Lightsaber object to a minimal API will validate all properties according to their configuration:

C#
public class Lightsaber
{
    [Required]
    public Guid Id { get; set; }

    [Range(0,10000)]
    public int NumberOfFights { get; set; } = 0;

    [Required]
    public KyberCristal mainCristal { get; set; }
}

//controller
[HttpPost]
public ResponseResult Add(Lightsaber lightsaber)
)
{
    ...
}

Beyond standard validations, you can create custom validation attributes by implementing the IValidatableObject interface.

⚠️Important note

API validation has been moved to the Microsoft.Extensions.Validation package.

Null properties on complex objects

When passing complex objects via the [FromForm] data annotation, any empty value associated with a nullable property now maps to null. In the example below, the ValidUntil field will map to null:

C#
app.MapPost("/messages", ([FromForm] Message newMessage) => TypedResults.Ok(newMessage));

public class Message
{
  public string Message { get; set; }
  public string Author { get; set; }
  public DateOnly? ValidUntil { get; set; } //--> Mappta come null
}

/*

  payload:
  {
    'title': 'Aiutami Obi One Kenobi, sei la mia unica speranza',
    'author': 'Leila'
    'validUntil': ''
  }
  
*/

Metrics

Metrics have been improved across several aspects:

  • AuthN and AuthZ events
  • ASP.NET Core Identity
  • Memory eviction
  • Blazor apps (for better tracing and observability of Blazor applications)

Security

One of the biggest (and most requested) changes addresses the long-standing redirect to the login endpoint that occurred when accessing a cookie-protected API endpoint without credentials.

In an API context, redirects make no sense—you can now choose to return 403 or 401 instead. The previous behavior can be re-enabled, though it’s likely this option will be removed in future framework releases.

Passkey

Passkey is the new approach for accessing websites as a replacement for good but old passwords, and they work with a key pair (public and private): logging in with a fingerprint is an example of using a passkey instead of typing a password. In .NET 10, support for this authentication method has been introduced, based on the WebAuthn and FIDO2 standards. If you want to see how it works, you can check out the Blazor Web App project template.

Conclusion

In this article, we’ve explored the web-focused innovations introduced with .NET 10. If you enjoyed this post, remember there are additional articles covering other areas of framework improvements.

Until next time!

Share this article
Shareable URL
Prev Post

C# 14

Next Post

EF Core 10: .leftJoin() and .rightJoin() in LINQ

Read next

Hybrid Cache released!

Hey devs! Great news – .NET 9.0.3 just dropped a few days ago, and with it comes the long-awaited official…
Hybrid cache released header image