Releases: FastEndpoints/FastEndpoints
v7.1.1 Release
.NET 10 Support
We've had .NET 10 preview support for a while and this is just a patch release to update the SDK references to .NET 10 final.
v7.1 Release
❇️ Help Keep FastEndpoints Free & Open-Source ❇️
Due to the current unfortunate state of FOSS, please consider becoming a sponsor and help us beat the odds to keep the project alive and free for everyone.
New 🎉
Better conditional sending of responses
All Send.*Async() methods now return a Task<Void> result. If a response needs to be sent conditionally, you can simply change the return type of the handler from Task to Task<Void> and return the awaited result as shown below in order to stop further execution of endpoint handler logic:
public override async Task<Void> HandleAsync(CancellationToken c)
{
if (id == 0)
return await Send.NotFoundAsync();
if (id == 1)
return await Send.NoContentAsync();
return await Send.OkAsync();
}If there's no async work being done in the handler, the Task<Void> can simply be returned as well:
public override Task<Void> HandleAsync(CancellationToken c)
{
return Send.OkAsync();
}Specify max request body size per endpoint
Instead of globally increasing the max request body size in Kestrel, you can now set a max body size per endpoint where necessary like so:
public override void Configure()
{
Post("/file-upload");
AllowFileUploads();
MaxRequestBodySize(50 * 1024 * 1024);
}Customize error response builder func when using 'ProblemDetails'
You can now specify a custom response builder function when doing .UseProblemDetails() as shown below in case you have a special requirement to use a certain shape
for one or more of your endpoints while the rest of the endpoints use the standard response.
app.UseFastEndpoints(
c => c.Errors.UseProblemDetails(
p =>
{
p.ResponseBuilder = (failures, ctx, statusCode) =>
{
if (ctx.Request.Path.StartsWithSegments("/group-name"))
{
// return any shape you want to be serialized
return new
{
Errors = failures
};
}
// anything else will use the standard problem details.
return new ProblemDetails(failures, ctx.Request.Path, ctx.TraceIdentifier, statusCode);
};
}))Use 'ProblemDetails.Detail' property for describing single error instances
The FastEndpoints.ProblemDetails.Detail property has been unused until now. It will now by default be populated according to the following DetailTransformer logic, which you can customize if needed. The transformer can also be set to null in case you'd like to go back to the previous behavior.
app.UseFastEndpoints(
c => c.Errors.UseProblemDetails(
p =>
{
p.DetailTransformer = pd => pd.Errors.Count() == 1
? pd.Errors.First().Reason
: null;
}))The default behavior is to populate the Detail property with the reason if there's only 1 error and not populate it at all in case there's more than 1 error.
Specify a request binder per group
It is now possible to register a particular open generic request binder such as the following:
class MyBinder<TRequest> : RequestBinder<TRequest> where TRequest : notnull
{
public override async ValueTask<TRequest> BindAsync(BinderContext ctx, CancellationToken ct)
{
var req = await base.BindAsync(ctx, ct); // run the default binding logic
if (req is MyRequest r)
r.SomeValue = Guid.NewGuid().ToString(); // do whatever you like
return req;
}
} only for a certain group configuration, so that only endpoints of that group will have the above custom binder associated with them.
sealed class MyGroup : Group
{
public MyGroup()
{
Configure("/my-group", ep => ep.RequestBinder(typeof(MyBinder<>)));
}
} Position endpoint version anywhere in the route
With the built-in versioning, you could only have the endpoint version number either pre-fixed or post-fixed. You can now make the version appear anywhere in the route by using a route template. The template segment will be replaced by the actual version number instead of being prepended or appended.
sealed class MyEndpoint : EndpointWithoutRequest
{
public override void Configure()
{
Get("/sales/{_version_}/my-endpoint");
Version(1);
}
...
}This version placement strategy must be enabled at startup like so:
app.UseFastEndpoints(
c =>
{
c.Versioning.RouteTemplate = "{_version_}";
})If this setting is enabled, it will take precedence over the default behavior of appending/prepending the version number to the route.
Support for Feature Management libraries
Endpoints can now be setup to execute a FeatureFlag for every Http request that comes in, which allows an endpoint to be conditionally available according to some evaluation logic.
To create a feature flag, implement the interface IFeatureFlag and simply return true from the IsEnabledAsync() handler method if the endpoint is to be accessible to that particular request.
sealed class BetaTestersOnly : IFeatureFlag
{
public async Task<bool> IsEnabledAsync(IEndpoint endpoint)
{
//use whatever mechanism/library you like to determine if this endpoint is enabled for the current request.
if (endpoint.HttpContext.Request.Headers.TryGetValue("x-beta-tester", out _))
return true; // return true to enable
//this is optional. if you don't send anything, a 404 is sent automatically.
await endpoint.HttpContext.Response.SendErrorsAsync([new("featureDisabled", "You are not a beta tester!")]);
return false; // return false to disable
}
}Attach it to the endpoint like so:
sealed class BetaEndpoint : EndpointWithoutRequest<string>
{
public override void Configure()
{
Get("beta");
FeatureFlag<BetaTestersOnly>();
}
public override async Task HandleAsync(CancellationToken c)
{
await Send.OkAsync("this is the beta!");
}
}[FromCookie] attribute for auto binding cookie values
You can now decorate request DTO properties with [FromCookie] and matching cookies will be auto bound from incoming request cookies.
Improvements 🚀
Recursive validation of Data Annotation Attributes
Until now, only the top level properties of a request DTO was being validated when using Data Annotation Attributes. This release adds support for recursively validating the whole object graph and generating errors for each that fails validation.
SSE response standard compliance
The SSE response implementation has been enhanced by making the Id property in StreamItem optional, adding an optional Retry property for client-side reconnection control, as well as introducing an extra StreamItem constructor overload for more flexibility. Additionally, the X-Accel-Buffering: no response header is now automatically sent to improve compatibility with reverse proxies like NGINX, ensuring streamed data is delivered without buffering. You can now do the following when doing multi-type data responses:
yield return new StreamItem("my-event", myData, 3000);Respect app shutdown when using SSE
The SSE implementation now passes the ApplicationStopping cancellation token to your IAsyncEnumerable method. This means that streaming is cancelled at least when the application host is shutting down, and also when a user provided CancellationToken (if provided) triggers it.
public override async Task HandleAsync(CancellationToken ct)
{
await Send.EventStreamAsync(GetMultiDataStream(ct), ct);
async IAsyncEnumerable<StreamItem> GetMultiDataStream([EnumeratorCancellation] CancellationToken ct)
{
// Here ct is now your user provided CancellationToken combined with the ApplicationStopping CancellationToken.
while (!ct.IsCancellationRequested)
{
await Task.Delay(1000, ct);
yield return new StreamItem(Guid.NewGuid(), "your-event-type", 42);
}
}
}Async variation of Global Response Modifier
If you need to do some async work in the global response modifier, you can now use the GlobalResponseModifierAsync variant.
Allow 'ValidationContext' instances to be cached
Until now, you were meant to obtain an instance of the v...
v7.0.1 Release
❇️ Help Keep FastEndpoints Free & Open-Source ❇️
Due to the current unfortunate state of FOSS, please consider becoming a sponsor and help us beat the odds to keep the project alive and free for everyone.
New 🎉
Relocate response sending methods ⚠️
Response sending methods such as SendOkAsync() have been ripped out of the endpoint base class for a better intellisense experience and extensibility.
Going forward, the response sending methods are accessed via the Send property of the endpoint as follows:
public override async Task HandleAsync(CancellationToken c)
{
await Send.OkAsync("hello world!");
}In order to add your own custom response sending methods, simply target the IResponseSender interface and write extension methods like so:
static class SendExtensions
{
public static Task HelloResponse(this IResponseSender sender)
=> sender.HttpContext.Response.SendOkAsync("hello!");
}This is obviously is a wide-reaching breaking change which can be easily remedied with a quick regex based find & replace. Please see the breaking changes section below for step-by-step instructions on how to migrate. Takes less than a minute.
Send multiple Server-Sent-Event models in a single stream
It is now possible to send different types of data in a single SSE stream with the use of a wrapper type called StreamItem like so:
public override async Task HandleAsync(CancellationToken ct)
{
await Send.EventStreamAsync(GetMultiDataStream(ct), ct);
async IAsyncEnumerable<StreamItem> GetMultiDataStream([EnumeratorCancellation] CancellationToken ct)
{
long id = 0;
while (!ct.IsCancellationRequested)
{
await Task.Delay(1000, ct);
id++;
if (DateTime.Now.Second % 2 == 1)
yield return new StreamItem(id.ToString(), "odd-second", Guid.NewGuid()); //guide data
else
yield return new StreamItem(id.ToString(), "even-second", "hello!"); //string data
}
}
}By default, the StreamItem will be serialized as a JSON object, but you can change this by inheriting from it and overriding the GetDataString method to return a different format such as XML or plain text.
Customize param names for strongly typed route params
It is now possible to customize the route param names when using the strongly typed route params feature by simply decorating the target DTO property with a [BindFrom("customName"))] attribute. If a BindFrom attribute annotation is not present on the property, the actual name of the property itself will end up being the route param name.
Support for malformed JSON array string binding
When submitting requests via SwaggerUI where a complex object collection is to be bound to a collection property of a DTO, SwaggerUI sends in a malformed string of JSON objects without properly enclosing them in the JSON array notation [...] such as the following:
{"something":"one"},{"something":"two"}whereas it should be a proper JSON array such as this:
[{"something":"one"},{"something":"two"}]Since we have no control over how SwaggerUI behaves, support has been added to the default request binder to support parsing and binding the malformed comma separateed JSON objects that SwaggerUI sends at the expense of a minor performance hit.
Auto infer query parameters for routeless integration tests
If you annotate request DTO properties with [RouteParam] attribute, the helper extensions such as .GETAsync() will now automatically populate
the request query string with values from the supplied DTO instance when sending integration test requests.
sealed class MyRequest
{
[RouteParam]
public string FirstName { get; set; }
public string LastName { get; set; }
}
[Fact]
public async Task Query_Param_Test()
{
var request = new MyRequest
{
FirstName = "John", //will turn into a query parameter
LastName = "Gallow" //will be in json body content
};
var result = await App.Client.GETAsync<MyEndpoint, MyRequest, string>(request);
}Fixes 🪲
Header example value not picked up from swagger example request
If a request DTO specifies a custom header name that is different from the property name such as the following:
sealed class GetItemRequest
{
[FromHeader("x-correlation-id")]
public Guid CorrelationId { get; init; }
}and a summary example request is provided such as the following:
Summary(s => s.ExampleRequest = new GetItemRequest()
{
CorrelationId = "54321"
});the example value from the summary example property was not being picked up due to an oversight.
Xml comments not picked up by swagger for request schema
There was a regression in the code path that was picking up Summary xml comments from DTO properties in certain scenarios, which has now been fixed.
Incorrect swagger example generation when '[FromBody] or [FromForm]' was used
If a request DTO was defined like this:
sealed class MyRequest
{
[FromBody]
public Something Body { get; set; }
}and an example request is provided via the Summary like this:
Summary(x=>x.ExampleRequest = new MyRequest()
{
Body = new Something()
{
...
}
});swagger generated the incorrect request example value which included the property name, which it shouldn't have.
Default exception handler setting incorrect mime type
Due to an oversight, the default exception handler was not correctly setting the intended content-type value of application/problem+json. Instead, it was being overwritten with application/json due to not using the correct overload of WriteAsJsonAsync() method internally.
Minor Breaking Changes ⚠️
API change of endpoint response sending methods
The response sending methods are no longer located on the endpoint class itself and are now accessed via the Send property of the endpoint.
This is a breaking change which you can easily fix by doing a quick find+replace using a text editor such as VSCode. Please follow the following steps in order to update your files:
- Open the top level folder of where your endpoint classes exist in the project in a text editor like VSCode.
- Click
Edit > Replace In Filesand enableRegex Matching - Use
(?<!\.)\bSend(?=[A-Z][A-Za-z]*Async\b)as the regex to find matches to target for editing. - Enter
Send.in the replacement field and hitReplace All - Then use
(?<!\.)\bSendAsync\bas the regex. - Enter
Send.OkAsyncas the replacement and hitReplace Allagain. - Build the project and profit!
Note: In case some Send.OkAsync() calls won't compile, it's most likely you were using the SendAsync() overload that allowed to set a custom status code. Simply use the Send.ResponseAsync() method instead of Send.OkAsync() for those calls as OkAsync() doesn't allow custom status codes.
Here's a complete walkthrough of the above process.
Small change in the Server-Sent-Event response stream
Previously the Server-Sent-Event response was written as:
id:12345
event: my-event
data: hello world!
Notice the inconsistency in the spacing between the id, event and data fields. This has now been fixed to be consistent with the following format:
id: 12345
event: my-event
data: hello world!
v6.2 Release
❇️ Help Keep FastEndpoints Free & Open-Source ❇️
Due to the current unfortunate state of FOSS, please consider becoming a sponsor and help us beat the odds to keep the project alive and free for everyone.
New 🎉
Support 'Scope' based access restriction
Your can now restrict access based on Scopes in tokens (e.g., from OAuth2/OpenID Connect IDPs). Simply specify required scopes using the newly added Scopes() method:
public override void Configure()
{
Get("/item");
Scopes("item:read", "item:write");
}This allows access if the user's "scope" claim includes ANY of the listed values. To require ALL scopes, use ScopesAll() instead.
By default, scopes are read from the "scope" claim, which can be changed like so:
app.UseFastEndpoints(c => c.Security.ScopeClaimType = "scp")If scope values aren't space-separated, customize parsing like so:
app.UseFastEndpoints(c => c.Security.ScopeParser = input =>
{
//extract scope values and return a collection of strings
})Automatic 'Accepts Metadata' for Non-Json requests
In the past, if an endpoint defines a request DTO type, an accepts-metadata of application/json would be automatically added to the endpoint, which would require the user to clear that default metadata if all the properties of the DTO is bound from non-json binding sources such as route/query/header etc.
Now, if the user annotates all the properties of a DTO with the respective non-json binding sources such as the following:
sealed class GetAccountStatementRequest
{
[RouteParam]
public int UserId { get; set; }
[QueryParam]
public DateTime DateFrom { get; set; }
[QueryParam]
public DateTime DateTo { get; set; }
}It is no longer necessary for the user to manually clear the default accepts-metadata as the presence of non-json binding source attributes on each of the DTO properties allows us to correctly detect that there's not going to be any JSON body present in the incoming request.
Support for 'DataAnnotations' Validation Attributes
You can now annotate request DTO properties with DataAnnotations attributes such as [Required], [StringLength(...)] etc., instead of writing a FluentValidations validator for quick-n-dirty input validation. Do note however, only one of the strategies can be used for a single endpoint. I.e. if a request DTO has annotations as well as a fluent validator, only the fluent validator will be run and the annotations will be ignored. Mixing strategies is not allowed in order to prevent confusion for the reader. To enable DataAnnotations support, do the following:
app.UseFastEndpoints(c => c.Validation.EnableDataAnnotationsSupport = true)Fixes 🪲
Swagger generation bug caused by lists of request DTOs
A new feature introduced in v6.1 caused swagger generation to fail if the request DTO type of the endpoint is a List<T>, which has been corrected.
Infinite recursion issue with swagger generation due to self referencing validators
If a request uses a self referencing validator for nested properties, a stack overflow was happening due to infinite recursion.
Issue with generic post-processor registration
Generic post-processors were not being correctly registered due to an oversight, which has been corrected with this release.
Minor Breaking Changes ⚠️
Behavior change with multiple binding sources and the 'required' keyword
If a request DTO has required properties like so:
{
public required string UserId { get; set; } //to be bound from route param
public required string Name { get; set; } //to be bound from json body
}The previous advice was to simply decorate the UserId property with a [JsonIgnore] attribute so that the serializer will ignore the required keyword and won't complain due to missing data for that property in the incoming JSON body.
Even though the [JsonIgnore] attribute seemed logical for this purpose at the time, we've come to realize it has the potential to cause problems elsewhere.
So, if you are using the required keyword on DTO properties that are to be bound from a non-json binding source such as route/query params, form fields, headers, claims, etc. and would like to keep on using the required keyword (even though it doesn't really make much sense in the context of request DTOs in most cases), you should remove the [JsonIgnore] property and annotate the binding related attribute that actually specifies what binding source should be used for that property, such as [RouteParam], [QueryParam], [FormField], [FromClaim], [FromHeader], etc.
The request DTO now needs to look like the following:
sealed class MyRequest
{
[RouteParam]
public required string UserId { get; set; }
public required string Name { get; set; }
}v6.1 Release
❇️ Help Keep FastEndpoints Free & Open-Source ❇️
Due to the current unfortunate state of FOSS, please consider becoming a sponsor and help us beat the odds to keep the project alive and free for everyone.
New 🎉
Convenience method for retrieving the auto generated endpoint name
You can now obtain the generated endpoint name like below for the purpose of custom link generation using the LinkGenerator class.
var endpointName = IEndpoint.GetName<SomeEndpoint>();Auto population of headers in routeless tests
Given a request dto such as the following where a property is decorated with the [FromHeader] attribute:
sealed class Request
{
[FromHeader]
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}Previously, you had to manually add the header to the request in order for the endpoint to succeed without sending back an error response.
Now you can simply supply the value for the header when making the request as follows, and the header will be automatically added to the request with the value from the property.
var (rsp, res) = await App.Client.POSTAsync<MyEndpoint, Request, string>(
new()
{
Title = "Mrs.",
FirstName = "Doubt",
LastName = "Fire"
});This automatic behavior can be disabled as follows if you'd like to keep the previous behavior:
POSTAsync<...>(..., populateHeaders: false);Comma separated value binding support for collection properties
It was only possible to model bind collection properties if the values were submitted either as json arrays or duplicate keys.
You can now submit csv data for automatically binding to collection properties such as the following:
public class FindRequest
{
[QueryParam]
public string[] Status { get; set; }
}A url with query parameters such as this would work out-of-the-box now:
/find?status=queued,completed'SendAcceptedAtAsync()' method for endpoints
The following method is now available for sending a 202 - Accepted similarly to the 201 - Created response.
await SendAcceptedAtAsync<ProgressEndpoint>(new { Id = "123" });Error Code & Error Severity support for the 'ThrowError()' method
A new overload has been added for the ThrowError() method where you can supply an error code and an optional severity value as follows:
ThrowError("Account is locked out!", errorCode: "AccountLocked", severity: Severity.Error, statusCode: 423);Setting for changing 'default value' binding behavior
Given a request dto such as the following, which has a nullable value type property:
public class MyRequest
{
[QueryParam]
public int? Age { get; set; }
}and a request is made with an empty parameter value such as:
/person?age=the default behavior is to populate the property with the default value for that value type when model binding, and if the parameter name is also omitted, the property would end up being null.
You can now change this behavior so that in case an empty parameter is submitted, the property would end up being null, instead of the default value:
app.UseFastEndpoints(c => c.Binding.UseDefaultValuesForNullableProps = false)Note: the setting applies to all non-STJ binding paths such as route/query/claims/headers/form fields etc.
Preliminery support for OData
A new pre-release package FastEndpoints.OData has been created to provide support for OData. Since the OData library's Minimal APIs support is still in flux, FE's OData package is referncing a nightly build from MyGet and the public API may change drastically. See here if you'd like to give it a try.
Improvements 🚀
'Configuration Groups' support for refresh token service
Configuration groups were not previously compatible with the built-in refresh token functionality. You can now group refresh token service endpoints using a group as follows:
public class AuthGroup : Group
{
public AuthGroup()
{
Configure("users/auth", ep => ep.Options(x => x.Produces(401)));
}
}
public class UserTokenService : RefreshTokenService<TokenRequest, TokenResponse>
{
public MyTokenService(IConfiguration config)
{
Setup(
o =>
{
o.Endpoint("refresh-token", ep =>
{
ep.Summary(s => s.Summary = "this is the refresh token endpoint");
ep.Group<AuthGroup>(); // this was not possible before
});
});
}
}Pick up Swagger request param example values from summary example
In the past, the only way to provide an example value for a swagger request parameter was with an xml document comment like so:
sealed class MyRequest
{
/// <example>john doe</example>
public string Name { get; set; }
}The example values will now be picked up from the summary example request properties which you can supply like so:
Summary(
s => s.ExampleRequest = new()
{
Name = "jane doe"
});If you provide both, the values from the summary example will take precedence.
Fixes 🪲
Reflection source generator issue with 'required' properties
If a DTO class had required properties with [JsonIgnore] attributes such as this:
sealed class UpdateRequest
{
[JsonIgnore]
public required int Id { get; set; }
public required string Name { get; set; }
} The reflection source generator failed to generate the correct object initialization factory causing a compile error, which has now been corrected.
'ClearDefaultProduces()' stopped working from global configurator
If ClearDefaultProduces() was called from the global endpoint configurator function, it had no effect due to a regression introduced in v6.0, which has now been corrected.
Incorrect OAS3 spec generation when 'NotNull()' or 'NotEmpty()' validators were used on nested objects
If a request DTO has complex nested properties and those properties are being validated with either NotNull() or NotEmpty(), an incorrect swagger3 spec was being generated due to a bug in the "validation schema processor".
v6.0 Release
❇️ Help Keep FastEndpoints Free & Open-Source ❇️
Due to the current unfortunate state of FOSS, please consider becoming a sponsor and help us beat the odds to keep the project alive and free for everyone.
Breaking Changes ⚠️
Support for .NET 6 & 7 has been dropped as those SDKs are no longer supported by Microsoft. In order to use this release of FastEndpoints, you need to be on at least .NET 8.0.4
New 🎉
Support for .NET 10 preview
You can start targeting net10.0 SDK in your FE projects now. Currently preview versions of the dependencies are used.
Generic Pre/Post Processor global registration
Open generic pre/post processors can now be registered globally using the endpoint configurator func like so:
app.UseFastEndpoints(c => c.Endpoints.Configurator = ep => ep.PreProcessors(Order.Before, typeof(MyPreProcessor<>)))sealed class MyPreProcessor<TRequest> : IPreProcessor<TRequest>
{
public Task PreProcessAsync(IPreProcessorContext<TRequest> ctx, CancellationToken c)
{
...
}
}Middleware pipeline for Command Bus
By popular demand from people moving away from MediatR, a middleware pipeline similar to MediatRs pipeline behaviors has been added to FE's built-in command bus. You just need to write your pipeline/middleware pieces by implementing the interface ICommandMiddleware<TCommand,TResult> and register those pieces to form a middleware pipeline as described in the documentation.
Support 'CONNECT' and 'TRACE' verbs
The FastEndpoints.Http enum and the endpoint base classes now have support for the HTTP CONNECT & TRACE verbs.
Verify event publishes when integration testing
When integration testing using the AppFixture, it is now possible to setup a Test Event Receiver as a collector of all the events that gets published from your code. These received events can be used as verification that your code did actually publish the desired event. A full example of this new capability can be seen here.
Dynamic updating of JWT signing keys at runtime
Updating the signing keys used by JWT middleware at runtime is now made simple without having to restart the application.
See here for a full example of how it is done.
Improvements 🚀
Automatic addition of 'ProducesResponseTypeMetadata'
The library automatically adds response type metadata for certain response types.
Sometimes, the automatically added responses need to be cleared by the user when it's not appropriate.
From now on, the automatic additions will only happen if the user hasn't already added it.
Before:
Description(x => x.ClearDefaultProduces(200) //had to clear the auto added 200
.Produces(201))Now:
Description(x => x.Produces(201)) //nothing to clear as nothing was added due to 201 being presentUse source generated regex
Source generated regex is now used whereever possible. Source generated regex was not used before due to having to support older SDK versions.
Allow overriding the 'Verbs()' method of `Endpoint<>` class
The Verbs() method was sealed until now because it was doing some essential setup which was required for adding the default request/response swagger descriptions.
This logic has been moved out of the Verbs() method making it overrideable if needed.
Prevent configuration methods being called after startup
A meaningful exception will now be thrown if the user tries to call endpoint configuration methods such as Verbs()/Routes()/etc. outside of the endpoint Configure() method.
Fixes 🪲
Contention issue in reflection source generator
The reflection source generator was using some static state which was causing issues in certain usage scenarios, which has now been fixed.
Type discriminator missing from polymorphic responses
The type discriminator was not being serialized by STJ when the response type was a base type, due to an oversight in the default response serialized func.
Source generated reflection for obsolete members
When source generation happens for obsolete members of classes, the generated file triggered a compiler warning, which has now been correctly handled.
v5.35 Release
❇️ Help Keep FastEndpoints Free & Open-Source ❇️
Due to the current unfortunate state of FOSS, please consider becoming a sponsor and help us beat the odds to keep the project alive and free for everyone.
New 🎉
Bypass endpoint caching for integration tests
You can now easily test endpoints that have caching enabled, by using a client configured to automatically bypass caching like so:
var antiCacheClient = App.CreateClient(new() { BypassCaching = true });Mark properties as "bind required"
You can now make the request binder automatically add a validation failure when binding from route params, query params, and form fields by decorating the dto properties if the binding source doesn't provide a value:
sealed class MyRequest
{
[QueryParam(IsRequired = true)]
public bool Correct { get; set; }
[RouteParam(IsRequired = true)]
public int Count { get; set; }
[FormField(IsRequired = true)]
public Guid Id { get; set; }
}Generic command support for job queues
Closed generic commands can now be registered like so:
app.Services.RegisterGenericCommand<QueueCommand<OrderCreatedEvent>, QueueCommandHandler<OrderCreatedEvent>>();and then be queued as jobs like so:
await new QueueEventCommand<OrderCreatedEvent>()
{
...
}.QueueJobAsync();Note: Open generic commands are not supported for job queueing.
Inter-Process-Communication via Unix-Domain-Sockets
The FastEndpoints.Messaging.Remote library can now do inter-process-communication via unix sockets when everything is running on the same machine by doing the following:
//server setup
bld.WebHost.ConfigureKestrel(k => k.ListenInterProcess("ORDERS_MICRO_SERVICE"));
//client setup
app.MapRemote("ORDERS_MICRO_SERVICE", c => c.Register<CreateOrderCommand>());When a service is lifted out to a remote machine, all that needs to be done is to update the connection settings like so:
//server
bld.WebHost.ConfigureKestrel(k => k.ListenAnyIP(80, o => o.Protocols = HttpProtocols.Http2);
//client
app.MapRemote("http://orders.my-app.com", c => c.Register<CreateOrderCommand>());Optionally save command type on job storage record
A new optional/addon interface IHasCommandType has been introduced if you need to persist the full type name of the command that is associated with the job storage record. Simply implement the new interface on your job storage record and the system will automatically populate the property value before being persisted.
Improvements 🚀
RPC Event Subscriber parallel execution
Event subscribers used to execute the event handlers in sequence when a batch of event storage records were fetched from the storage provider.
The handlers will now be executed in parallel just like how parallel execution happens in job queues.
RPC Event Hub startup sequence
The rpc event hub was using a thread sleep pattern during startup to restore subscriber IDs via the storage provider, resulting in a sequential initialization.
It has been refactored to use an IHostedService together with retry logic for a proper async and parallel initialization, resulting in decreased startup time.
RPC Event Hub event distribution
Previously if a hub was not registered before events were broadcasted, or if event serialization fails due to user error, those exceptions would have been swallowed in some cases.
The internals of the hub has been refactored to surface those exceptions when appropriate.
Workaround for NSwag quirk with byte array responses
NSwag has a quirk that it will render an incorrect schema if the user does something like the following:
b => b.Produces<byte[]>(200, "image/png");In order to get the correct schema generated, we've had to do the following:
b => b.Produces<IFormFile>(200, "image/png");You now have the ability to do either of the above, and it will generate the correct schema.
Fixes 🪲
Job result is null when job execution failure occurs
The result property of job records that was passed into the OnHandlerExecutionFailureAsync() method of the storage provider was null due to an oversight, which has been corrected.
Parallel 'VersionSet' creation issue
If VersionSets are created by multiple SUTs at the same time when doing integration testing, a non-concurrent dictionary modification exception was being thrown.
The internal dictionary used to keep track of the version sets has been changed to a concurrent dictionary which solves the issue.
Breaking Changes (Minor) ⚠️
'IEventHubStorageProvider' contract change
In order to improve database write performance, the IEventHubStorageProvider.StoreEventAsync(TStorageRecord r, CancellationToken ct) method signature has been changed to the following:
ValueTask StoreEventsAsync(IEnumerable<TStorageRecord> r, CancellationToken ct);Previously, records were persisted one at a time. Now, the records are supplied in batches allowing you to take advantage of batched inserts and/or transactions improving database write performance and consistency.
NOTE: You should make sure either none or all of the supplied records are persisted to disk in order to avoid duplicate events being published due to the built-in retry mechanism.
'IEvent.Broadcast()' extension method is no longer cancellable
The BroadCast() method is now a fire-n-forget method and no longer accepts a CancellationToken, which simplifies event publication.
v5.34 Release
❇️ Help Keep FastEndpoints Free & Open-Source ❇️
Due to the current unfortunate state of FOSS, please consider becoming a sponsor and help us beat the odds to keep the project alive and free for everyone.
New 🎉
Queued job progress tracking
It is now possible to queue a job and track its progress and/or retrieve intermediate results while the command handler executes via the job tracker as documented here.
Global 'JwtCreationOptions' support for refresh token service
If you configure jwt creation options at a global level like so:
bld.Services.Configure<JwtCreationOptions>( o => o.SigningKey = "..." ); The RefreshTokenService will now take the default values from the global config if you don't specify anything when configuring the token service like below:
sealed class MyTokenService : RefreshTokenService<TokenRequest, TokenResponse>
{
public MyTokenService
{
Setup(o =>
{
//no need to specify token signing key/style/etc. here unless you want to.
o.Endpoint("/api/refresh-token");
o.AccessTokenValidity = TimeSpan.FromMinutes(5);
o.RefreshTokenValidity = TimeSpan.FromHours(4);
});
}
}Global response modifier setting
A new global action has been added which gets triggered right before a response is written to the response stream allowing you to carry out some common logic that should be applied to all endpoints.
app.UseFastEndpoints(
c => c.Endpoints.GlobalResponseModifier
= (ctx, content) =>
{
ctx.Response.Headers.Append("x-common-header", "some value");
})Access 'IServiceProvider' when configuring Swagger Documents
You can now access the built service provider instance via the DocumentOptions.Services property when configuring swagger documents like so:
var bld = WebApplication.CreateBuilder(args);
bld.Services.Configure<MySettings>(bld.Configuration.GetSection(nameof(MySettings)));
bld.Services
.SwaggerDocument(
o =>
{
// IServiceProvider is available via DocumentOptions.Services property
var conf = o.Services.GetRequiredService<IOptions<MySettings>>();
o.DocumentSettings = doc =>
{
doc.DocumentName = conf.Value.DocName;
};
})
.AddFastEndpoints();Improvements 🚀
Value parser support for Reflection Source Generator
Value parser functions (used by non-stj model binding) will now be source generated instead of being compiled at runtime when you opt-in to use the reflection source generator.
Optimize value parser internals
String value parsing logic used in most non-stj model binding paths has been simplified and optimized to reduce allocations and unnecessary boxing.
Graceful shutdown of Job Queue processing
If app shutdown is requested during a retry loop (due to transient failures) in job queue processing, the operation will now be tried at least once before exiting the retry loops and allowing the app to shut down.
Miscellaneous improvements
- Remove all traces of
FluentAssertionsfrom FE test projects & documentation examples - Prioritize the typed
Summary(x => {})overload over the untyped overload in .NET 9
Fixes 🪲
Issue with unit testing endpoints with pre-processors with injected dependencies
Unit tests were failing to instantiate pre-processors that had injected dependencies due to a small oversight in the ServiceResolver code with regards to how singletons were instantiated, which has been fixed.
Potential infinite recursion in Swagger Processor due to circular references
In certain edge cases where the schema has circular references, there was a potential inifinite recursion issue which could lead to memory leaks when generating the swagger docs.
v5.33 Release
❇️ Help Keep FastEndpoints Free & Open-Source ❇️
Due to the current unfortunate state of FOSS, please consider becoming a sponsor and help us beat the odds to keep the project alive and free for everyone.
New 🎉
Migrate to xUnit v3 ⚠️
If you're using the FastEndpoints.Testing package in your test projects, take the following steps to migrate your projects:
- Update all "FastEndpoints" package references in your projects to "5.33.0".
- In your test project's
.csprojfile:- Remove the package reference to the
xunitv2 package. - Add a package reference to the new
xunit.v3library with version1.0.0 - Change the version of
xunit.runner.visualstudioto3.0.0
- Remove the package reference to the
- Build the solution.
- If there are compilation errors related to the return type of overridden methods in your derived
AppFixture<TProgram>classes, such asSetupAsyncandTearDownAsync. Change their return type fromTasktoValueTaskto resolve these errors. - If there are any compilation errors related to
XUnit.Abstractionsnamespace not being found, simply delete those "using statements" as that namespace has been removed in xUnit v3.
After doing the above, it should pretty much be smooth sailing, unless your project is affected by the removal of previously deprecated classes as mentioned in the "Breaking Changes" section below.
Eliminate the need for [BindFrom(...)] attribute
Until now, when binding from sources other than JSON body, you had to annotate request DTO properties with the [BindFrom("my_field")] attribute when the incoming field name is different to the DTO property name. A new setting has now been introduced which allows you to use the same property naming policy as the serializer for matching incoming request parameters without having to use any attributes.
app.UseFastEndpoints(c => c.Binding.UsePropertyNamingPolicy = true)This only applies to properties where you haven't specified the field names manually using an attribute such as [BindFrom(...)], [FromClaim(...)]. [FromHeader(...)] etc.
Control binding sources per DTO property
The default binding order is designed to minimize attribute clutter on DTO models. In most cases, disabling binding sources is unnecessary. However, for rare scenarios where a binding source must be explicitly blocked, you can now do the following:
[DontBind(Source.QueryParam | Source.RouteParam)]
public string UserID { get; set; } The opposite approach can be taken as well, by just specifying a single binding source for a property like so:
[FormField]
public string UserID { get; set; }
[QueryParam]
public string UserName { get; set; }
[RouteParam]
public string InvoiceID { get; set; }Deeply nested complex model binding from query parameters
Binding deeply nested complex DTOs from incoming query parameters is now supported. Please refer to the documentation here.
Swagger descriptions for deeply nested DTO properties
Until now, if you wanted to provide text descriptions for deeply nested request DTO properties, the only option was to provide them via XML document summary tags. You can now provide descriptions for deeply nested properties like so:
Summary(
s =>
{
s.RequestParam(r => r.Nested.Name, "nested name description");
s.RequestParam(r => r.Nested.Items[0].Id, "nested item id description");
});Descriptions for lists and arrays can be provided by using an index 0 to get at the actual property. Note: only lists and arrays can be used for this.
Fixes 🪲
Incorrect latest version detection with 'Release Versioning' strategy
The new release versioning strategy was not correctly detecting the latest version of an endpoint if there was multiple endpoints for the same route such as a GET & DELETE endpoint on the same route.
Issue with nullability context not being thread safe in Swagger processor
In rare occasions where swagger documents were being generated concurrently, an exception was being thrown due to NullabilityInfoContext not being thread safe. This has been fixed by implementing a caching mechanism per property type.
Complex DTO handling in routeless testing extensions
If the request DTO is a complex structure, testing with routeless test extensions like the following did not work correctly:
[Fact]
public async Task FormDataTest()
{
var book = new Book
{
BarCodes = [1, 2, 3],
CoAuthors = [new Author { Name = "a1" }, new Author { Name = "a2" }],
MainAuthor = new() { Name = "main" }
};
var (rsp, res) = await App.GuestClient.PUTAsync<MyEndpoint, Book, Book>(book, sendAsFormData: true);
rsp.IsSuccessStatusCode.Should().BeTrue();
res.Should().BeEquivalentTo(book);
}Incorrect detection of generic arguments of generic commands
There was a minor oversight in correctly detecting the number of generic arguments of generic commands if there was more than one.
This has been fixed to correctly detect all generic arguments of generic commands.
Complex form data binding issue
When binding deeply nested form data with the [FromForm] attribute, if a certain deeply nested objects didn't have at least one primitive type property, it would not get bound correctly. This has been fixed as well as the binding logic being improved.
Breaking Changes ⚠️
Removal of deprecated classes (Testing related)
After following the xUnit v3 upgrade instructions above, you may be affected by the removal of the following previously deprecated classes:
TestFixture<TProgram>: Use theAppFixture<TProgram>class instead.TestClass<TFixture>: Use theTestBase<TFixture>class instead.
Removal of constructor overloads from 'AppFixture<TProgram>'
Due to the migration to xUnit v3, the AppFixture<TProgram> base class no longer accepts IMessageSink and ITestOutputHelper arguments and only has a parameterless constructor.
Removal of undocumented [FromQueryParams] attribute
[FromQueryParams] was an undocumented feature that was put in place to help people migrating from old MVC projects to make the transition easier. It was not documented due to its extremely poor performance and we wanted to discourage people from using query parameters as a means to submit complex data structures.
The newly introduced [FromQuery] attribute can be used now if you really must send complex query parameters. However, it is not a one-to-one replacement as the query naming convention is quite strict and simplified.
v5.32 Release
❇️ Help Keep FastEndpoints Free & Open-Source ❇️
Due to the current unfortunate state of FOSS, please consider becoming a sponsor and help us beat the odds to keep the project alive and free for everyone.
New 🎉
.NET 9.0 Support
Migration to .NET 9.0 SDK is now complete. You can now target net9.0 sdk without any issues.
Support for enforcing antiforgery token checks for non-form requests
The antiforgery middleware can now be configured to check antiforgery tokens for any content-type by configuring it like so:
app.UseAntiforgeryFE(additionalContentTypes: ["application/json"])User configurable Endpoint Name (Operation Id) generation
The endpoint name generation logic can now be overriden at a global level like so:
app.UseFastEndpoints(
c => c.Endpoints.NameGenerator =
ctx =>
{
return ctx.EndpointType.Name.TrimEnd("Endpoint");
})Global configuration of 'JwtCreationOptions'
You can now configure JwtCreationOptions once globally like so:
bld.Services.Configure<JwtCreationOptions>(
o =>
{
o.SigningKey = "...";
o.Audience = "...";
o.Issuer = "...";
})and only specify just the relevant settings that are needed to be set during token creation like so.
var token = JwtBearer.CreateToken(
o =>
{
o.ExpireAt = DateTime.UtcNow.AddHours(1);
o.User.Claims.Add(("UserId", "001"));
});If you need to override any of the globally specified settings, that can be done during token creation as well, as what's supplied to the CreateToken(o=> ...) method is a deeply cloned instance independant of the global instance.
New endpoint versioning strategy based on 'Release Versions'
A new route based versioning strategy called Release Versioning has been introduced in addition to the existing two built-in versioning strategies.
Improvements 🚀
Swagger OneOf support for polymorphic responses
When enabling OneOf for polymorphism like the following:
.SwaggerDocument(c => c.UseOneOfForPolymorphism = true)The correct response swagger spec is not generated by NSwag by default. As requested by #807, the correct spec will now be generated.
Default response metadata for endpoints without a response type
Previously, if the endpoint doesn't define a particular response DTO type, a default produces 200 response metadata was being added which resulted in swagger spec like the following:
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {}
},
"application/json": {
"schema": {}
}
}
}Even though it's not incorrect, it can cause issues in some cases such as TS client generation. From now on if the endpoint doesn't specify a response DTO type, a 204 - No Content produces metadata would be added by default which results in more correct swagger spec such as the following:
"responses": {
"204": {
"description": "No Content"
}
}Detection of mapper type in unit tests
The logic for automatically detecting the endpoint mapper type during unit tests has been improved to prevent any "Endpoint mapper not set!" exceptions in some cases.
Fixes 🪲
Struct support for request DTOs
Adding the new reflection source generator broke support for struct types to be used for request DTOs, which has been corrected in this release.
Struct support for Reflection Source Generator
The reflection generator was not generating the correct source for unboxing value types.
Reflection generation for abstract types
The reflection generator was trying to generate code for deeply nested abstract types, which has now been fixed.
Incorrect constructor detection in Reflection Generator
The reflection generator was wrongly detecting constructors from base types instead of just stopping at the top most level.
Included validators causing an exception
There was a regression in the validation schema processor which resulted in included fluent validators causing an exception at the time of generating the swagger spec.
Unescaped back slashes breaking model binding
Incorrectly unescaped parameter values from the client was causing model binding failures which has been now corrected.
Rogue 200 response in Swagger spec
Due to a known bug in the .NET 9.0 SDK, a rogue 200 response metadata was being added to endpoints if the endpoints response DTO type is an IResult type and it's not a 200 status code. A workaround has been put in place to prevent that from happening.