Model:
public class TestModel
{
public Guid? Id1 { get; set; }
public Guid? Id2 { get; set; }
public DateTime? Dt1 { get; set; }
public int? Int1 { get; set; }
public bool? Bool1 { get; set; }
}
Json:
{
"Id1": "11111111111111111111111111111111",
"Id2": "22222222222222222222222222222222",
"Dt1": "2001-01-01 00:00:00",
"Int1": "5",
"Bool1": "a"
}
Controller:
public IActionResult Index([FromBody] TestModel model)
If one of properties has wrong "format", like Bool1 in above example, model binder will set entire model to null. The same situation when I try to send wrong Guid or DateTime, for example value "a" model is set to null.
I don't know which value was wrong if I have entire model null. I can't send back to user response about wrong field, I can only send back some generic error.
The solution is to use all properties as a type string and parse them in application but I would prefer to use model binder for correct binding.
I think model binder should create object and try to bind properties. If bind is impossible, should set null to property or default value. If property is object of some class, should create that object but only if in request data there is at least one property of it. In other case, property should be set to null.
MVC is not responsible for creating the TestModel instance. That's done within Json.NET. In this case, Json.NET throws an Exception when it hits a conversion error and does not return normally.
The ModelState should contain at least two errors, one for the Bool1 property conversion error and one for the overall input formatting problem. Are you seeing something else?
@p33nty , Json Data are handled by a formatter (based on Json.net), not by model binder. Main difference being formatters in general doesnt use a recursive procedure but handle data as a single object. Thus formatters either succeed globally or fail globally (in which case they return null). However, formatters get at least the error that caused the failure of the whole operation.
There is no general procedure to recover ill formatted Json...but true that single ill formatted properties might be recovered. The price for a similar behavior would be to re-write an asp.net Mvc specific Json deserializer ie re-inventing the wheel...
Anyway if you are using Json, probably you moved some processing logic to the client, so the best solution is relying on client side validation at least for simple format errors.
@dougbu @frankabbruzzese thanks for reply.
Let me explain my point of view. I'm thinking about MVC like consumer. I don't care about internal implementation, for me it is black-box. Of course I want to know how it works internally and understand why I get null as a model instead object, but still, for me it's a black-box. If it doesn't work as I want, I will perceive it as not useful as it could be.
What is interesting, MVC5 creates model object even if one of properties has wrong format, i.e. bool - "a". So, it wouldn't be re-inventing a wheel. Let's imagine that you have MVC5 app and you want to migrate to MVC Core. You will get nulls instead of objects. In a big application can be a problem.
In general, approach [1] when object is created and particular properties aren't binded allows to do the same what current approach [2] in MVC Core and more. For me it means, [1] is better, more flexible etc.
@p33nty ,
Im Mvc5 behavior differs between standard controllers and WebApi controllers. Standard controllers NEVER use formatters, so model binder takes care of Json, too, so they dont fail globally in case of ill formatted properties(the behavior you prefer). However, also in Mv5, WebApi controllers use a Json.net based formatter to parse Json, so they have the same behavior as Mvc Core.
Now, since in Mvc Core the two type of controllers were merged into one some features were taken from standard Mvc5 controllers and some others were takren froma WebApi controllers. Basically, for Json stuffs Mvc5 WebApi behavior was preferred, while for standard pos/get operations the standard controller behavior was preferred (not exactly this way...but basically this way...).
Using formatters, intstead of an hardwired model binder for Json has the disadvantage you pointed out, but it has also several advantages, mainly you may pass an MvcJsonOption object to customize the formatter behavior in several ways. For instance you may also serialize the original .Net type name in the Json object, or other metadata. You may also change the convention on property names (moving from pascal case to camel case), and so on.
So the point here is to balance advantages with disadvantage. For sure it would be great to have the advantages of both approaches, but this is basically impossible!
Now considering the subject from a _consumer point of view_. Why do you need a not null model in case of ill formatted properties? Error messages are not enough to drive interaction with the client? Why do you need a partially filled ViewModel?
Another question is? How do you handle the partially ill formed model on the client side, before sending it to the server? May be it is better to move format validation to the client side so you never have ill formed ViewModels on the client side, too.
If after having answered these questions, you think it is worth for you to spend some time to have this feature, you may customize Json.net de-serializer in such a way that, in case of format errors, instead of stopping it just put a default value into ill formatted property. Basically you need to customize behavior for numbers and datetime
@frankabbruzzese you are right in case of MVC5, I tested only on MVC controllers, I didn't test on api controllers.
Back to problem.
My assumptions for application:
And consequences of assumptions:
I want to have validation as a part of business logic or just before business logic starts. Thanks to that I can in easy way to introduce the new interface for my application. For example, I have MVC application and I want to publish API. I don't need to duplication validation in MVC controllers and in API controllers. In the new API controllers I just call the same service which performs validation and returns errors. I met with such situation, of course in that case we didn't have validation in one place and was problem.
I can't require from frontend to create model object because I don't trust frontend.
But back to source problem.
I see you point. The simplest and the most flexible solution for me (I'm not talking how to do that and who, just what to do!) is to add new setting to Json.NET - WrongFormatMemberHandling with to options:
Create - it creates object and maybe errors
Error - it behaves as now, returns null and errors
Of course, this time, I had to break my assumptions and add validation in controllers.
@p33nty ,
You might define Validation rules in Just One place and having validated both on the server side and on the client side: Mvc validation rules are defined just once, and apply on both parts. Client side validation should have the purpose of helping the user while server valid. So you might use somehow unobtrusive client validation on your inputs to prevent ill formatted propertie also on JavaScript, while protecting you server also with a validation procedure that returns nul for the whole model (also this way server coherence is protected!).
Finally, about your solution:
services.Configure<MvcJsonOptions>(o => o..........)Closing because there doesn't appear to be any further action required here.
When using model binding (which is the default), you will get partially-created models. When using formatters (e.g. with [FromBody]), you will not get partially-created models by default.
@Eilon Sorry to dig up an old thread Eilon but is it possible to get partially-created models when using FromBody? I can't seem to find anything on the topic.
@DinoSourcesRex as @Eilon said, you won't get partially-created models when using [FromBody]. If that's possible at all, it would require re-configuring Json.NET somehow.
/cc @JamesNK is there a way to get Json.NET to continue after hitting e.g. a formatting error for a single property?
FYI the JsonInputFormatter only sets the returned model when de-serialization was successful. But, when I've debugged this, the JsonSerializer returned null anyhow whenever the JsonSerializer.Error handler was invoked. Our handler captures the Exception and sets eventArgs.ErrorContext.Handled = true.
@dougbu He said you will not get partially-created models by default, hence my question. I appreciate the quick response!
I debugged it as well but I wasn't sure if there was another way to get the partially created class out.
e.g. a formatting error for a single property?
No. If JSON is invalid there is no automatic way that Json.NET could figure out what you actually meant and get back in the correct state.
@DinoSourcesRex given that, my only suggestion would be binding to an intermediate model that contains only string properties. Then, use realType.TryParse(...) to manually set the real model.
@dougbu It's all good, I'll just return a 500. I was just curious if there was a way around it or not as I knew you could get partials without using FromBody. Appreciated!