⚠️ Sponsorship Level Critically Low ⚠️
Due to low financial backing by the community, FastEndpoints will soon be going into "Bugfix Only" mode until the situation improves. Please join the discussion here and help out if you can.
New 🎉
Support for Native AOT compilation
FastEndpoints is now Native AOT compatible. Please see the documentation here on how to configure it.
If you'd like to jump in head first, a fresh AOT primed starter project can be scaffolded like so:
dotnet new install FastEndpoints.TemplatePack
dotnet new feaot -n MyProjectIf you've not worked with AOT compilation in .NET before, it's highly recommended to read the docs linked above.
Auto generate STJ JsonSerializationContexts
You no longer need to ever see a JsonSerializerContext thanks to the new serializer context generator in FastEndpoints. (Unless you want to that is 😉). See the documentation here on how to enable it for non-AOT projects.
Distributed job processing support
The job queueing functionality now has support for distributed workers that connect to the same underlying database. See the documentation here.
Qualify endpoints in global configurator according to endpoint level metadata
You can now register any object as metadata at the endpoint level like so:
sealed class SomeObject
{
public int Id { get; set; }
public bool Yes { get; set; }
}
sealed class MetaDataRegistrationEndpoint : EndpointWithoutRequest
{
public override void Configure()
{
Get("/test-cases/endpoint-metadata-reg-test");
Metadata(
new SomeObject { Id = 1, Yes = true },
new SomeObject { Id = 2, Yes = false });
}
}and configure endpoints conditionally at startup according to the endpoint level metadata that was added by the endpoint configure method:
app.UseFastEndpoints(
c => c.Endpoints.Configurator =
ep =>
{
if (ep.EndpointMetadata?.OfType<SomeObject>().Any(s => s.Yes) is true)
ep.AllowAnonymous();
})Response sending method 'NotModifiedAsync'
A new response sending method has been added for sending a 304 status code response.
public override async Task HandleAsync(CancellationToken c)
{
await Send.NotModifiedAsync();
}Fixes 🪲
Index out of range exception in routeless test helpers
The routeless integration test helpers such as .GETAsync<>() would throw an exception when testing an endpoint configured with the root URL /, which has now been fixed.
Query/Route param culture mismatch with routeless test helpers and backend
The routeless test helpers such as .GETAsync<>() would construct route/query params (of certain primitives such as DateTime) using the culture of the machine where the tests are being run, while the application is set up to use a different culture, the tests would fail. This has been solved by constructing route/query params for primitive/IFormattable types using ISO compliant invariant culture format when constructing the requests.
Routeless testing helpers contention issue
The test helpers were using a regular dictionary to cache test URLs internally which could sometimes cause trouble under high load. This has been solved by switching to a concurrent dictionary.
Source generators having trouble with special characters in project names
The source generators were generating incorrect namespaces if the project name has dashes such as My-Project.csproj which would result in generating namespaces with dashes, which is invalid for C#.
Test collection ordering regression
Due to an internal behavior change in XUnit v3, test collection ordering was being overriden by XUnit. This has been rectified by taking matters in to our own hands and bypassing XUnit's collection runner.
Improvements 🚀
Job Queues storage processing
Several optimizations have been done to the job queues storage logic to reduce the number of queries in certain scenarios. Please see the breaking changes section below as one of the methods of IJobStorageProvider needs a minor change.
Easy access to error response content with testing helpers
You can now easily inspect why a request failed when you expected it to succeed. There's now a new string property ErrorContent on the TestResult record that routeless testing helpers return.
[Fact]
public async Task Get_Request_Responds_With_200_Ok()
{
var (rsp, res, errorContent) = await app.Client.GETAsync<MyEndpoint, MyRequest, string>(new() { ... });
if (rsp.IsSuccessStatusCode)
Assert.True(...);
else
Assert.Fail(errorContent); //errorContent contains the error response body as a string
}Request DTO serialization behavior of testing helpers
Testing helpers such as .POSTAsync<>() will only serialize the request DTO in to the request body if there's at least one property on the DTO that will be bound from the JSON body. In instances where nothing should be bound from the JSON body, the request body content will be empty.
Mitigate incorrect service scoping due to user error in Command Bus
If a user for whatever reason registerd command handlers as scoped services in DI themselves (when they're not supposed to), it could lead to unexpected behavior. This is no longer an issue.
Minor Breaking Changes ⚠️
New 'IJobStorageProvider.DistributedJobProcessingEnabled' property
Due to adding support for distributed job processing, all storage provider implementations must now implement the following boolean property. Simply set it to false when not using distributed job processing like so:
sealed class JobStorageProvider : IJobStorageProvider<JobRecord>
{
public bool DistributedJobProcessingEnabled => false;
}New 'IJobStorageRecord.DequeueAfter' property
Even though you only need to implement this property when using distributed job processing, it's recommended to either let all the jobs in your database run to completion before upgrading to v8.0 and/or run a migration to set the value of DequeueAfter to DateTime.MinValue for all jobs already in the database to ensure that they get picked up properly for processing. (Even when not using distributed processing.)
'IJobStorageProvider.GetNextBatchAsync()' return type change
As a result of optimizations done to the storage processing logic in job queues, your job storage provider implementation requires a minor change from:
public Task<IEnumerable<...>> GetNextBatchAsync(...)to:
public Task<ICollection<...>> GetNextBatchAsync(...)You are now required to return a materialized collection instead of an IEnumerable<T>.