Releases: FastEndpoints/FastEndpoints
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.
v5.21.2 Patch Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
Fixes 🪲
- Unit test not calling
Configure()
method #569 - Source generators causing namespace conflict in multi-project generation
v5.21 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Api Client generation using Kiota
Kiota is now the recommended way to generate API Clients. Please see the documentation on how
to use it. The previous methods for client generation using NSwag are still valid but may be deprecated at a future point in time.
Attribute based Pre/Post Processor configuration
When doing simple attribute based endpoint configuration instead of using the Configure()
method, you can now add pre/post processors to the endpoint like so:
[HttpPost("/test"),
PreProcessor<PreProc>,
PostProcessor<PostProc>]
sealed class Endpoint : Endpoint<Request, Response>
{
public override Task HandleAsync(Request r, CancellationToken c)
{
...
}
}
Ability to specify descriptions with ACL generation
You can now specify a description/xml doc summary for individual permission items when source generating them. See the documentation on how to use it.
[HideFromDocs] attribute for removing properties from Swagger schema
sealed class MyRequest
{
[HideFromDocs]
public int Internal { get; set; } //this will not appear in swagger schema
public string Name { get; set; }
}
Improvements 🚀
Treat validation rules with conditions attached as optional properties in Swagger spec.
If a validation rule is conditional, like in the example below, that particular DTO property will be considered optional and will not be marked as required in the Swagger Schema.
RuleFor(x => x.Id) //this property will be a required property in the swagger spec
.NotEmpty(); //because there's no 'When(...)' condition attached to it.
RuleFor(x => x.Age) //this will be an optional property in swagger spec because
.NotEmpty() //'NotEmpty()' is conditional.
.When(SomeCondition);
For this to work, the rules have to be written separately as above. I.e. the .When(...)
condition must proceed immediately after the .NotEmpty()
or .NotNull()
rule.
Support for 'UrlSegmentApiVersionReader' of 'Asp.Versioning.Http'
Only the HeaderApiVersionReader
was previously supported. Support for doing versioning based on URL segments using the Asp.Versioning.Http
package is now working
correctly.
Automatically forward endpoint attribute annotations
When using attribute annotations to configure endpoints, any custom attributes were not automatically added to endpoint metadata previously. You would've had to do the following and use the Configure()
method for configuration instead if you had some custom attributes you needed to use:
Description(b => b.WithMetadata(new CustomAttribute()));
Now, all custom attributes are automatically added/forwarded to endpoint metadata when you configure endpoints using attribute annotations.
[HttpGet("/"), CustomAttribute]
public class Endpoint : Endpoint<Request, Response>
Note: you still have to choose one of the strategies for endpoint configuration (attributes or configure method). Mixing both is not allowed.
Optimize source generators
All source generators were refactored to reduce GC pressure by reducing heap allocations. Allocations are now mostly done when there's actually a need to regenerate the
source code.
Micro optimization with 'Concurrent Dictionary' usage
Concurrent dictionary GetOrAdd()
overload with lambda parameter seems to perform a bit better in .NET 8. All locations that were using the other overload was changed to use the overload with the lambda.
Fixes 🪲
'JsonNamingPolicy.SnakeCaseLower' was causing incorrect Swagger Schema properties
Snake case policy did not exist before .NET 8, so it's usage was not accounted for in the Swagger operation processor, which has now been corrected.
v5.20 Release
✨ Looking For Sponsors ✨
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
New 🎉
Support for .Net 8.0
The project is now developed and built using .NET 8.0 while supporting .NET 6 & 7 as well. You can upgrade your existing projects simply by targeting .NET 8
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
The .NET 8 Request Delegate Generator is not yet compatible with FastEndpoints as FE has it's own endpoint mapping and model binding system which will require a complete rewrite as a Source Generator to properly support Native AOT. We're currently investigating ways to achieve that but cannot give a timeframe on completion as it's a massive undertaking. You can see our internal discussion about this matter on discord.
Simpler way to register Pre/Post Processors with DI support
Processors can now be configured just by specifying the type of the processor without the need for instantiating them yourself.
public class MyEndpoint : EndpointWithoutRequest
{
public override void Configure()
{
...
PreProcessor<PreProcessorOne>();
PreProcessor<PreProcessorTwo>();
}
}
While the old PreProcessors(...) method continues to work, the new method automatically resolves any constructor injected dependencies without you having to manually register the processors in DI.
Exception handling capability for Post-Processors
Post-Processors can now handle uncaught exceptions as an alternative to an exception handling middleware.
Please see the documentation page for details.
Shared state support for global Pre/Post Processors
Global Pre/Post Processors now have shared state support with the following two newly added abstract types:
- GlobalPreProcessor<TState>
- GlobalPostProcessor<TState>
Ability to hide the error reason in the JSON response when using the default exception handler middleware
The actual error reason can now be hidden from the client by configuring the exception handler middleware like so:
app.UseDefaultExceptionHandler(useGenericReason: true);
Fixes 🪲
Auto binding collections of form files fails after first request
An object disposed error was being thrown in subsequent requests for file collection submissions due to a flaw in the model binding logic, which has now been corrected.
Incorrect validation error field names when nested request DTO property has [FromBody] attribute
JSON error responses didn't correctly render the deeply nested property chain/paths when the following conditions were met:
- Request DTO has a property annotated with
[FromBody]
attribute - The bound property is a complex object graph
- Some validation errors occur for deeply nested items
This has been fixed to correctly render the property chain of the actual item that caused the validation failure.
More info here.
Test assertions couldn't be done on ProblemDetails DTO
The ProblemDetails
DTO properties had private setter properties preventing STJ from being able to deserialize the JSON which has now been corrected.
Minor Breaking Changes ⚠️
Pre/Post Processor interface changes
Due to the Processor related new features introduced in this release, the *ProcessAsync(...)
method signatures had to be changed. The previous arguments are still available but they have been grouped/pushed into a processor context object. The new method signatures look like the following. Migrating your existing pre/post processors shouldn't take more than a few minutes.
PreProcessor Signature:
sealed class MyProcessor : IPreProcessor<MyRequest>
{
public Task PreProcessAsync(IPreProcessorContext<MyRequest> ctx, CancellationToken c)
{
...
}
}
PostProcessor Signature:
sealed class MyProcessor : IPostProcessor<MyRequest, MyResponse>
{
public Task PostProcessAsync(IPostProcessorContext<MyRequest, MyResponse> ctx, CancellationToken c)
{
...
}
}
AddJWTBearerAuth() default claim type mapping behavior change
The JwtSecurityTokenHandler.DefaultInboundClaimTypeMap
static dictionary is presently used by ASP.NET for mapping claim types for inbound claim type mapping. In most cases people use JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()
to not have long claim types such as http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
as the claim type for the sub
claim for example.
The default behavior has now been changed to make the claim types not use the SOAP type identifiers like above. If for some reason you'd like to revert to the old behavior, it can be achieved like so:
.AddJWTBearerAuth("jwt_signing_key", o =>
{
o.MapInboundClaims = true;
});
If using the new default behavior, it is highly recommended to invalidate any previously issued JWTs by resetting your JWT signing keys or any other means neccessary to avoid any potential issues with claim types not matching. This obviously means your clients/users will have re-login (obtain new JWTs). If that's not an option, simply set
.MapInboundClaims = true;
as mentioned above to use the previous behavior.
See #526 for more info.
v5.19.2 Patch Release
Fixes 🪲
Tests couldn't assert on 'ProblemDetails' DTO due to having private property setters
Trying to assert on properties of ProblemDetails
when testing, STJ could not deserialize the error JSON response due to the DTO having incorrect access modifiers for public properties, which has been corrected with this patch release.
v5.19.1 Patch Release
Fixes 🪲
Auto binding collections of form files fails after first request
An object disposed error was being thrown in subsequent requests for file collection submissions due to a flaw in the model binding logic, which has now been corrected.