'Make JSON as a parameter in a multipart/form-data HTTP POST method so that both form and JSON is available for user input in swagger
I am rewriting an ASP.NET CORE API from 2.2 to 6.0, and found the HTTP POST methods in Swagger of the old API allow both form and body as parameter, such that either posting form parameters or the JSON body works. However I am not able to achieve the same behavior with ASP.NET CORE 6.0. I've tried to make two HTTP POST methods with same endpoint but different consumes, but Swagger complained that there are conflicting paths.
Here's the Swagger screenshot demonstrating what I want. How can this be achieved in ASP.NET CORE 6.0? Parameter name masked for privacy. Thanks in advance. Screenshot
Old Code:
program.cs:
public class Program {
public static void Main(string[] args) {
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
startup.cs:
public class Startup {
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) => Configuration = configuration;
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseServiceStack(new AppHost {
AppSettings = new NetCoreAppSettings(Configuration)
});
JsConfig<DateTime>.SerializeFn = datetime => datetime.ToString("yyyy-MM-dd hh:mm:ss");
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.Run(async (context) => {
await context.Response.WriteAsync("This is the API page.");
});
}
public xxxResponse POST(xxxRequest request) {
string sqlTerms = "select Terms from termstable where index = '" + request.index + "'";
string sqlDetails = "select aaa, bbb, ccc from detailstable where language = '" + Language + "' and index = '" + request.index.ToString() + "'";
var dbFactory = new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider);
using (var db = dbFactory.Open()) {
List<xxxResponse> p = db.Select<xxxResponse>(sql);
List<xxxDetails> q = db.Select<xxxDetails>(sql);
p[0].xxxDetails.Add(q[0]);
return p[0];
}
}
[Route("/vvv/API/www/xxx", "POST")]
public class xxxRequest : IReturn<xxxResponse> {
public int index { get; set; }
public string Language { get; set; }
}
public class xxxResponse {
public string Terms { get; set; }
public List<xxxDetail> xxxDetails { get; set; }
}
public class xxxDetails {
public string aaa { get; set; }
public string bbb { get; set; }
public int ccc { get; set; }
}
New Code:
program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
builder.Services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new GuidConverter()));
builder.Services.AddDbContext<PppDbContext>(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("PPPDB")));
builder.Services.AddDbContext<QqqDbContext>(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("QQQDB")));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
if (app.Environment.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.MapControllers();
app.Run();
xxxRequest.cs:
public class xxxRequest {
public int index{ get; set; }
public string? Language { get; set; }
}
xxxDetailsResponse.cs:
public class xxxDetailsResponse {
public string aaa { get; set; }
public string bbb { get; set; }
public int ccc{ get; set; }
}
xxxResponse.cs:
public class xxxResponse {
public string Terms { get; set; }
[NotMapped]
public xxxDetailsResponse[] xxxDetails { get; set; }
}
ApiController.cs:
[HttpPost("/vvv/API/www/xxx")]
public async Task<ActionResult<xxxResponse>> xxxMethod([FromBody] xxxRequest request) {
string sqlDetails =
"SELECT " +
"aaa, " +
"bbb, " +
"ccc " +
"FROM detailstable " +
"WHERE language = {0} AND index = {1}";
string sqlTerms =
"SELECT " +
"Terms " +
"FROM termstable " +
"WHERE index = {0}";
xxxResponse[] xxxResponses = await PppDbContext.xxxResponses.FromSqlRaw(sqlTerms, request.index).ToArrayAsync();
if (xxxResponses.Count() == 1) {
xxxResponses[0].xxxDetails = await PppDbContext.xxxDetailsResponses.FromSqlRaw(sqlDetail, request.Language, request.index).ToArrayAsync();
return xxxResponses[0];
} else {
throw new KeyNotFoundException("not Found");
}
}
Solution 1:[1]
I try this in .Net core 2.1 with Swagger 4.0.1 and it works like what you say, But In .Net 6, I am afriad you can't achieve this. An action can only accept one or the other, To get around this, you can simply create multiple actions, one with [FromBody] and one without. They'll of course need separate routes as well, since the presence of an attribute is not enough to distinguish overloads.
So you can refer to this:
[HttpPost("/Body")]
public IActionResult PostFromBody([FromBody] Request body)
{
//........
}
[HttpPost("/Form")]
public IActionResult PostFromForm([FromForm] Request form)
{
//.......
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Xinran Shen |

