Releases: FastEndpoints/FastEndpoints
v5.31 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 SDK Support
Migration to .NET 9 has been completed. We're currently referencing the GA build of the SDK. Once RTM comes out, the references will be updated in the following FastEndpoints release. The GA build seems to be quite stable and suitable for production use.
Source generator for avoiding reflection cost
The newly added Reflection Source Generator can be used in order to avoid the cost of runtime expression compilation & reflection based methods.
Multipart Form Data binding support for deeply nested complex DTOs
Binding deeply nested complex DTOs from incoming form-data (including files) is now supported. Please refer to the documentation here.
Ability to disable FluentValidation+Swagger integration per rule
The built-in FV+Swagger integration can be disabled per property rule with the newly added .SwaggerIgnore()
extension method as shown below.
sealed class MyValidator : Validator<MyRequest>
{
public MyValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.SwaggerIgnore();
}
}
Automatic transformation of 'ProblemDetails.Title' & 'ProblemDetails.Type' values according to 'StatusCode'
The ProblemDetails
Title and Type values will now be automatically determined/transformed according to the Status
code of the instance. The default behavior can be changed by setting your own TypeTransformer
and TitleTransformer
functions like so:
app.UseFastEndpoints(
cfg => cfg.Errors.UseProblemDetails(
pCfg =>
{
pCfg.TypeTransformer
= pd =>
{
switch (pd.Status)
{
case 404:
return "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.4";
case 429:
return "https://www.rfc-editor.org/rfc/rfc6585#section-4";
default:
return "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1";
}
};
}))
Enhance Swagger UI search bar behavior
The Swagger UI search bar is only capable of searching/filtering operations by tag values. The search bar has been enhanced via a custom injected JS plugin to be able to search the following sources:
- Operation paths
- Summary text
- Description text
- Operation parameters
- Request schema
- Response schema
Extension method to display Swagger operation IDs in Swagger UI
Calling the following extension method will cause the operation ids to show up in the swagger ui.
app.UseSwaggerGen(uiConfig: u => u.ShowOperationIDs());
Allow multiple gRPC event subscribers from the same machine
Previously it was only possible for a single gRPC subscriber to exist on a single machine for any given event type. Now it's possible to run the subscriber with a custom identifier per instance which will allow more than one subscriber to exist on a particular machine for an event type.
app.MapRemote(
"http://localhost:6000",
c =>
{
//set some unique value per each instance.
//process id here is just an example.
//the value just needs to be different across the multiple app/subscriber instances.
c.Subscribe<MyEvent, MyEventHandler>(clientIdentifier: Environment.ProcessId.ToString());
});
Improvements 🚀
Route template syntax highlighting for VS and Rider
Route template items such as the following will now be correctly syntax highlighted in Rider and Visual Studio:
Get("api/invoice/{id}/print")
Allow route param values with curly braces
The default request binder did not bind incoming route parameter values with curly braces such as:
http://localhost:5000/invoice/{123-456}
Better handling of 'JsonIgnore' attribute condition
The [JsonIgnore]
attribute on request/response DTO properties will now be taken into consideration only if it's declared in either of the following two ways:
[JsonIgnore] //without specifying a condition (defaults to JsonIgnoreCondition.Always)
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
This change only applies to Swagger generation and Non-STJ model binding behavior.
Fixes 🪲
Global 'TypeInfoResolver' not working
As reported by #783, there was an oversight in the way the built-in modifiers were checking the existence of custom attributes which lead to DTO properties being marked as "should not serialize".
Incorrect property name resolution of fluent validators with deeply nested DTOs
When json property naming policy is applied to fluentvalidation property chains, it was not correctly resolving the property chains for deeply nested request DTO properties.
'[FromBody]' attribute overriding media-type in Swagger
The usage of [FromBody]
attribute was incorrectly overriding the user specified media-type value to application/json
. Info: #800
Workaround for OpenApi bug in .NET 9.0 SDK
There's a bug in .NET 9.0 where any response DTOs implementing the IResult
interface will be filtered out from swagger documentation. A workaround has been put in place since a fix will not be available until .NET 9.1 or later according the ASPNET team. More info: #803
v5.30 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Fluent endpoint base class picker
A fluent endpoint base class picker similar to Ardalis.ApiEndpoints
has been added, with which you can pick and choose the DTO and Mapper types you'd like to use in a fluent manner.
sealed class MyEndpoint : Ep.Req<MyRequest>.Res<MyResponse>.Map<MyMapper>
{
...
}
sealed class MyEndpoint : Ep.Req<MyRequest>.NoRes.Map<MyMapper>
{
...
}
sealed class MyEndpoint : Ep.NoReq.Res<MyResponse>
{
...
}
Job Queuing support for Commands that return a result
A command that returns a result ICommand<TResult>
can now be queued up as a job. The result of a job can be retrieved via the JobTracker using its Tracking Id.
// queue the command as a job and retrieve the tracking id
var trackingId = new MyCommand { ... }.QueueJobAsync();
// retrieve the result of the command using the tracking id
var result = await JobTracker<MyCommand>.GetJobResultAsync<MyResult>(trackingId);
Click here to read the documentation for this feature.
Transform FluentValidation error property names with 'JsonPropertyNamingPolicy'
A new configuration setting has been introduced so that deeply nested request DTO property names can be transformed to the correct case using the JsonPropertyNamingPolicy
of the application.
app.UseFastEndpoints(c => c.Validation.UsePropertyNamingPolicy = true)
The setting is turned on by default and can be turned off by setting the above boolean to false
.
Skip anti-forgery checks for certain requests
The anti-forgery middleware will now accept a filter/predicate which can be used to skip processing certain requests if a given condition is matched. If the function returns true for a certain request, that request will be skipped by the anti-forgery middleware.
.UseAntiforgeryFE(httpCtx => !httpCtx.Request.Headers.Origin.IsNullOrEmpty())
Improvements 🚀
Make Pre/Post Processor Context's 'Request' property nullable
Since there are certain edge cases where the Request
property can be null
such as when STJ receives invalid JSON input from the client and fails to successfully deserialize the content, the pre/post processors would be executed (even in those cases) where the pre/post processor context's Request
property would be null. This change would allow the compiler to remind you to check for null if the Request
property is accessed from pre/post processors.
Preliminary support for .NET 9.0
Almost everything works with .NET 9 except for source generation. Full .NET 9 support will be available at the first FE release after .NET 9 final comes out.
Fixes 🪲
Nullable 'IFormFile' handling issue with 'HttpClient' extensions
The HttpClient
extensions for integration testing was not correctly handling nullable IFormFile
properties in request DTOs when automatically converting them to form fields.
Swagger processor issue with virtual path routes
The swagger processor was not correctly handling routes if it starts with a ~/
(virtual path that refers to the root directory of the web application).
Remove unreferenced schema from generated swagger document
When a request DTO has a property that's annotated with a [FromBody]
attribute, the parent schema was left in the swagger document components section as an unreferenced schema. These orphaned schema will no longer be present in the generated swagger spec.
Swagger request example issue with properties annotated with [FromBody]
Xml docs based example values were not correctly picked up for properties annotated with a [FromBody]
attribute, which resulted in a default sample request example being set in Swagger UI.
v5.29 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Multi-level test-collection ordering
Tests can now be ordered by prioritizing test-collections, test-classes in those collections as well as tests within the classes for fully controlling the order of test execution when test-collections are involved. See here for a usage example.
Customize character encoding of JSON responses
A new config setting has been added for customizing the charset of JSON responses. utf-8
is used by default. can be set to null
for disabling the automatic appending of the charset to the Content-Type
header of responses.
app.UseFastEndpoints(c => c.Serializer.CharacterEncoding = "utf-8")
Setting for allowing [JsonIgnore] attribute on 'required' DTO properties
STJ typically does not allow required
properties to be annotated with [JsonIgnore]
attribute. The following doesn't work out of the box:
public class MyRequest
{
[JsonIgnore]
public required string Id { get; init; }
}
The following setting is now enabled by default allowing you to annotate required
properties with [JsonIgnore]
:
app.UseFastEndpoints(c => c.Serializer.EnableJsonIgnoreAttributeOnRequiredProperties = true)
It's necessary to decorate required
properties with [JsonIgnore]
in situations where the same property is bound from multiple sources.
Read form-field values together with form-file sections
A new method has been added to conveniently read regular form-field values when they come in with multi-part form data requests while buffering is turned off.
await foreach (var sec in FormMultipartSectionsAsync(ct))
{
//reading the value of a form field
if (sec.IsFormSection && sec.FormSection.Name == "formFieldName")
{
var formFieldValue = await sec.FormSection.GetValueAsync(ct);
}
//obtaining the stream of a file
if (sec.IsFileSection && sec.FileSection.Name == "fileFieldName")
{
var fileStream = sec.FileSection.FileStream;
}
}
'IDisposable' & 'IAsyncDisposable' support for endpoint classes
You can now implement either 'IDisposable' or 'IAsyncDisposable' interfaces on your Endpoint classes and the correct dispose method will be called once the endpoint completes sending the response.
Ability to override Swagger Auto-Tagging per endpoint
Previously you could only choose a single strategy for tagging/grouping endpoints with Swagger. You could either use the path segment based auto-tagging or you could turn it off and manually tag each endpoint yourself. Now you can have auto-tagging enabled and override the auto-tag value per endpoint when necessary like so:
Description(x => x.AutoTagOverride("Overridden Tag Name"))
Improvements 🚀
Remove dependency on 'Xunit.Priority' package
The 'Xunit.Priority' package is no longer necessary as we've implemented our own test-case-orderer. If you've been using test ordering with the [Priority(n)]
attribute, all you need to do is get rid of any using
statements that refer to XUnit.Priority
.
Fixes 🪲
Issue with FluentValidation+Swagger integration
When a child validator has no parameterless constructor, the FV+Swagger integration was not able to construct an instance of the child validator causing it to be ignored when generating the Swagger spec. Child validators will now be instantiated via FE's service resolver in the validation schema processor to mitigate this issue.
v5.28 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Jwt token revocation middleware
Jwt token revocation can be easily implemented with the newly provided abstract class like so:
public class JwtBlacklistChecker(RequestDelegate next) : JwtRevocationMiddleware(next)
{
protected override Task<bool> JwtTokenIsValidAsync(string jwtToken, CancellationToken ct)
{
//return true if the supplied token is still valid
}
}
Simply register it before any auth related middleware like so:
app.UseJwtRevocation<JwtBlacklistChecker>()
.UseAuthentication()
.UseAuthorization()
Ability to override JWT Token creation options per request for Refresh Tokens
A couple of new optional hooks have been added that can be tapped in to if you'd like to modify Jwt token creation parameters per request, and also modify the token response per request before it's sent to the client. Per request token creation parameter modification may be useful when allowing the client to decide the validity of tokens.
Ability to subscribe to gRPC Events from Blazor Wasm projects
Until now, only gRPC Command initiations were possible from within Blazor Wasm projects. Support has been added to the FastEndpoints.Messaging.Remote.Core
project which is capable of running in the browser to be able to act as a subscriber for Event broadcasts from a gRPC server. See here for a sample project showcasing both.
Improvements 🚀
Get rid of Swagger middleware ordering requirements
Swagger middleware ordering is no longer important. You can now place the .SwaggerDocument()
and .UseSwaggerGen()
calls wherever you prefer.
Swagger OneOf support for polymorphic request/response DTOs
Correctly annotated polymorphic base types can now be used as request/response DTOs. The possible list of derived types will be shown in Swagger UI under a OneOf
field. To enable, decorate the base type with the correct set of attributes like so:
public class Apple : Fruit
{
public string Foo { get; set; }
}
public class Orange : Fruit
{
public string Bar { get; set; }
}
[JsonPolymorphic(TypeDiscriminatorPropertyName = "_t")]
[JsonDerivedType(typeof(Apple), "a")]
[JsonDerivedType(typeof(Orange), "o")]
public class Fruit
{
public string Baz { get; set; }
}
And set the following setting to true
when defining the Swagger document:
builder.Services.SwaggerDocument(c => c.UseOneOfForPolymorphism = true)
Fixes 🪲
Swagger generator issue with [FromBody] properties
The referenced schema was generated as a OneOf
instead of a direct $ref
when a request DTO property was being annotated with the [FromBody]
attribute.
Swagger route parameter detection issue
The Nswag operation processor did not correctly recognize route parameters in the following form:
api/a:{id1}:{id2}
Which has now been corrected thanks to PR #735
Kiota client generation issue with 'clean output' setting
If the setting for cleaning the output folder was enabled, Kiota client generation was throwing an error that it can't find the input swagger json file, because Kiota deletes everything in the output folder when that setting is enabled. From now on, if the setting is enabled, the swagger json file will be created in the system temp folder instead of the output folder.
Graceful shutdown issue of gRPC streaming
Server & Client Streaming was not listening for application shutdown which made app shutdown to wait until the streaming was finished. It has been fixed to be able to gracefully terminate the streams if the application is shutting down.
v5.27 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Support for cancellation of queued jobs
Queuing a command as a job now returns a Tracking Id with which you can request cancellation of a queued job from anywhere/anytime like so:
var trackingId = await new LongRunningCommand().QueueJobAsync();
await JobTracker<LongRunningCommand>.CancelJobAsync(trackingId);
Use either use the JobTracker<TCommand> generic class or inject a IJobTracker<TCommand> instance from the DI Container to access the CancelJobAsync() method.
NOTE: This feature warrants a minor breaking change. See how to upgrade below.
Check if app is being run in Swagger Json export mode and/or Api Client Generation mode
You can now use the following new extension methods for conditionally configuring your middleware pipeline depending on the mode the app is running in:
WebApplicationBuilder Extensions
bld.IsNotGenerationMode(); //returns true if running normally
bld.IsApiClientGenerationMode(); //returns true if running in client gen mode
bld.IsSwaggerJsonExportMode(); //returns true if running in swagger export mode
WebApplication Extensions
app.IsNotGenerationMode(); //returns true if running normally
app.IsApiClientGenerationMode(); //returns true if running in client gen mode
app.IsSwaggerJsonExportMode(); //returns true if running in swagger export mode
[AllowFileUploads] attribute for endpoint class decoration
When using attribute based configuration of endpoints you can now enable file upload support for endpoints like so:
[HttpPost("form"), AllowFileUploads]
sealed class MyEndpoint : Endpoint<MyRequest>
{
}
Blazor Wasm support for Remote messaging client
A new package has been added FastEndpoints.Messaging.Remote.Core
which contains only the core functionality along with a client that's capable of running in the web browser with Blazor Wasm.
Bypass endpoint throttle limits with integration tests
Integration tests can now bypass the throttle limits enforced by endpoints if they need to like so:
[Fact]
public async Task Throttle_Limit_Bypassing_Works()
{
var client = App.CreateClient(new()
{
ThrottleBypassHeaderName = "X-Forwarded-For" //must match with what the endpoint is looking for
});
for (var i = 0; i < 100; i++)
{
var (rsp, _) = await client.GETAsync<ThrottledEndpoint, Response>();
rsp.IsSuccessStatusCode.Should().BeTrue();
}
}
Each request made through that client would then automatically contain a X-Forwarded-For
header with a unique value per request allowing the test code to bypass the throttle limits set by the endpoint.
Improvements 🚀
Change default redirection behavior of cookie authentication middleware
The default behavior of the ASP.NET cookie auth middleware is to automatically return a redirect response when current user is either not authenticated or unauthorized. This default behavior is not appropriate for REST APIs because there's typically no login UI page as part of the backend server to redirect to, which results in a 404 - Not Found
error which confuses people that's not familiar with the cookie auth middleware. The default behavior has now been overridden to correctly return a 401 - Unauthorized
& 403 - Forbidden
as necessary without any effort from the developer.
Change 'RoleClaimType' of cookie auth to non-soap value
Until now, the CookieAuth.SignInAsync()
method was using the long soap version of 'Role Claim Type' value http://schemas.microsoft.com/ws/2008/06/identity/claims/role
which is not in line with what FE uses for JWT tokens. Now both JWT & Cookie auth uses the same value from the global config which is set like below or it's default value role
:
app.UseFastEndpoints(c=>c.Security.RoleClaimType = "role");
Workaround for CookieAuth middleware 'IsPersistent' misbehavior
By default, in ASP.NET Cookie middleware, if you specify an Expiry
or Max-Age
at the global/middleware level, setting IsPersitent = false
will have no effect when signing in the user, as the middleware sets Expiry/Max-Age
on the generated cookie anyway, making it a persistent cookie. A workaround has been implemented to fix this behavior.
Fixes 🪲
[HideFromDocs] attribute missing issue with the source generator
If the consuming project didn't have a global using FastEndpoints;
statement, the generated classes would complain about not being able to located the said attribute, which has now been rectified.
Service registration source generator issue with third party source generators
The service registration source generator was encountering a compatibility issue with partial classes generated by other source generators such as Mapster, which has no been fixed.
Swagger UI issue with path param names that are substrings
If a route contains multiple path parameters where one is a substring of another, the generated swagger spec would cause Swagger UI to not match the path param correctly. An example of this would be a route such as the following:
/api/parents/{ParentId}/children/{Id}
Path segment matching has been changed to include the parenthesis as well in order to prevent substring matching.
Default values for Enum properties not displayed in Swagger UI
Enum property default values were not being displayed in Swagger UI due to incorrectly generated Swagger Spec due to a bug in NSwag. A workaround has been implemented to generate the correct spec.
Incorrect property name detection for STJ deserialization exceptions
When deserializing Enum arrays from query parameters, if one of the values is invalid, the following error response was generated with an incorrect property name:
{
"statusCode": 400,
"message": "One or more errors occurred!",
"errors": {
"0]": [
"The JSON value could not be converted to MyEnum. Path: $[0] | LineNumber: 0 | BytePositionInLine: 9."
]
}
}
Which has been now corrected to provide a better error message and the correct property name as follows:
{
"statusCode": 400,
"message": "One or more errors occurred!",
"errors": {
"myEnumValues": [
"Value [Xyz] is not valid for a [MyEnum[]] property!"
]
}
}
Default response/produces metadata was ignored for 'IResult' response types
There was an oversight in adding default response metadata to endpoints that were returning 'IResult' types, which has now been rectified.
Minor Breaking Changes ⚠️
Job storage record & storage provider update
In order to support the new job cancellation feature, the following steps must be taken for a smooth migration experience:
- Purge all existing queued jobs from storage or allow them to complete before deploying new version.
- Add a
Guid
property calledTrackingID
to the job storage record entity and run a migration if using EF Core. - Implement
CancelJobAsync()
method on the job storage provider
v5.26 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Idempotency support based on 'OutputCaching' middleware
FastEndpoints now ships with built-in endpoint idempotency support built around the OutputCaching middleware.
Specify additional Http Verbs/Methods for endpoints globally
In addition to the Verbs you specify at the endpoint level, you can now specify Verbs to be added to endpoints with the global configurator as well as endpoint groups like so:
//global configurator
app.UseFastEndpoints(
c => c.Endpoints.Configurator =
ep =>
{
ep.AdditionalVerbs(Http.OPTIONS, Http.HEAD);
})
//endpoint group
sealed class SomeGroup : Group
{
public SomeGroup()
{
Configure(
"prefix",
ep =>
{
ep.AdditionalVerbs(Http.OPTIONS, Http.HEAD);
});
}
}
Collection-Fixture support for Testing
This release brings easy access to xUnit collection-fixtures for integration testing. Please see the documentation for details.
Export multiple swagger json files in one go
A new overload has been added to the ExportSwaggerJsonAndExitAsync()
method for exporting multiple Swagger Json files using the FastEndpoints.ClientGen.Kiota
library.
await app.ExportSwaggerJsonAndExitAsync(
ct: default,
c =>
{
c.DocumentName = "v1";
c.DestinationPath = "/export/path";
c.DestinationFileName = "v1.json";
},
c =>
{
c.DocumentName = "v2";
c.DestinationPath = "/export/path";
c.DestinationFileName = "v2.json";
});
Improvements 🚀
Throw meaningful exception when incorrect JWT singing algorithm is used
When creating Asymmetric JWTs, if the user forgets to change the default SigningAlgorithm
from HmacSha256
to something suitable for Asymmetric
signing, a helpful exception message will be thrown instructing the user to correct the mistake. More info: #685
Establish SSE connection before any data is available
The SSE request would previously stay in a pending state if there was no initial data available to be sent to the client. Now the client would receive the response headers and await future data in a "connection established" state, even if no data has been received from the server.
Workaround for NSwag bug with nullable enum parameters
Swagger UI fails to correctly render a dropdown for nullable Enum DTO properties due to a bug in NSwag where the schema is always referenced via a OneOf
, instead of directly referencing it with a $ref
. A workaround has been implemented to mitigate the issue. #699
Fixes 🪲
ACL source generator wasn't filtering out internal public static fields
Generated ACL incorrectly contained the Descriptions
property in the permission dictionary items due to not being filtered out correctly, which has now been fixed.
Swagger generation failure due to multiple route constraints
Swagger generation was throwing an exception if an endpoint with multiple route constraints on a single parameter such as the following was encountered:
/{member:int:min(1):max(5)}/
Minor Breaking Changes ⚠️
Move static properties of 'ProblemDetails' class to global config
Static configuration properties that used to be on the ProblemDetails
class will have to be set from the global configuration going forward like so:
app.UseFastEndpoints(
c => c.Errors.UseProblemDetails(
x =>
{
x.AllowDuplicateErrors = true; //allows duplicate errors for the same error name
x.IndicateErrorCode = true; //serializes the fluentvalidation error code
x.IndicateErrorSeverity = true; //serializes the fluentvalidation error severity
x.TypeValue = "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1";
x.TitleValue = "One or more validation errors occurred.";
x.TitleTransformer = pd => pd.Status switch
{
400 => "Validation Error",
404 => "Not Found",
_ => "One or more errors occurred!"
};
}));
v5.25 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
New generic attribute [Group<T>] for attribute based endpoint group configuration
When using attribute based endpoint configuration, you can now use the generic 'Group' attribute to specify the group which the endpoint belongs to like so:
//group definition class
sealed class Administration : Group
{
public Administration()
{
Configure(
"admin",
ep =>
{
ep.Description(
x => x.Produces(401)
.WithTags("administration"));
});
}
}
//using generic attribute to associate the endpoint with the above group
[HttpPost("login"), Group<Administration>]
sealed class MyEndpoint : EndpointWithoutRequest
{
...
}
Specify a label, summary & description for Swagger request examples
When specifying multiple swagger request examples, you can now specify the additional info like this:
Summary(
x =>
{
x.RequestExamples.Add(
new(
new MyRequest { ... },
"label",
"summary",
"description"));
});
Automatic type inference for route params from route constraints for Swagger
Given route templates such as the following that has type constraints for route params, it was previously only possible to correctly infer the type of the parameter (for Swagger spec generation) if the parameters are being bound to a request DTO and that DTO has a matching property. The following will now work out of the box and the generated Swagger spec will have the respective parameter type/format.
sealed class MyEndpoint : EndpointWithoutRequest
{
public override void Configure()
{
Get("test/{id:int}/{token:guid}/{date:datetime}");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken c)
{
var id = Route<int>("id");
var token = Route<Guid>("token");
var date = Route<DateTime>("date");
await SendAsync(new { id, token, date });
}
}
You can register your own route constraint types or even override the default ones like below by updating the Swagger route constraint map:
FastEndpoints.Swagger.GlobalConfig.RouteConstraintMap["nonzero"] = typeof(long);
FastEndpoints.Swagger.GlobalConfig.RouteConstraintMap["guid"] = typeof(Guid);
FastEndpoints.Swagger.GlobalConfig.RouteConstraintMap["date"] = typeof(DateTime);
Form related exception transformer function setting
When accessing Form data there are various cases where an exception would be thrown internally by ASP.NET such as in the case of the incoming request body size exceeding the default limit or whatever you specify like so:
bld.WebHost.ConfigureKestrel(
o =>
{
o.Limits.MaxRequestBodySize = 30000000;
});
If the incoming request body size is more than MaxRequestBodySize
, Kestrel would automatically short-circuit the request with a 413 - Content Too Long
response, which may not be what you want. You can instead specify a FormExceptionTrasnformer
func to transform the exception in to a regular 400 error/problem details JSON response like so:
app.UseFastEndpoints(
c =>
{
c.Errors.UseProblemDetails(); //this is optional
c.Binding.FormExceptionTransformer =
ex => new ValidationFailure("GeneralErrors", ex.Message);
})
Which would result in a JSON response like so:
{
"type": "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"instance": "/upload-file",
"traceId": "0HN39MGSS8QDA:00000001",
"errors": [
{
"name": "generalErrors",
"reason": "Request body too large. The max request body size is 30000000 bytes."
}
]
}
Ability to disable WAF/SUT caching in AppFixtures
AppFixture
's default behavior of internally caching the SUT/WAF instance per derived AppFixture
type can now be disabled simply by decorating the derived fixture class with an attribute like so:
[DisableWafCache]
public class MyAppFixture : AppFixture<Program> { ... }
Fixes 🪲
Contention issue resulting in random 415 responses
There was a possible contention issue that could arise in an extremely niche edge case where the WAFs could be instantiated in quick succession which results in tests failing due to 415 responses being returned randomly. This has been fixed by moving the necessary work to be performed at app startup instead of at the first request for a particular endpoint. More info: #661
Eliminate potential contention issues with 'AppFixture'
AppFixture
abstract class has been improved to use an Async friendly Lazy initialization technique to prevent any chances of more than a single WAF being created per each derived AppFixture
type in high concurrent situations. Previously we were relying solely on ConcurrentDictionary
's thread safety features which did not always give the desired effect. Coupling that with Lazy initialization seems to solve any and all possible contention issues.
Correct exception not thrown when trying to instantiate response DTOs with required properties
When the response DTO contains required properties like this:
public class MyResponse
{
public required string FullName { get; set; }
}
If an attempt was made to utilize the auto response sending feature by setting properties of the Response
object, a 400 validation error was being thrown instead of a 500 internal server error. It is now correctly throwing the NotSupportedException
as it should, because FE cannot automatically instantiate objects that have required properties and the correct usage is for you to instantiate the object yourself and set it to the Response
property, which is what the exception will now be instructing you to do.
Breaking Changes ⚠️
The way multiple Swagger request examples are set has been changed
Previous way:
Summary(s =>
{
s.RequestExamples.Add(new MyRequest {...});
});
New way:
s.RequestExamples.Add(new(new MyRequest { ... })); // wrapped in a RequestExample class
'PreSetupAsync()' trigger behavior change in `AppFixture` class
Previously the PreSetupAsync()
virtual method was run per each test-class instantiation. That behavior does not make much sense as the WAF instance is created and cached just once per test run. The new behavior is more in line with other virtual methods such as ConfigureApp()
& ConfigureServices()
that they are only ever run once for the sake of creation of the WAF. This change will only affect you if you've been creating some state such as a TestContainer
instance in PreSetupAsync
and later on disposing that container in TearDownAsync()
. From now on you should not be disposing the container yourself if your derived AppFixture
class is being used by more than one test-class. See this gist to get a better understanding.
Consolidate Jwt key signing algorithm properties into one
JwtCreationOptions
class had two different properties to specify SymmetricKeyAlgorithm
as well as AsymmetricKeyAlgorithm
, which has now been consolidated into a single property called SigningAlgorithm
.
Before:
var token = JwtBearer.CreateToken(
o =>
{
o.SymmetricKeyAlgorithm = SecurityAlgorithms.HmacSha256Signature;
//or
o.AsymmetricKeyAlgorithm = SecurityAlgorithms.RsaSha256;
});
After:
var token = JwtBearer.CreateToken(
o =>
{
o.SigningStyle = TokenSigningStyle.Symmetric;
o.SigningAlgorithm = SecurityAlgorithms.HmacSha256Signature;
});
v5.24 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Customize error response Content-Type globally
The default `content-type` header value for all error responses is `application/problem+json`. The default can now be customized as follows:app.UseFastEndpoints(c => c.Errors.ContentType = "application/json")
'DontAutoSend()' support for 'Results<T1,T2,...>' returning endpoint handler methods
When putting a post-processor in charge of sending the
response, it was not previously supported when the handler method returns a Results<T1,T2,...>
. You can now use the DontAutoSend()
config option with such endpoint
handlers.
'ProblemDetails' per instance title transformer
You can now supply a delegate that will transform the Title
field of ProblemDetails
responses based on some info present on the final problem details instance.
For example, you can transform the final title value depending on the status code of the response like so:
ProblemDetails.TitleTransformer = p => p.Status switch
{
400 => "Validation Error",
404 => "Not Found",
_ => "One or more errors occurred!"
};
Setting for allowing empty request DTOs
By default, an exception will be thrown if you set the TRequest
of an endpoint to a class type that does not have any bindable properties. This behavior can now be
turned off if your use case requires empty request DTOs.
app.UseFastEndpoints(c => c.Endpoints.AllowEmptyRequestDtos = true)
sealed record HelloRequest;
sealed class MyEndpoint : Endpoint<HelloRequest>
{
public override void Configure()
{
Post("test");
Description(x => x.ClearDefaultAccepts()); //this will be needed for POST requests
AllowAnonymous();
}
}
Ability to specify output file name with 'ExportSwaggerJsonAndExitAsync()'
It is now possible to customize the name of the exported swagger.json
file when exporting a swagger document to disk with the ExportSwaggerJsonAndExitAsync()
method.
Improvements 🚀
Support async setup activity that contributes to WAF creation in 'AppFixture'
Previously it was not possible to do any setup activity that directly contributes to the creation of the WAF instance. Now it can be achieved like so:
using Testcontainers.MongoDb;
public class Sut : AppFixture<Program>
{
const string Database = "TestingDB";
const string RootUsername = "root";
const string RootPassword = "password";
MongoDbContainer _container = null!;
protected override async Task PreSetupAsync()
{
// anything that needs to happen before the WAF is initialized can be done here.
_container = new MongoDbBuilder()
.WithImage("mongo")
.WithUsername(RootUsername)
.WithPassword(RootPassword)
.WithCommand("mongod")
.Build();
await _container.StartAsync();
}
protected override void ConfigureApp(IWebHostBuilder b)
{
b.ConfigureAppConfiguration(
c =>
{
c.AddInMemoryCollection(
new Dictionary<string, string?>
{
{ "Mongo:Host", _container.Hostname },
{ "Mongo:Port", _container.GetMappedPublicPort(27017).ToString() },
{ "Mongo:DbName", Database },
{ "Mongo:UserName", RootUsername },
{ "Mongo:Password", RootPassword }
});
});
}
protected override async Task TearDownAsync()
=> await _container.DisposeAsync();
}
Automatically rewind request stream with 'IPlainTextRequest' when 'EnableBuffering()' is used.
It was not possible to manually re-read the request body stream due to IPlainTextRequest
automatically consuming the stream even with the use of EnableBuffering()
.
The stream will now be automatically re-wound if EnableBuffering()
is detected in order to allow re-reading the stream by the user.
Filter out illegal header names from being created as request parameters in Swagger docs
According to the OpenApi Spec, there are certain header names that are not allowed as part of the regular parameter specification in the Swagger Spec. These
Headers (Accept
, Content-Type
and Authorization
) are described using other OpenApi fields. The FE Swagger generation did not previously respect/filter them out
when processing properties marked with [FromHeader]
.
'[FromBody]'attribute support for strongly-typed integration testing
There was no support for correctly integration testing an endpoint where its request DTO had a property decorated with [FromBody]
attribute. This scenario is now
correctly implemented and handled by the strongly-typed extension methods for the HttpClient
.
Hydrate typed integration testing route url with values from request DTO
Until now, when a strongly-typed integration test calls the endpoint, it was using a faux url with the correct number of route segments so that the correct endpoint
gets called. Now, if there's a request DTO instance present, the actual values from the request DTO properties would be substituted resulting an actual url being
called with actual values you supply during the test.
Fixes 🪲
Prevent duplicate Swagger tag descriptions
An issue was reported with the swagger tag descriptions being repeated one for each endpoint in the generated swagger document. It has been fixed to prevent that from
happening under any circumstances.
v5.23 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Keyed service injection support
Keyed services introduced in .NET 8 can be injected like so:
//property injection
[KeyedService("KeyName")]
public IHelloWorldService HelloService { get; set; }
//constructor injection
public MyEndpoint([FromKeyedServices("KeyName")]IHelloWorldService helloScv)
{
...
}
//manual resolving
Resolve<IHelloWorldService>("KeyName");
Model binding support for Typed Http Headers
Typed Http Headers can be bound by simply annotating with a [FromHeader(...)]
attribute like so:
sealed class MyRequest : PlainTextRequest
{
[FromHeader("Content-Disposition")]
public ContentDispositionHeaderValue Disposition { get; set; }
}
NOTE: Only supported on .Net 8+ and typed header classes from Microsoft.Net.Http.Headers
namespace.
Ability to strip symbols from Swagger group/tag names
Given a route like:
/api/admin-dashboard/ticket/{id}
And swagger config like this:
bld.Services.SwaggerDocument(
o =>
{
o.AutoTagPathSegmentIndex = 2;
o.TagCase = TagCase.TitleCase;
o.TagStripSymbols = true; //this option is new
});
The resulting group/tag name will be:
AdminDashboard
Improvements 🚀
Better separation of concerns for integration test-classes
Previously, the recommendation was to create as many derived TestFixture<TProgram>
classes as needed and use them as the means to share data/state among multiple test-methods of the same test-class.
A new StateFixture
abstract class has been introduced. So that your test suit can have just a couple of "App Fixtures"(AppFixture<TProgram>
) - each representing a uniquely configured SUT(live app/WAF instance), while each test-class can have their own lightweight "StateFixture" for the sole purpose of sharing state/data amongst multiple test-methods of that test-class.
This leads to better test run performance as each unique SUT is only created once no matter how many test classes use the same derived AppFixture<TProgram>
class. Please re-read the integration testing doc page for further clarification.
Relax DTO type constraint on 'Validator<TRequest>' class
The type constraint on the Validator<TRequest>
class has been relaxed to notnull
so that struct type DTOs can be validated.
Allow TestFixture's TearDownAsync method to make Http calls
Previously the TestFixture<TProgram>
class would dispose the default http client before executing the teardown method. This prevents cleanup code to be able to make http calls. Now the http client is only disposed after TearDownAsync
has completed.
Ability to customize job queue storage provider re-check frequency
You can now customize the job queue storage provider re-check time delay in case you need re-scheduled jobs to execute quicker.
app.UseJobQueues(
o =>
{
o.StorageProbeDelay = TimeSpan.FromSeconds(5);
});
Fixes 🪲
Swagger UI displaying random text for email fields
When a FluentValidator rule is attached to a property that's an email address, Swagger UI was displaying a random string of characters instead of showing an email address. This has been rectified.
Swagger generation issue with form content and empty request DTO
Endpoints configured like below, where the request dto type is EmptyRequest
and the endpoint allows form content; was causing the swagger processor to throw an error, which has been rectified.
sealed class MyEndpoint : EndpointWithoutRequest<MyResponse>
{
public override void Configure()
{
...
AllowFileUploads();
}
}
Swagger issue with reference type DTO props being marked as nullable
Given a DTO such as this:
sealed class MyRequest
{
public string PropOne { get; set; }
public string? PropTwo { get; set; }
}
The following swagger spec was generated before:
"parameters": [
{
"name": "propOne",
"in": "query",
"required": true,
"schema": {
"type": "string",
"nullable": true //this is wrong as property is not marked nullable
}
},
{
"name": "propTwo",
"in": "query",
"schema": {
"type": "string",
"nullable": true
}
}
]
Non-nullable reference types are now correctly generated as non-nullable.
Swagger security processor was unable to handle Minimal Api Endpoints with Auth requirements
A NRE was being thrown when the swagger security operation processor was encountering minimal api endpoints with auth requirements.
v5.22 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Attribute driven response headers
Please see the documentation for more information.
Allow a Post-Processor to act as the sole mechanism for sending responses
As shown in this example, a post-processor can now be made the sole orchestrator of sending the
appropriate response such as in the case with the "Results Pattern".
Support for generic commands and command handlers
Please see the documentation for more information.
Improvements 🚀
Auto resolving of Mappers in unit tests
Previously it was necessary for the user to instantiate and set the mapper on endpoints when unit testing endpoints classes. It is no longer necessary to do so
unless you want to. Existing code doesn't need to change as the Mapper
property is still publicly settable.
Respect default values of constructor arguments when model binding
The default request binder will now use the default values from the constructor arguments of the DTO when instantiating the DTO before model binding starts. For
example, the SomeOtherParam
property will have a value of 10
if no other binding sources provides a value for it.
record MyRequest(string SomeParam,
int SomeOtherParam = 10);
Warn user about illegal request DTO types
FastEndpoints only supports model binding with DTOs that have publicly accessible properties. The following is not supported:
sealed class MyEndpoint : Endpoint<Guid>
A more detailed NotSupportedException
is now being thrown to make it easy to track down the offending endpoint.
Property naming policy was not applied to route parameters when generating Swagger spec
If you had a request DTO like this:
sealed class MyRequest
{
public long SomeId { get; set; }
}
And a route like this:
public override void Configure()
{
Get("/something/{someID}");
}
Where the case of the parameter is different, and also had a property naming policy applied like this:
app.UseFastEndpoints(c => c.Serializer.Options.PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower)
Previously the Swagger spec generated would have a mismatched operation path parameter {someID}
and a Swagger request parameter some-id
.
Now the Swagger path parameter is correctly rendered to match with the exact value/case as the request parameter.
Change default output location for Kiota Api Client generation
When using .MapApiClientEndpoint()
, previously the default value was a sub folder called ClientGen
under the current folder. The default has been changed to a sub folder called KiotaClientGen
in the current user's TEMP
folder away from any project/source files, which is a much safer default location.
Fixes 🪲
Type discovery source generator creating duplicates for partial classes
The type discovery source generator will now correctly detect partial classes of targets and only create a single entry. #574
Correct handling of Swagger request param examples
Examples for request parameters were previously rendered as strings instead of the respective primitives or json objects.
Given the DTO model (with examples as xml tags):
sealed class MyRequest
{
/// <example>
/// 10
/// </example>
public int SomeNumber { get; set; }
/// <example>
/// ["blah1","blah2"]
/// </example>
public string[] SomeList { get; set; }
/// <example>
/// { id : 1000, name : "john" }
/// </example>
public Nested SomeClass { get; set; }
public sealed class Nested
{
public int Id { get; set; }
public Guid GuidId { get; set; }
public string Name { get; set; }
}
}
Will now be correctly rendered as follows:
"parameters": [
{
"name": "someNumber",
"example": 10
},
{
"name": "someList",
"example": [
"blah1",
"blah2"
]
},
{
"name": "someClass",
"example": {
"id": 1000,
"name": "john"
}
}
]
MinLength validator rule was not detected after a NotEmpty rule in Swagger generation
RuleFor(r => r.Name)
.NotEmpty()
.MinimumLength(10); // this was not being picked up before
Breaking Changes ⚠️
Minor behavior change of 'HttpClient' extensions when integration testing
Previous behavior of the HttpClient extension methods such as GETAsync()
,PUTAsync()
was to swallow any deserialization exceptions and return a default value for the TestResult.Result
property. While this works fine for 99% of scenarios, it's not apparent for the developer why exactly it's null such as in the case of #588
From now on, if an exception occurs with deserialization on successful requests, it will not be swallowed. The test will fail with an exception.
'SendRedirectAsync()' method signature change
The method signature has been updated to the following:
SendRedirectAsync(string location, bool isPermanent = false, bool allowRemoteRedirects = false)
This would be a breaking change only if you were doing any of the following:
-
Redirecting to a remote url instead of a local url. In which case simply set
allowRemoteRedirects
totrue
. otherwise the new behavior will throw an exception.
this change was done to prevent open redirect attacks by default. -
A cancellation token was passed in to the method. The new method does not support cancellation due to the underlying
Results.Redirect(...)
methods do not support
cancellation.
Minor behavior change for exception handling with 'Post Processors'
Previously when an exception is handled by a post-processor
the captured exception would only be thrown out to the middleware pipeline in case the post-processor hasn't already written to the response stream. Detecting this
reliably has proven to be difficult and now your post-processor must explicitly call the following method if it's handling the exception itself and don't need the
exception to be thrown out to the pipeline.
public class ExceptionProcessor : IPostProcessor<Request, Response>
{
public async Task PostProcessAsync(IPostProcessorContext<Request, Response> ctx, ...)
{
ctx.MarkExceptionAsHandled();
//do your exception handling after this call
}
}
Rename 'UseAntiForgery()' method
The builder.Services.UseAntiForgery()
extension method has been renamed to .UseAntiforgeryFE()
in order to avoid confusion.