.Net MVC3 Custom Model Binder - Initially Loading Model - c#

I am creating a custom model binder to initially load a model from the database before updating the model with incoming values. (Inheriting from DefaultModelBinder)
Which method do I need to override to do this?

You need to override the BindModel method of the DefaultModelBinder base class:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(YourType))
{
var instanceOfYourType = ...;
// load YourType from DB etc..
var newBindingContext = new ModelBindingContext
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instanceOfYourType, typeof(YourType)),
ModelState = bindingContext.ModelState,
FallbackToEmptyPrefix = bindingContext.FallbackToEmptyPrefix,
ModelName = bindingContext.FallbackToEmptyPrefix ? string.Empty : bindingContext.ModelName,
ValueProvider = bindingContext.ValueProvider,
};
if (base.OnModelUpdating(controllerContext, newBindingContext)) // start loading..
{
// bind all properties:
base.BindProperty(controllerContext, bindingContext, TypeDescriptor.GetProperties(typeof(YourType)).Find("Property1", false));
base.BindProperty(controllerContext, bindingContext, TypeDescriptor.GetProperties(typeof(YourType)).Find("Property2", false));
// trigger the validators:
base.OnModelUpdated(controllerContext, newBindingContext);
}
return instanceOfYourType;
}
throw new InvalidOperationException("Supports only YourType objects");
}

You'll want to override BindModel to do this.

Related

Custom JsonConverter not starting

I created a custom converter extending from JsonConverter, which would be used for an ASP.NET MVC and a Web API.
public class MyCustomConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
....
}
}
And I created this CustomObject that uses that converter:
[JsonConverter(typeof(MyCustomJsonConverter))]
public class CustomObject
{
...
}
This converter work correctly for second application (WebApi), that means method of ReadJson are running after calling it in TestOfUsingJson. And in this case I didn't have to set up anything.
For the first application (ASP.NET MVC) I have a trouble, object are converted from json, but this object are not created from my custom converter. Method of ReadJson are not running.
Method which who use custom converter are looks same on the every application
public HttpResponseMessage TestOfUsingJson([FromBody] CustomObject objs)
{
...
}
Some settings for Json Serializer in the ASP.NET MVC's Global.asax.cs:
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new MyCustomJsonConverter() }
};
What am I doing wrong?
I have prepared some solution that may be useful for someone
Create binding model
public class BindJson : IModelBinder
{
public BindJson()
{
}
public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
throw new SysException("Missing controller context");
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;
controllerContext.HttpContext.Request.InputStream.Position = 0;
using (var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream))
{
var data = reader.ReadToEnd();
if (!String.IsNullOrEmpty(bodyText))
{
return JsonConvert.DeserializeObject(data, bindingContext.ModelType);
}
}
return null;
}
}
Assign binder in Global.asax.cs
..
ModelBinders.Binders.Add(typeof(BindJson), new BindJson());
..
Assign binder into class
[ModelBinder(typeof(BindJson))]
public class CustomObject
{
...
}
Call binder in method
public HttpResponseMessage TestOfUsingJson(CustomObject objs)
{
...
}

OnPropertyValidating method for CustomModelBinder never gets called

Hi I am new to asp net mvc programming and I am wondering why the OnPropertyValidating method for my CustomModelBinder class is not being called.
Here is my declaration for the CUstomModelBinder.
public class TestModelBinder : DefaultModelBinder
{
protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
if (value is string && (controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)))
{
if (controllerContext.Controller.ValidateRequest && bindingContext.PropertyMetadata[propertyDescriptor.Name].RequestValidationEnabled)
{
int index;
if (IsDangerousString(value.ToString(), out index))
{
throw new HttpRequestValidationException("Dangerous Input Detected");
}
}
}
return base.OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, value);
}
}
and here is what I added to the Global.asax
ModelBinders.Binders.Add(typeof(TestModelBinder), new TestModelBinder());
ModelBinders.Binders.DefaultBinder = new TestModelBinder();
now I am assuming that this OnPropertyValidating method will get called everytime I call a controller action something like :
[HttpPost]
public JsonResult TestMethod(int param1, string param2, string param3)
{
...
}
but the OnPropertyValidating method on my customModelBinder never gets called.
Can anyone help me to understand why? Is there any good tutorial sites for this?
Thank in advance!
Instead of this:
ModelBinders.Binders.Add(typeof(TestModelBinder), new TestModelBinder());
Do this:
ModelBinders.Binders.Add(typeof(<your model>), new TestModelBinder());

How to bind complex properties within polymorphic model in Asp.Net MVC 4?

I need to create a dynamic input form based on a derived type but I cannot get complex properties bound properly when passed to the POST method of my controller. Other properties bind fine. Here is a contrived example of what I have:
Model
public abstract class ModelBase {}
public class ModelDerivedA : ModelBase
{
public string SomeProperty { get; set; }
public SomeType MySomeType{ get; set; }
public ModelDerivedA()
{
MySomeType = new SomeType();
}
}
public class SomeType
{
public string SomeTypeStringA { get; set; }
public string SomeTypeStringB { get; set; }
}
Custom Model Binder
The binder is based on this answer: polymorphic-model-binding
public class BaseViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
if (!typeof(ModelBase).IsAssignableFrom(type))
{
throw new InvalidOperationException("The model does not inherit from mode base");
}
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
Controller
[HttpPost]
public ActionResult GetDynamicForm([ModelBinder(typeof(BaseViewModelBinder))] ModelBase model)
{
// model HAS values for SomeProperty
// model has NO values for MySomeType
}
View Excerpt
#Html.Hidden("ModelType", Model.GetType())
#Html.Test(Model);
JavaScript
The form is posted using $.ajax using data: $(this).serialize(), which, if I debug shows the correct populated form data.
All properties are populated in the model excluding those of SomeType. What do I need to change to get them populated?
Thanks
Values are not being populated because you are creating new instance of type like following:
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
and returning the same model instead which is not correct.
do something like below.
ValueProviderResult valueResult;
bindingContext.ModelState.SetModelValue("ModelType", valueResult);
return valueResult;
Here is very good discussion on modelBinder.
http://odetocode.com/blogs/scott/archive/2009/05/05/iterating-on-an-asp-net-mvc-model-binder.aspx
I have solved my immediate issue by:
get an instance of FormvalueProvider (to get access to what has been posted)
recursively going through my model and setting each property value to the matching value in the FormValueProvider
private FormValueProvider vp;
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
if (!typeof(ModelBase).IsAssignableFrom(type))
{
throw new InvalidOperationException("Bad Type");
}
var model = Activator.CreateInstance(type);
vp = new FormValueProvider(controllerContext);
bindingContext.ValueProvider = vp;
SetModelPropertValues(model);
return model;
}
And the recursion, based on this answer here to print properties in nested objects
private void SetModelPropertValues(object obj)
{
Type objType = obj.GetType();
PropertyInfo[] properties = objType.GetProperties();
foreach (PropertyInfo property in properties)
{
object propValue = property.GetValue(obj, null);
var elems = propValue as IList;
if (elems != null)
{
foreach (var item in elems)
{
this.SetModelPropertValues(item);
}
}
else
{
if (property.PropertyType.Assembly == objType.Assembly)
{
this.SetModelPropertValues(propValue);
}
else
{
property.SetValue(obj, this.vp.GetValue(property.Name).AttemptedValue, null);
}
}
}
}
Anyone using this may need to make it more robust for their needs.
I would be very intersted to hear of any drawbacks to this as a general approach to this kind of problem.
However, I'm hoping that this post helps in some situations.
Try to add default constructor to your ModelDerivedA to initialize MySomeType
public class ModelDerivedA : ModelBase
{
public ModelDerivedA()
{
MySomeType = new SomeType();
}
}

Is it possible to get Dictionary from query string?

My controller method looks like this:
public ActionResult SomeMethod(Dictionary<int, string> model)
{
}
Is it possible to call this method and populate "model" using only query string? I mean, typing something like this:
ControllerName/SomeMethod?model.0=someText&model.1=someOtherText
in our browser address bar. Is it possible?
EDIT:
It would appear my question was misunderstood - I want to bind the query string, so that the Dictionary method parameter is populated automatically. In other words - I don't want to manually create the dictionary inside my method, but have some automathic .NET binder do it form me, so I can access it right away like this:
public ActionResult SomeMethod(Dictionary<int, string> model)
{
var a = model[SomeKey];
}
Is there an automatic binder, smart enough to do this?
In ASP.NET Core, you can use the following syntax (without needing a custom binder):
?dictionaryVariableName[KEY]=VALUE
Assuming you had this as your method:
public ActionResult SomeMethod([FromQuery] Dictionary<int, string> model)
And then called the following URL:
?model[0]=firstString&model[1]=secondString
Your dictionary would then be automatically populated. With values:
(0, "firstString")
(1, "secondString")
For .NET Core 2.1, you can do this very easily.
public class SomeController : ControllerBase
{
public IActionResult Method([FromQuery]IDictionary<int, string> query)
{
// Do something
}
}
And the url
/Some/Method?1=value1&2=value2&3=value3
It will bind that to the dictionary. You don't even have to use the parameter name query.
try custom model binder
public class QueryStringToDictionaryBinder: IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var collection = controllerContext.HttpContext.Request.QueryString;
var modelKeys =
collection.AllKeys.Where(
m => m.StartsWith(bindingContext.ModelName));
var dictionary = new Dictionary<int, string>();
foreach (string key in modelKeys)
{
var splits = key.Split(new[]{'.'}, StringSplitOptions.RemoveEmptyEntries);
int nummericKey = -1;
if(splits.Count() > 1)
{
var tempKey = splits[1];
if(int.TryParse(tempKey, out nummericKey))
{
dictionary.Add(nummericKey, collection[key]);
}
}
}
return dictionary;
}
}
in controller action use it on model
public ActionResult SomeMethod(
[ModelBinder(typeof(QueryStringToDictionaryBinder))]
Dictionary<int, string> model)
{
//return Content("Test");
}
More specific to mvc model binding is to construct the query string as
/somemethod?model[0].Key=1&model[0].Value=One&model[1].Key=2&model[1].Value=Two
Custom Binder would simply follow DefaultModelBinder
public class QueryStringToDictionary<TKey, TValue> : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var modelBindingContext = new ModelBindingContext
{
ModelName = bindingContext.ModelName,
ModelMetadata = new ModelMetadata(new EmptyModelMetadataProvider(), null,
null, typeof(Dictionary<TKey, TValue>), bindingContext.ModelName),
ValueProvider = new QueryStringValueProvider(controllerContext)
};
var temp = new DefaultModelBinder().BindModel(controllerContext, modelBindingContext);
return temp;
}
}
Apply custom model binder in model as
public ActionResult SomeMethod(
[ModelBinder(typeof(QueryStringToDictionary<int, string>))] Dictionary<int, string> model)
{
// var a = model[SomeKey];
return Content("Test");
}

Call Default Model Binder from a Custom Model Binder?

I have written a Custom Model Binder which is supposed to map Dates, coming from URL-Strings (GET) according to the current culture (a sidenote here: the default model binder does not consider the current culture if you use GET as http-call...).
public class DateTimeModelBinder : IModelBinder
{
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext.HttpContext.Request.HttpMethod == "GET")
{
string theDate = controllerContext.HttpContext.Request.Form[bindingContext.ModelName];
DateTime dt = new DateTime();
bool success = DateTime.TryParse(theDate, System.Globalization.CultureInfo.CurrentUICulture, System.Globalization.DateTimeStyles.None, out dt);
if (success)
{
return dt;
}
else
{
return null;
}
}
return null; // Oooops...
}
#endregion
}
I registered the model binder in global.asax:
ModelBinders.Binders.Add(typeof(DateTime?), new DateTimeModelBinder());
Now the problem occurs in the last return null;. If I use other forms with POST, it would overwrite the already mapped values with null. How can I avoid this?
Thx for any inputs.
sl3dg3
Derive from DefaultModelBinder and then invoke the base method:
public class DateTimeModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// ... Your code here
return base.BindModel(controllerContext, bindingContext);
}
}
Well, it is actually a trivial solution: I create a new instance of the default binder and pass the task to him:
public class DateTimeModelBinder : IModelBinder
{
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext.HttpContext.Request.HttpMethod == "GET")
{
string theDate = controllerContext.HttpContext.Request.Form[bindingContext.ModelName];
DateTime dt = new DateTime();
bool success = DateTime.TryParse(theDate, System.Globalization.CultureInfo.CurrentUICulture, System.Globalization.DateTimeStyles.None, out dt);
if (success)
{
return dt;
}
else
{
return null;
}
}
DefaultModelBinder binder = new DefaultModelBinder();
return binder.BindModel(controllerContext, bindingContext);
}
#endregion
}
One more possible solution is pass some of the best default model bidners into custom and call it there.
public class BaseApiRequestModelBinder : IModelBinder
{
private readonly IModelBinder _modelBinder;
public BaseApiRequestModelBinder(IModelBinder modelBinder)
{
_modelBinder = modelBinder;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
//calling best default model binder
await _modelBinder.BindModelAsync(bindingContext);
var model = bindingContext.Result.Model as BaseApiRequestModel;
//do anything you want with a model that was bind with default binder
}
}
public class BaseApiRequestModelBinderProvider : IModelBinderProvider
{
private IList<IModelBinderProvider> _modelBinderProviders { get; }
public BaseApiRequestModelBinderProvider(IList<IModelBinderProvider> modelBinderProviders)
{
_modelBinderProviders = modelBinderProviders;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(BaseApiRequestModel) || context.Metadata.ModelType.IsSubclassOf(typeof(BaseApiRequestModel)))
{
//Selecting best default model binder. Don't forget to exlude the current one as it is also in list
var defaultBinder = _modelBinderProviders
.Where(x => x.GetType() != this.GetType())
.Select(x => x.GetBinder(context)).FirstOrDefault(x => x != null);
if (defaultBinder != null)
{
return new BaseApiRequestModelBinder(defaultBinder);
}
}
return null;
}
//Register model binder provider in ConfigureServices in startup
services
.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new BaseApiRequestModelBinderProvider(options.ModelBinderProviders));
})

Categories

Resources