We use the aspnetcore 2.
We have configured JsonOptions in startup like this.
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
options.SerializerSettings.Converters = new List<JsonConverter> {
new JsonDocumentConverter(),
new JsonDocumentMetaConverter(),
//new Bll.FileRecords.JsonDocumentMetaListConverter(),
};
});
public class JsonDocumentConverter : CustomCreationConverter<IDocument>
{
public override IDocument Create(Type objectType)
{
return new LocalDocument();
}
}
public class JsonDocumentMetaConverter : CustomCreationConverter<IDocumentMeta>
{
public override IDocumentMeta Create(Type objectType)
{
return new LocalDocumentMeta();
}
}
In our controller we have 2 methods
[HttpPost("PostUrlencoded")]
public async Task<IActionResult> PostUrlencoded([FromBody]IDocument document)
{ /*...*/ }
2.. The second one is called Post螠ultipart in which we make a http post request with content-type : multipart/form-data.
[HttpPost("Post螠ultipart")]
public async Task<IActionResult> Post螠ultipart([FromForm]IDocument document)
{ /*...*/ }
The first method works fine, and the deserialization works fine. In the second method deserialization fails.
The error is:
Model bound complex types must not be abstract or value types and must have a parameterless constructor.
That's because the JsonOptions isn't applied.
When we remove the jsonOptions from the startup.cs, we get the same error in both cases.
If you use the Fromform attribute, the model's binding is not using JSON.NET.
Why is this happening?
The http request is sent like this
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=document
{...json..}
Shouldn't this be deserialized?
@mbonatsos , can you please share a sample project with us to reproduce the issue?
https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding#customize-model-binding-behavior-with-attributes
[FromHeader]
, [FromQuery]
, [FromRoute]
, [FromForm]
: Use these to specify the exact binding source you want to apply.
[FromBody]
: Use the configured formatters to bind data from the request body. The formatter is selected based on content type of the request.
I've created a sample project for you to see the issues and the workarounds.
In my opinion the modelbinder solution is not clean.
The mvc framework should deserialize a model from some data that is posted (http), regardless what the post request type is "multipart/form-data" or "application/x-www-form-urlencoded".
@dougbu , can you please investigate this?
The JSON input formatter accepts application/json
, text/json
and application/*+json
. It does not support any multipart content types by default.
To support multipart and input formatters together, need to position the Body
stream after the boundary for the appropriate section. See https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.FileUpload/Controllers/FileController.cs for example. (This isn't simple. MVC helps most when combining files and application/x-www-form-urlencoded
sections into a multipart/form-data
submission. That is the normal use of multipart/form-data
.)
As a smaller detail, also need to update the JsonInputFormatter
because it will check the overall content type, not that of the "current" section. E.g. in your ConfigureServices(IServiceCollection services)
:
c#
services.AddMvc(options =>
{
var jsonInputFormatter = options.InputFormatters.OfType<JsonInputFormatter>().First();
jsonInputFormatter.SupportedMediaTypes.Add("multipart/form-data");
};
Clearing the investigate
label.
I probably misunderstood the concern, focusing on using input formatters with multipart requests.
The sample contains a number of issues:
[FromBody]
and [FromForm]
are completely separate. E.g. nothing in MvcJsonOptions
is used when binding [FromForm]
parameters.[FromForm]
parameters must contain an application/form-url-encoded
body or a multipart/form-data
body with an application/x-www-form-urlencoded
sectoin. The tests sending application/json
data to actions with [FromForm]
parameters led to my earlier confusion.[FromForm] Document document
parameters should work fine given a valid request (see above) and the default configuration.[FromForm] IDocument document
parameters requires an IModelBindingProvider
implementation that gets the metadata for Document
and returns the associated binder. Should not need a specific binder (just use ComplexTypeModelBinder
) e.g.c#
if (context.Metadata.ModelType == typeof(IDocument))
{
var metadata = context.MetadataProvider.GetMetadataForType(typeof(Document));
return context.CreateBinder(metadata);
}
IModelBindingProvider
implementation last, certainly after the BodyModelBindingProvider
. Otherwise, the ComplexTypeModelBinder
will attempt to bind when the request contains application/json
data -- and fail.IFormFile
action parameters should not have an associated [FromForm]
attribute.I believe and would expect the mvc framework to deserialize this http request, with out any other configuration.
I would also expect [FromForm] working in the same way with [FromBody] for the deserialization of posted data. I mean that if i post this
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=document
{...json..}
MvcJsonOptions should be used/respected. Is confusing not to work there.
I also think that the implementation with IModelBindingProvider is not so clean.
I would like to see some more opinions from other team members.
Thank you for your feedback.
We're closing this issue as the behavior discussed seems to be by design.
I am not conviced that the behavior should be this.
I coundm't understand the arguments behind this decision.
I always hear from the members of the asp.net team in confernces and videos that the framework is openly developed, not only open source.
I ask for more info about this and you close the issue not fair.
I was hoping this would have worked with either [FromForm] or automatically.
Is there any reason why it doesn't?
I can send multiple IFormFile in a similar way and they get picked up without problems, why doesn't this?
Content-Disposition: form-data; name="news"
Content-Type: application/json
{"title":"title1","text":"text1"}
public async Task<IActionResult> PostNews([FromForm]News news, IFormFile attachment = null, IFormFile image = null)
{
...
}
I guess I can just do something like this, but this feels REALLY clunky.
public async Task<IActionResult> PostNews(IFormFile news, IFormFile attachment = null, IFormFile image = null)
{
if (news != null && news.ContentType == "application/json")
{
News newsObject = null;
var js = new JsonSerializer();
using (var ms = new MemoryStream())
using (var sr = new StreamReader(ms))
using (var jtr = new JsonTextReader(sr))
{
await news.CopyToAsync(ms);
ms.Seek(0, SeekOrigin.Begin);
newsObject = js.Deserialize<News>(jtr);
}
}
}
I knew someone else would say something about this. I expect more reactions.
It was pretty authoritarian the way this was closed.
Reopening this issue as there seems to be some customer interest in this being a first-class experience.
Will track this as part of the 3.0 milestone.
I guess what we are looking for is something similar to this https://stackoverflow.com/questions/41367602/upload-files-and-json-in-asp-net-core-web-api
Where we want the parameter in [FromBody] to be parsed using a jsonbinding/serialization.
Was refactoring some code, and found my old JsonModelBinder class. Scratched my head on why I would need that, after all it's {currentYear}. Figured I could delete it...
I was wrong.
We could really use this.
For everyone else still waiting, I've switched to Bruno's Nuget: https://github.com/BrunoZell/JsonModelBinder
@pranavkm Did you have a look at this? I'm wondering what it would take to get this scenario supported out-of-the-box and whether it would be a good PR candidate?
I haven't, but we wanted to look at improvements in the multipart area. I'll bring this up in our planning, we should at the very least get a design out for this that we'd accept a PR for.
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
Whelp, I just wasted an afternoon by assuming this was possible.
Requests matching actions with [FromForm] parameters must contain an application/form-url-encoded body or a multipart/form-data body with an application/x-www-form-urlencoded sectoin.
I have an application/x-www-form-urlencoded section in my request, but the model binder still does not work for me. This is with standard form content (no json).
Whelp, I just wasted an afternoon by assuming this was possible.
Requests matching actions with [FromForm] parameters must contain an application/form-url-encoded body or a multipart/form-data body with an application/x-www-form-urlencoded sectoin.
I have an application/x-www-form-urlencoded section in my request, but the model binder still does not work for me. This is with standard form content (no json).
Ditto - would be great to finally make json + IFormFile multipart requests a first class citizen
Yup it's on our radar. We'd hoped to address it in 5.0 but unfortunately weren't able to fit it.
Thanks for contacting us.
We're moving this issue to the Next sprint planning
milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
Most helpful comment
Reopening this issue as there seems to be some customer interest in this being a first-class experience.
Will track this as part of the 3.0 milestone.