A light-weight REST API development framework for ASP.NET 6 and newer.
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
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
{
...
}
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"));
});
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);
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."
}
]
}
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> { ... }
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
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.
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.
Previous way:
Summary(s =>
{
s.RequestExamples.Add(new MyRequest {...});
});
New way:
s.RequestExamples.Add(new(new MyRequest { ... })); // wrapped in a RequestExample 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.
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;
});
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
app.UseFastEndpoints(c => c.Errors.ContentType = "application/json")
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.
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!"
};
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();
}
}
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.
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();
}
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.
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]
.
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
.
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.
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.
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
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");
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.
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
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.
The type constraint on the Validator<TRequest>
class has been relaxed to notnull
so that struct type DTOs can be validated.
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.
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);
});
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.
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();
}
}
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.
A NRE was being thrown when the swagger security operation processor was encountering minimal api endpoints with auth requirements.
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
Please see the documentation for more information.
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".
Please see the documentation for more information.
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.
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);
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.
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.
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.
The type discovery source generator will now correctly detect partial classes of targets and only create a single entry. #574
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"
}
}
]
RuleFor(r => r.Name)
.NotEmpty()
.MinimumLength(10); // this was not being picked up before
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.
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.
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
}
}
The builder.Services.UseAntiForgery()
extension method has been renamed to .UseAntiforgeryFE()
in order to avoid confusion.
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
Configure()
method #569FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
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.
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)
{
...
}
}
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.
sealed class MyRequest
{
[HideFromDocs]
public int Internal { get; set; } //this will not appear in swagger schema
public string Name { get; set; }
}
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.
Only the HeaderApiVersionReader
was previously supported. Support for doing versioning based on URL segments using the Asp.Versioning.Http
package is now working
correctly.
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.
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.
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.
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.
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
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.
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.
Post-Processors can now handle uncaught exceptions as an alternative to an exception handling middleware. Please see the documentation page for details.
Global Pre/Post Processors now have shared state support with the following two newly added abstract types:
The actual error reason can now be hidden from the client by configuring the exception handler middleware like so:
app.UseDefaultExceptionHandler(useGenericReason: true);
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.
JSON error responses didn't correctly render the deeply nested property chain/paths when the following conditions were met:
[FromBody]
attributeThis has been fixed to correctly render the property chain of the actual item that caused the validation failure.
More info here.
The ProblemDetails
DTO properties had private setter properties preventing STJ from being able to deserialize the JSON which has now been corrected.
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)
{
...
}
}
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.
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.
FastEndpoints needs sponsorship to sustain the project. Please help out if you can.
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 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 {...});
});
Please see the documentation page for details of this feature.
Thank you Wàn Yǎhǔ for the contribution.
//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.
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.
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
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.