Swashbuckle.AspNetCore icon indicating copy to clipboard operation
Swashbuckle.AspNetCore copied to clipboard

[FromForm] and lists are not serialized correctly?

Open sander1095 opened this issue 5 years ago • 15 comments

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""

sander1095 avatar Apr 01 '19 10:04 sander1095

hi it's related to https://github.com/swagger-api/swagger-ui/issues/4146

bhugot avatar Apr 14 '19 10:04 bhugot

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.

domaindrivendev avatar Aug 11 '19 23:08 domaindrivendev

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.)

jnpwly avatar Nov 26 '19 04:11 jnpwly

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.

skini82 avatar Jul 02 '20 16:07 skini82

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.

domaindrivendev avatar Jan 18 '21 22:01 domaindrivendev

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.

sander1095 avatar Jan 19 '21 12:01 sander1095

What's the status on this bug?

ClassyCircuit avatar Mar 10 '21 14:03 ClassyCircuit

Need fix for this bug

bannarisoftwares avatar Sep 06 '21 16:09 bannarisoftwares

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());
	}
}

PatrickGhosn avatar Jun 23 '22 10:06 PatrickGhosn

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 ?

Mwaseemzakir avatar Apr 07 '24 23:04 Mwaseemzakir

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 avatar Apr 08 '24 08:04 sander1095

@sander1095 unfortunately, NSwag also have same issue

Mwaseemzakir avatar Apr 16 '24 10:04 Mwaseemzakir

This project has basically been abandoned; the last commit was January 2023.

See #2778.

A PR to fix this issue would be welcome.

martincostello avatar Apr 16 '24 10:04 martincostello

@everyone Is there any workaround for this issue?

nishila2 avatar Sep 02 '24 04:09 nishila2

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; }
	}
}

image

jgarciadelanoceda avatar Sep 03 '24 07:09 jgarciadelanoceda