.NET 8 Updates
JSON Improvements
Missing member handling
It’s now possible to configure object deserialization behavior, whenever the underlying JSON payload includes properties
that cannot be mapped to members of the deserialized POCO type. This can be controlled by setting a
JsonUnmappedMemberHandling
value, either as an annotation on the POCO type itself, globally on JsonSerializerOptions
or
programmatically by customizing the JsonTypeInfo
contract for the relevant types:
JsonSerializer.Deserialize<MyPoco>("""{"Id" : 42, "AnotherId" : -1 }""");
// JsonException : The JSON property 'AnotherId' could not be mapped
// to any .NET member contained in type 'MyPoco'.
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
public class MyPoco
{
public int Id { get; set; }
}
Benefits
Overall, JsonUnmappedMemberHandling
provides you with a level of control and flexibility when dealing with missing or
unexpected members during JSON deserialization, ensuring that your application can handle such scenarios gracefully.
Interface hierarchy support
System.Text.Json
now supports serializing properties from interface hierarchies:
IDerived value = new Derived { Base = 0, Derived =1 };
JsonSerializer.Serialize(value); // {"Base":0,"Derived":1}
public interface IBase
{
public int Base { get; set; }
}
public interface IDerived : IBase
{
public int Derived { get; set; }
}
public class Derived : IDerived
{
public int Base { get; set; }
public int DerivedProp { get; set; }
}
Benefits
The support for serializing properties from interface hierarchies in System.Text.Json provides benefits such as increased flexibility and extensibility in designing and implementing data models, allowing for more modular and reusable code. It also simplifies the serialization process by automatically including properties defined in interfaces, reducing the need for manual mapping or custom serialization logic.
JsonSerializerOptions.MakeReadOnly()
and JsonSerializerOptions.IsReadOnly
APIs
The JsonSerializerOptions
class has always been using freezable semantics, but up until now freezing could only be done
implicitly by passing the instance to one of the JsonSerializer
methods. The addition of the new APIs makes it possible
for users to explicitly control when their JsonSerializerOptions
instance should be frozen:
public class MySerializer
{
private JsonSerializerOptions Options { get; }
public MySerializer()
{
Options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
Converters =
{
new MyCustomConverter()
}
};
// Make read-only before exposing the property.
Options.MakeReadOnly();
}
}
Benefits
The JsonSerializerOptions.IsReadOnly
property allows you to check whether a JsonSerializerOptions
instance is read-only,
providing a convenient way to determine if the options can be modified or if they are in a finalized state. These APIs
promote code stability, enhance security, and help prevent accidental modifications to serialization settings, ensuring
consistent and predictable behavior throughout your application.
System.ComponentModel.DataAnnotations Extensions
RequiredAttribute.DisallowAllDefaultValues
The RequiredAttribute now allows validating that structs do not equal their default values.
For example:
[Required(DisallowAllDefaultValues = true)]
public Guid MyGuidValue { get; set; }
This example will fail validation if its value equals Guid.Empty.
RangeAttribute exclusive bounds
Users can now specify exclusive bounds in their range validation:
[Range(0d, 1d, MinimumIsExclusive = true, MaximumIsExclusive = true)]
public double Sample { get; set; }
This attribute accepts any values in the open interval but rejects the boundary values 0 and 1.
LengthAttribute
The LengthAttribute can now be used to set both lower and upper bounds for strings or collections:
[Length(10, 20)] // Require at least 10 elements and at most 20 elements.
public ICollection<int> Values { get; set; }
AllowedValuesAttribute and DeniedValuesAttribute
These attributes can be used to specify allow lists and deny lists for validating a property:
[AllowedValues("apple", "banana", "mango")]
public string Fruit { get; set; }
[DeniedValues("pineapple", "anchovy", "broccoli")]
public string PizzaTopping { get; set; }
SDK: Enhancements to Metrics APIs
Dependency Injection (DI) Friendly metrics APIs
Introduced in Preview 5 and brings a number of improvements and updates to metrics APIs that covers additional use
cases. New IMeterFactory
interface, which can be registered in DI containers and used to create Meter
objects in an
isolated manner.
// service is the DI IServiceCollection
// Register the IMeterFactory to the DI container
// using the default meter factory implementation.
services.AddMetrics();
Consumers can now use the code below to create a meter factory and use it to easily create a new Meter
object.
var meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();
MeterOptions options = new MeterOptions("MeterName")
{
Version = "version",
};
Meter meter = meterFactory.Create(options);
Benefits
Dependency Injection (DI) friendly metrics APIs provide benefits such as improved testability, modularity, and flexibility in monitoring and measuring application performance. By using DI to inject metric dependencies, it becomes easier to replace or mock metrics implementations during testing, decoupling metric collection from the business logic and promoting separation of concerns.
IExceptionHandler
The exception handler middleware is an existing component used to catch un-expected request processing exceptions and
return a user-friendly error page without leaking implementation details. IExceptionHandler
is a new interface for
services that can be resolved and invoked by the exception handler middleware. It gives the developer a callback that
allows handling known exceptions in a central location.
IExceptionHandler
‘s are registered by calling IServiceCollection.AddExceptionHandler<T>
. Multiple can be added, and
they’ll be called in the order registered. If an exception handler handles a request, it can return true to stop
processing. If an exception isn’t handled by any exception handler, then control falls back to the old behavior and
options from the middleware. Different metrics and logs will be emitted for handled versus unhandled exceptions.
public interface IExceptionHandler
{
ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken);
}
Benefits
The introduction of the new IExceptionHandler
interface in .NET 8 provides benefits by allowing developers to have
fine-grained control over exception handling and customization in their applications. It enables the implementation of
custom exception handling logic, such as logging, error reporting, or recovery strategies, providing a centralized and
extensible approach to handle exceptions consistently across the application.
API authoring
Support for generic attributes
Attributes that previously required a System.Type
parameter are now available in cleaner generic variants. This is made
possible by support for generic attributes in C# 11. For example, the syntax for annotating the response type of an
action can be modified as follows:
[ApiController]
[Route("api/[controller]")]
public class TodosController : Controller
{
[HttpGet("/")]
// Before: [ProducesResponseType(typeof(Todo), StatusCodes.Status200OK)]
[ProducesResponseType<Todo>(StatusCodes.Status200OK)]
public Todo Get() => new Todo(1, "Write a sample", DateTime.Now, false);
}
Generic variants are supported for the following attributes:
- [ProducesResponseType
] - [Produces
] - [MiddlewareFilter
] - [ModelBinder
] - [ModelMetadataType
] - [ServiceFilter
] - [TypeFilter
]
Benefits
The support for generic attributes in API authoring in .NET 8 brings benefits by enabling the creation of highly customizable and type-safe attribute-based metadata annotations for APIs. This allows for more flexible and reusable code, promotes better code organization, and simplifies the implementation of cross-cutting concerns such as validation, authorization, and configuration.
Introducing the options validation source generator
To reduce startup overhead and improve validation feature set in .NET 8 was introduced the source code generator that implements the validation logic.
Options validation usage
public class FirstModelNoNamespace
{
[Required]
[MinLength(5)]
public string P1 { get; set; } = string. Empty;
[Microsoft.Extensions.Options
.ValidateObjectMembers(typeof(SecondValidatorNoNamespace))]
public SecondModelNoNamespace? P2 { get; set; }
[Microsoft.Extensions.Options.ValidateObjectMembers]
public ThirdModelNoNamespace? P3 { get; set; }
}
public class SecondModelNoNamespace
{
[Required]
[MinLength(5)]
public string P4 { get; set; } = string. Empty;
}
public class ThirdModelNoNamespace
{
[Required]
[MinLength(5)]
public string P5 { get; set; } = string.Empty;
}
[OptionsValidator]
public partial class FirstValidatorNoNamespace
: IValidateOptions<FirstModelNoNamespace>
{
}
[OptionsValidator]
public partial class SecondValidatorNoNamespace
: IValidateOptions<SecondModelNoNamespace>{}
If the app is using dependency injection, it can inject the validation using the following pattern.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(builder.Configuration
.GetSection(...));
builder.Services
.AddSingleton<IValidateOptions<FirstModelNoNamespace>,
FirstValidatorNoNamespace>();
builder.Services
.AddSingleton<IValidateOptions<SecondModelNoNamespace>,
SecondValidatorNoNamespace>();
Benefits
The options validation source generator in .NET 8 provides automated validation of configuration options at compile-time, ensuring early detection of errors and reducing runtime issues caused by misconfigured options. This leads to improved code reliability, faster debugging, and enhanced developer productivity by eliminating manual validation and providing clear error messages.
Things we’ve missed
Missed things from Preview 1 & 2
- Native AOT; -.NET Container images;
- Utility methods for working with randomness (GetItems, Shuffle);
- New performance-focused types in the core libraries (System.Collections.Frozen, IndexOfAny);
- Blazor updates;
- HTTP/3 enabled by default;
- HTTP/2 over TLS (HTTPS) support on macOS;
- IPNetwork.Parse and TryParse;
- New analyzer to detect multiple FromBody attributes;
- New APIs in ProblemDetails to support more resilient integrations;
- New IResettable interface in ObjectPool
Missed things from Preview 3 & 4
- ASP.NET Core support for native AOT;
- Blazor and Razor updates;
- NuGet: Auditing package dependencies for security vulnerabilities;
- Template Engine: secure experience with packages from Nuget.org;
- Libraries: UTF8 improvements;
- Introduced Time abstraction;
- API authoring: expanded support for form binding in minimal APIs;
- Authentication and Authorization: Identity API endpoints & improved support for custom authorization policies with IAuthorizationRequirementData
Missed things from Preview 5 & 6
- Servers & middleware: IHttpSysRequestTimingFeature;
- SignalR seamless reconnect;
- Blazor updates;
- Authentication and authorization: authentication updates in ASP.NET Core SPA templates;
- Testing metrics in ASP.NET Core apps (MetricCollector
); - Stream-based ZipFile CreateFromDirectory and ExtractToDirectory method overloads;
- Expanding LoggerMessageAttribute Constructor Overloads for Enhanced Functionality.