Swashbuckle.AspNetCore
Swashbuckle.AspNetCore copied to clipboard
[FromForm] and lists are not serialized correctly?
Please see this stackoverflow post: https://stackoverflow.com/questions/55452461/a-list-of-guids-is-empty-when-passed-into-a-model-which-is-used-by-fromform-in
A short summary:
When using the default swagger configuration with a newly created ASP.NET Core 2.2 project and the following code, a [FromForm]
model that contains a list will not be filled correctly.
A list of strings will contain 1 value that contains all values passed, but seperated by a comma.
I expect these things to be turned into a list containing x values?
Controller:
[HttpPost("bug")]
public ActionResult<string> Bug([FromForm] BugModel bugModel)
{
var message = GetMessage(bugModel);
return Ok(message);
}
Model:
public class BugModel
{
/// <summary>
/// If you send a GUID it will not appear in this list
/// </summary>
public IEnumerable<Guid> Ids { get; set; }
/// <summary>
/// If you send 3 strings, the list will contain 1 entry with the 3 string comma separated.
/// </summary>
public IEnumerable<string> IdsAsStringList { get; set; }
}
Swagger will, with some input values, generate the following CURL statement:
curl -X POST "https://localhost:5001/api/Bug/bug" -H "accept: text/plain" -H "Content-Type: multipart/form-data" -F "Ids="9dfb212a-a215-4991-9452-3ddf90e21ec0","9dfb212a-a215-4991-9452-3ddf90e21ec0"" -F "IdsAsStringList="9dfb212a-a215-4991-9452-3ddf90e21ec0","9dfb212a-a215-4991-9452-3ddf90e21ec0""
But this is wrong, at least for a default generated ASP.NET Core 2.2 project.
The following CURL statement, which has duplicate Ids
fields, DOES work:
curl -X POST "https://localhost:5001/api/Bug/bug" -H "accept: text/plain" -H "Content-Type: multipart/form-data" -F "Ids="9dfb212a-a215-4991-9452-3ddf90e21ec0"" -F "Ids="9dfb212a-a215-4991-9452-3ddf90e21ec0"" -F "IdsAsStringList="9dfb212a-a215-4991-9452-3ddf90e21ec0","9dfb212a-a215-4991-9452-3ddf90e21ec0""
hi it's related to https://github.com/swagger-api/swagger-ui/issues/4146
Actually - I think it's related to this one https://github.com/swagger-api/swagger-ui/issues/4836. Or more specifically, it's because the swagger-ui does not "have support for encoding
yet".
For [FromForm]
, Swashbuckle will add the encoding
property with style: "form"
for each property in the generated schema, and because a style of form
implies "explode": true
(see OA3 snippet below), this should ultimately indicate that array values are submitted as separate parameters.
encoding.explode
=> When this is true, property values of type array or object generate separate parameters for each value of the array, or key-value-pair of the map. For other types of properties this property has no effect. When style is form, the default value is true. For all other styles, the default value is false. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded.
So, I think this is a swagger-ui and not a SB issue. I'm happy to keep it open for tracking purposes but will move it from the 5.0.0 milestone.
Would it be possible for Swashbuckle to be able to set other properties of the encoding
object, based on something in the C# code? Specifically, I would like to be able to "manually" set the encoding.{property}.encodingType
to be application/json
, and then (assuming this is possible) for the swagger-ui to render an array of strings (the tags
property) as:
------WebKitFormBoundaryKQWA8MEJTEXgMvsj
Content-Disposition: form-data; name="metadata"
{
"additionalProp1": {},
"additionalProp2": {},
"additionalProp3": {}
}
------WebKitFormBoundaryKQWA8MEJTEXgMvsj
Content-Disposition: form-data; name="tags"
Content-type: application\json
["string1","string2",",","string4\""]
------WebKitFormBoundaryKQWA8MEJTEXgMvsj--
and not:
------WebKitFormBoundaryKQWA8MEJTEXgMvsj
Content-Disposition: form-data; name="metadata"
{
"additionalProp1": {},
"additionalProp2": {},
"additionalProp3": {}
}
------WebKitFormBoundaryKQWA8MEJTEXgMvsj
Content-Disposition: form-data; name="tags"
string1,string2,,string4"
------WebKitFormBoundaryKQWA8MEJTEXgMvsj--
It's a little hard for me to work out whether this is a bug in:
- the OpenAPI spec,
- the swagger-ui code, or
- the Swashbuckle code.
If there is anything I can do to help push this along, I'm more than willing to give it a try. I'd need some direction, but ¯\_(ツ)_/¯
(Edit See also https://stackoverflow.com/q/59043510/837469.)
Hello, I have something similar bug. With a derived property of same name as base class (eg. public new List<MyObject> MyObjectCollection {get; set;} ), i have an AmbiguousMatchException if using [FromForm] on my api call. Everything goes well with [FromBody].
public class MyClass : BaseClasses.MyClass { public new List<MyObject> MyObject { get; set; } //this causes AmbiguousMatchException with FromBody property public MyClass () : base() { } }
On API Controller:
[HttpPost("")] [Authorize] public async Task<IActionResult> Post([FromForm] MyClass newElement) { ... }
I solved this creating a new list with different name (but i really want avoid this).
public new List<MyObject> MyObject_derived{ get; set; }
Hope this helps. Thanks. Dario.
I believe this might be resolved in the latest version (5.6.3
at time of writing) due to swagger-ui upgrades. @sander1095 could you please confirm? Actually - better still could you test with the lastest preview build from myget.org as well.
Hello.
Back then I created a test project for this issue: https://github.com/sander1095/FromFormGuidListBug.
This uses .net core 2.2
and SwashBuckle.AspNetCore 4.0.1
.
I have installed 5.6.3
AND 6.0.0-preview-1523
but in both cases the bug still persists.
Even after upgrading to .net 5.0
the bug still persists.
What's the status on this bug?
Need fix for this bug
We've just encountered this issue. We had to do a workaround like below, but of course this is not usable as every API generation will override this.
if (ids) {
// localVarFormParams.append('ids', ids.join(COLLECTION_FORMATS.csv));
for (let index = 0; index < ids.length; index++) {
localVarFormParams.append(`ids[${index}]`, ids[index].toString());
}
}
Facing same issue where my model contains multiple list of objects but with FromForm those are not being read correctly. This issue is 3 years old, is it not got fixed yet ?
This project has basically been abandoned; the last commit was January 2023.
.NET 9 will no longer ship Swashbuckle.AspNetCore by default because of this.
That is why I would recommend migrating to NSwag. Shouldn't be too much work and works better than Swashbuckle.
Finally, please be nice. This bug might not have been fixed for 5 years, but you're getting this software for free! You're not entitled to fixes; you could fork this project, fix the bug and maintain it yourself. If you're not willing to do so, it's good manners to refrain from demanding fixes for problems you don't want to fix yourself. You could ask your company to support projects like this financially so they might not get abandoned. You could even ask if you can fix these bugs during work hours.
@sander1095 unfortunately, NSwag also have same issue
This project has basically been abandoned; the last commit was January 2023.
See #2778.
A PR to fix this issue would be welcome.
@everyone Is there any workaround for this issue?
Hi, I have just tested with the code provided and it seems to work in dotnet8 and the latest version of Swashbuckle:
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.SwaggerGen;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.IncludeXmlComments(Assembly.GetExecutingAssembly(), true);
c.UseInlineDefinitionsForEnums();
c.EnableAnnotations();
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Test API", Version = "1" });
});
//builder.Services.AddSwaggerGenNewtonsoftSupport();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
public class TestController : ControllerBase
{
[HttpPost("bug")]
public ActionResult<string> Bug([FromForm] BugModel bugModel)
{
var amountOfGuids = bugModel?.Ids?.Count();
var guidsText = string.Join(", ", bugModel?.Ids) ?? "EMPTY";
var amountOfGuidsAsString = bugModel?.IdsAsStringList?.Count();
var guidsAsStringText = string.Join(", ", bugModel?.IdsAsStringList) ?? "EMPTY";
string guidMessage = $"You have supplied a total of {amountOfGuids} GUID(s). {Environment.NewLine}" +
$"The values are as follows: {guidsText}";
string guidsAsStringMessage = $"You have supplied a total of {amountOfGuidsAsString} GUID(s) as strings. {Environment.NewLine}" +
$"The values are as follows: {guidsAsStringText}";
var message = guidMessage +
Environment.NewLine +
Environment.NewLine +
guidsAsStringMessage;
return Ok(message);
}
public class BugModel
{
/// <summary>
/// If you send a GUID it will not appear in this list
/// </summary>
public IEnumerable<Guid> Ids { get; set; }
/// <summary>
/// If you send 3 strings, the list will contain 1 entry with the 3 string comma separated.
/// </summary>
public IEnumerable<string> IdsAsStringList { get; set; }
}
}