I have the following Model
public class MyModel
{
public string Name {get;set;}
public int? Age {get;set;}
public string City {get;set;}
public decimal? Salary {get;set;}
public JObject ExtraFields {get;set;}
}
I am trying to implement Custom Model Binder. If the submitted form has key
that matches with the Model's propery then set model's property value else add the key and value to ExtraFields
.
Note that ExtraFields
is JObject
public class MyModelBinder: IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
MyModel model = new MyModel()
{
ExtraFields = new JObject()
};
var form = bindingContext.HttpContext.Request.Form;
var properties = typeof(MyModel).GetProperties();
foreach (var key in form.Keys)
{
var p = properties.FirstOrDefault(x => x.Name == key);
var val = form[key];
if (p != null)
{
p.SetValue(model, val); // throws exception
}
else
{
var v = StringValues.IsNullOrEmpty(val) ? null : val.ToString();
model.ExtraFields.Add(key, v);
}
}
bindingContext.Model = model;
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Issue
I am getting exception while setting the value of the model
Object of type 'Microsoft.Extensions.Primitives.StringValues' cannot be converted to type 'System.String
If possible, i would like to avoid checking type of the model's target property and then convert value to target type. This should happen implicitly.
Basically, for all the matching keys invoke ASP.NET's default binder, and for all remaining keys add value to ExtraFields
Object of type 'Microsoft.Extensions.Primitives.StringValues' cannot be converted to type 'System.String
val
is of type Microsoft.Extensions.Primitives.StringValues which cannot directly assign it's value to model fields.
First,you can get the string form of the corresponding value directly through val.ToString()
.
Since the type of each field is different, you can create a custom Convert.ChangeType method to dynamically convert the type by passing val.ToString()
and p.PropertyType
.
More details, refer to this demo:
public class MyModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
MyModel model = new MyModel()
{
ExtraFields = new JObject()
};
var form = bindingContext.HttpContext.Request.Form;
var properties = typeof(MyModel).GetProperties();
foreach (var key in form.Keys)
{
var p = properties.FirstOrDefault(x => x.Name == key);
var val = form[key];
if (p != null)
{
// call custom method ChangeType and pass two parameters.
p.SetValue(model, ChangeType(val.ToString(),p.PropertyType));
}
else
{
var v = StringValues.IsNullOrEmpty(val) ? null : val.ToString();
model.ExtraFields.Add(key, v);
}
}
bindingContext.Model = model;
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
// custom method
public static object ChangeType(object value, Type conversion)
{
var t = conversion;
if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
if (value == null)
{
return null;
}
t = Nullable.GetUnderlyingType(t);
}
return Convert.ChangeType(value, t);
}
}
@LP13 provides a simpler solution:
TypeConverter typeConverter = TypeDescriptor.GetConverter(p.PropertyType);
object propValue = typeConverter.ConvertFromString(val); p.PropertyType));
p.SetValue(model, propValue);
Here is my test result: