Skip to content

Commit

Permalink
Merge pull request #50 from marcominerva/develop
Browse files Browse the repository at this point in the history
Refactor Swagger configuration and update dependencies
  • Loading branch information
marcominerva authored Aug 1, 2024
2 parents 3493db8 + 06c4d78 commit f7b1102
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 316 deletions.
176 changes: 19 additions & 157 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,168 +162,30 @@ dotnet add package MinimalHelpers.OpenApi

### Usage

***Add OpenApi support for IFormFile and IFormFileCollection***
***Extension methods for OpenApi***

Minimal APIs don't generate the correct schema in **swagger.json** if we have an endpoint that accepts a [IFormFile](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.http.iformfile) or [IFormFileCollection](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.http.iformfilecollection) parameter and we're using the [WithOpenApi](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.builder.openapiendpointconventionbuilderextensions.withopenapi) extension method in .NET 7.0 or later. For example:
This library provides some extensions methods that simplify the OpenAPI configuration in Minimal API projects. For example, it is possible to customize the description of a response using its status code:

```csharp
app.MapPost("/api/upload", (IFormFile file) =>
{
return TypedResults.Ok(new { file.FileName, file.ContentType, file.Length });
})
.WithOpenApi();
```

This definition generates the following incorrect content in **swagger.json**:

```json
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "string",
"format": "binary"
}
}
},
"required": true
}
```

To solve this issue, just call the following extension method:

```csharp
builder.Services.AddSwaggerGen(options =>
{
// using MinimalHelpers.OpenApi;
options.AddFormFile();
});
```

And now the [IFormFile](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.http.iformfile) is correctly defined:

```json
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"required": [
"file"
],
"type": "object",
"properties": {
"file": {
"type": "string",
"format": "binary"
}
}
},
"encoding": {
"file": {
"style": "form"
}
}
}
}
}
```

***Add missing schema in swagger.json (.NET 7.0)***

Minimal APIs in .NET 7.0 don't generate the correct schema in **swagger.json** for certain file types, like `Guid`, `DateTime`, `DateOnly` and `TimeOnly` when using the [WithOpenApi](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.builder.openapiendpointconventionbuilderextensions.withopenapi) extension method on endpoints. For example, given the following endpoint:

```csharp
app.MapGet("/api/schemas",
(Guid id, DateTime dateTime, DateOnly date, TimeOnly time) => TypedResults.NoContent());
```

**swagger.json** will not contain **format** specification for these data types (whereas Controllers correctly set them):

```json
"parameters": [
{
"name": "id",
// ...
"schema": {
"type": "string"
}
},
{
"name": "dateTime",
// ...
"schema": {
"type": "string"
}
},
{
"name": "date",
// ...
"schema": {
"type": "string"
}
},
{
"name": "time",
// ...
"schema": {
"type": "string"
}
}
]
```

To solve these issues, just call the following extension method:

```csharp
builder.Services.AddSwaggerGen(options =>
{
// using MinimalHelpers.OpenApi;
options.AddMissingSchemas();
});
```

And you'll see that the correct **format** attribute has been specified for each parameter.

```json
"parameters": [
{
"name": "id",
// ...
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "dateTime",
// ...
"schema": {
"type": "string",
"format": "date-time"
}
},
{
"name": "date",
// ...
"schema": {
"type": "string",
"format": "date"
}
},
{
"name": "time",
// ...
"schema": {
"type": "string",
"format": "time"
}
}
]
```
endpoints.MapPost("login", LoginAsync)
.AllowAnonymous()
.WithValidation<LoginRequest>()
.Produces<LoginResponse>(StatusCodes.Status200OK)
.Produces<LoginResponse>(StatusCodes.Status206PartialContent)
.Produces(StatusCodes.Status403Forbidden)
.ProducesValidationProblem()
.WithOpenApi(operation =>
{
operation.Summary = "Performs the login of a user";

> **Note**
This workaround is no longer necessary in .NET 8.0 or higher, since it correctly sets in the **format** attribute in **swagger.json** for these data types.
operation.Response(StatusCodes.Status200OK).Description = "Login successful";
operation.Response(StatusCodes.Status206PartialContent).Description = "The user is logged in, but the password has expired and must be changed";
operation.Response(StatusCodes.Status400BadRequest).Description = "Incorrect username and/or password";
operation.Response(StatusCodes.Status403Forbidden).Description = "The user was blocked due to too many failed logins";

return operation;
});
```

**Contribute**

Expand Down
2 changes: 1 addition & 1 deletion samples/MinimalSample/MinimalSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
</ItemGroup>

<ItemGroup>
Expand Down
33 changes: 1 addition & 32 deletions samples/MinimalSample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
using MinimalHelpers.OpenApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerGen(options =>
{
options.AddFormFile();
// This is only needed with .NET 7.0.
//options.AddMissingSchemas();
});
builder.Services.AddSwaggerGen();

var app = builder.Build();

Expand All @@ -26,26 +17,4 @@
// Maps all the endpoints within this Assembly.
app.MapEndpoints();

app.MapPost("/api/upload", (IFormFile file) =>
{
return TypedResults.Ok(new { file.FileName, file.ContentType, file.Length });
})
.DisableAntiforgery()
.WithOpenApi(operation =>
{
operation.Description = "If you use the 'WithOpenApi' extension method, you need to call the 'AddFormFile' method on the 'SwaggerGenOptions' instance to be sure that swagger.json file contains the right definition.";
return operation;
});

app.MapPost("/api/multiupload", (IFormFileCollection files) =>
{
return TypedResults.Ok(new { FileCount = files.Count });
})
.DisableAntiforgery()
.WithOpenApi(operation =>
{
operation.Description = "If you use the 'WithOpenApi' extension method, you need to call the 'AddFormFile' method on the 'SwaggerGenOptions' instance to be sure that swagger.json file contains the right definition.";
return operation;
});

app.Run();
49 changes: 0 additions & 49 deletions src/MinimalHelpers.OpenApi/Filters/FormFileOperationFilter.cs

This file was deleted.

This file was deleted.

4 changes: 2 additions & 2 deletions src/MinimalHelpers.OpenApi/MinimalHelpers.OpenApi.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand Down Expand Up @@ -38,6 +38,6 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.7.0" />
</ItemGroup>
</Project>
Loading

0 comments on commit f7b1102

Please sign in to comment.