I have solution when I need to inherit ComplexModelBinder like in this post: http://www.dotnet-programming.com/post/2017/03/17/Custom-Model-Binding-in-Aspnet-Core-2-Model-Binding-Interfaces.aspx
It is work fine until I add [FromBody] in the Controller's method.
As I can see in debug, it is seems that subcalls form model binders lost BindingSource, and I can note set it because its has only getter. Here is code:
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
//HERE: property.BindingSource IS NULL and can not be setted.
propertyBinders.Add(property, context.CreateBinder(property));
}
Can you share your code and your controller method/model ? It's hard for me to tell what's going on without all of the details.
Hi @topguss I am the author of the article you mentioned. The snippet of code you posted is adequate when you inherit from the ComplexTypeModelBinder, if you inherit from another model binder a diffrent code might be needed. To get an idea of how the provider should look like you may give a look to the source code of the provider of the model binder you want to inherit from.
Anyway, I don't understand why you need to change the property metadata? They are basically read-only you can't change them (they are cached, so if modify them you might experience hard-to-debug bugs).
Please tell me which model binder are you trying to inherit from. Moreover, consider that when you use [FromBody] you should trigger "formatters", and formatters doesnt call recursively the model binding process itself like in the ComplexTypeModelBinder. The only way to modify a formatter, basically is rewriting the whole model tree construction process and/or changing some parameters it reads (as it is the case for the Json formatter)
I suspect, that you are trying to inherit from the ComplexModelBinder to deserialize Json data. This is incorrect, since ComplexModelBinder is not involved in Json deserialization. Also inheriting from the right model binder would not solve since it is just a wrapper around Json.net deserializer. Thus , the right way is to write a Json.Net contract resolver that handles interfaces appropiately, and install it in asp.net core, .
Moreover, in this case you should prevent the model binder that inherits from ComplexModelBinder to be called in case of Json data: it is enough to install its provider just before the ComplexModelBinder instead that at the beginning of the providers list. This way it will not prevent the call of the Json formatter.
Well, I am inheriting from the ComplexModelBinder. Everything is the same as in the code above, except I need to deserialized JSON (and XML in the future) from a body of request.
I do not need to change property metadata, but when I watch it in DEBUG, I see that BindingSource is NULL, and in my orginal request, it is FromBody.
I found same solution on (which is similar to the post form the beginning)https://github.com/Serjster/IOCModelBinderExample/tree/ASPNETRTMVersion/src/IOCModelBinderExample/Infrastructure. I changed only model class name and add more porpeties. In the controller method, I added [FromBody].
When model passed to the controller method, every property has NULL vallue, e.g. https://github.com/Serjster/IOCModelBinderExample/blob/ASPNETRTMVersion/src/IOCModelBinderExample/Controllers/HomeController.cs. Without [FromBody], everytnig work fine.
In the model binder, I need to create model object, so I override CreateModel, like in the above example because I need to extract some data from JWT token and populate model with them. I can not do it in the controller, because that data are mandatory, and ModelState will be in INVALID state, if I do not populate them. So, solution is to do that in the model binder.
As I guess the ComplexModelBinder has a problem when it works with FromBody. Or maybe there is another ModelBinder which I can use? I can not find more docs about model binder...
The involved model binder is the BodyModelBinder. However, it just selects the right formatter (which in your case is the json formatter), and calls it. In turn the json formatter is just a wrapper around json.net.
Thus, acting on model binders in case of json data is nor recommended. You should write a new json formatter...
You don't need this! The way json formatter calls Json.net is customizable, so you should act this other way.
You may customize Json.net behaviour by passing customizion the MvcJsonOptions object:
services.Configure<MvcJsonOptions>(o => o.SerializerSettings.ContractResolver = new YourContractResolver())
You may change also other settings. However, the right place to find documentation is the Json.Net documentation.
I do not need to change anything in formatter. Only thing I want to do is to create Model class on proper way. My model class has constructor with params, where I need to inject some context information.
So, I need to override method:
protected override object CreateModel(ModelBindingContext bindingContext)
it is perfect method in may case, but I can find solution using ContractResolver.
@topguss ,
The path you have in mind is NOT POSSIBLE. When a model binder calls a formatter the formatter itself takes care of creating the model, so once and for all in case of Json data IT IS NOT POSSIBLE TO OVERRIDE model binder CreateModell.
In a custom Json.net contract resolver you may specify custom ways to deserialize some types. Namely, ContractResolver recognize a specific class or the fact that the current type is an interface and may call a custom deserializer for that type/set of types.
Below links of a custom contract resolver that handles subclasses and its associate custom deserializer:
You may abstract from that code above, or wait a couple of weeks, since within a couple of weeks I will write a further post on my blog that covers this subject since a lot of people asked me about using Dependency Injection in ModelBinders/Formatters
@topguss ,
As promised, I published a solution on my blog.
@frankabbruzzese I've read through all your examples and blog post, but I cannot get my custom ContractResolver to be called during deserialization. Specifically, I want to serialize and deserialize enum members using [Description] attributes.
Everything is working perfectly during serialization; my ContractResolver.ResolveContractConverter() creates my custom JsonConverter which then serializes the property using WriteJson().
I installed MyJsonContractResolver using:
services.AddMvc()
.AddJsonOptions(opt =>
{
opt.SerializerSettings.ContractResolver = new MyJsonContractResolver();
});
Here is the ContractResolver (I created the various overrides just so I could set breakpoints):
public class MyJsonContractResolver : CamelCasePropertyNamesContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
return base.ResolvePropertyName(propertyName);
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
return base.CreateObjectContract(objectType);
}
protected override JsonContract CreateContract(Type objectType)
{
return base.CreateContract(objectType);
}
public override JsonContract ResolveContract(Type type)
{
return base.ResolveContract(type);
}
protected override JsonConverter ResolveContractConverter(Type objectType)
{
return objectType.IsEnum ? new EnumDescriptionJsonConverter() : base.ResolveContractConverter(objectType);
}
protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
{
return base.CreateMemberValueProvider(member);
}
}
However none of the MyJsonContractResolver breakpoints (CreateObjectContract, CreateContract, ResolveContract) are hit during model binding. My controller action is simply hit without my custom deserialization for that property.
Is there some reason my custom ContractResolver works for serialization but not deserialization? I see that the MVC options also have InputFormatters and OutputFormatters, do I need to hook in to InputFormatters also?
Have you installed also the interfaces model binder provider from my other post? Maybe the problem is it intercepts all interfaces processing, thus preventing the formatter model binder from being called. You should move it to a more appropriate place in the list of all model binder providers (in my post I add it on top of the list), that is immediately before the ComplexTypeModelBinderProvider. Just loop through this list looking for it while counting its position, and then perform an insert in that position.
If you have not insyalled it, may be there is something "strange" in your project. Let me knos, I might send you the source code of my post so you may compare it with your project.
@frankabbruzzese no, I did not install any model binder, I am using only the default model binders and wanted to customize only how the JSON is deserialized. Based on your comments above, I thought that was the approach you were recommending.
I will look over the source code for BodyModelBinder, and try to figure out why it is not choosing my ContractResolver.
Oops, I misspoke, in my case it uses ComplexTypeModelBinder because I'm binding from Query String properties, not Body.
Then you may use the model binder described here that inherits from ComplexTypeModelBinder. However, if you want to use both approaches in the same project please do not place it on top of all binder providers, but just before the provider for the ComplexTypeModelBinder.
Okay, I'll try that I approach, but just so I understand, what is different about my scenario that requires a custom model binder? In your comment above, you say that inheriting ComplexTypeModelBinder is not the correct approach if I just want to change how the JSON is deserialized.
Nevermind, I think I understand now, in the case of Query parameters it is binding one single property at a time, as opposed to Body where it deserializes everything at once. It makes complete sense that the entire JSON deserialization is not even used in the case of Query parameters. Thanks for the great posts.
Custinizing Json contract resolver is the right approach when using json data in the request body, while inheriting from ComplexTypeModelBinderis the right approach whem using form or query string data.
Pls notice you may send json data also in the header. If you are using jQueryjson data may be transformed automatically into query string or form data, depending on the setting passed to the ajax call..
Closing as this seems to be answered
@rynowak can we reopen this issue, there are other reason for wanting to do something like this.
I'm looking to do something similar to the OP. Does anyone know how to create a new DefaultModelMetadata?
@frankabbruzzese I too was following along with you tutorial, which is great by the way.
I'm trying to make a version which works off of white-lists, but in order to do so I need to create a new DefaultModelMetadata for the corresponding concrete type to add the additional propertyBinders.
You can get ModelMetadata instances from the IModelMetadataProvider.
Please open a new issue if you more questions or feedback.
For anyone who needs to do this I have a solution on another opened Issue for this:
https://github.com/aspnet/Mvc/issues/6884