FastEndpoints Versions Save

A light-weight REST API development framework for ASP.NET 6 and newer.

v5.25

1 week ago

✨ 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<TEndpointGroup>' 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

1 month ago

✨ 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

2 months ago

✨ 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

3 months ago

✨ 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 to true. 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

4 months ago

✨ 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

4 months ago

✨ 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

5 months ago

✨ 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

6 months ago

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

6 months ago

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.

v5.19

6 months ago

✨ Looking For Sponsors ✨

FastEndpoints needs sponsorship to sustain the project. Please help out if you can.


New 🎉

Model binding collections of 'IFormFile' from incoming form data

The following types of properties can now be automatically model bound from file form data fields.

class Request
{
    public IEnumerable<IFormFile> Cars { get; set; }
    public List<IFormFile> Boats { get; set; }    
    public IFormFileCollection Jets { get; set; }
}

When submitting collections of form files, the incoming field names can be one of the following 3 formats:

Format One Format Two Format Three
# 1 Cars Boats[1] Jets[]
# 2 Cars Boats[2] Jets[]
Multiple request examples for Swagger

Multiple examples for the request DTO can be specified by either setting the ExampleRequest property of the Summary class multiple times or adding to the RequestExamples collection like so:

Summary(s =>
{
    s.ExampleRequest = new MyRequest {...};  
    s.ExampleRequest = new MyRequest {...};
    s.RequestExamples.Add(new MyRequest {...});
});
Anti-forgery token validation middleware

Please see the documentation page for details of this feature.

Thank you Wàn Yǎhǔ for the contribution.

Support for simple validation with 'DataAnnotations'
//enable the feature at startup.

app.UseFastEndpoints(c.Validation.EnableDataAnnotationsSupport = true;)

//decorate properties with DataAnnotations attributes

sealed class Request
{
    [Required, StringLength(10, MinimumLength = 2)]
    public string Name { get; set; }
}

//can be used together with `FluentValidations` rules

sealed class MyValidator : Validator<Request>
{
    public MyValidator()
    {
        RuleFor(x => x.Id).InclusiveBetween(10, 100);
    }
}

Note: there's no swagger integration for data annotations.

Thank you Wàn Yǎhǔ for the contribution.

Improvements 🚀

Prevent swallowing of STJ exceptions in edge cases

If STJ throws internally after it has started writing to the response stream, those exceptions will no longer be swallowed. This can happen in rare cases such as when the DTO being serialized has an infinite recursion depth issue.

Deep nested collection property name serialization support with 'AddError(expression, ...)' method

When doing a manual add error call like this:

AddError(r => r.ObjectArray[i].Test, "Some error message");

Previous output was:

New output:

Thank you Mattis Bratland for the contribution

Misc. improvements
  • Upgrade dependencies to latest
  • Minor internal code refactors/optimizations

Fixes 🪲

Response DTO property description not being detected

When the response DTO property description was provided by a lambda expression and the respective DTO property is also decorated with [JsonPropertyName] attribute, the Swagger operation processor was not correctly setting the property description in generated Swagger spec. See #511 for more details.