OnPropertyValidating method for CustomModelBinder never gets called - c#

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());

Related

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();
}
}

Custom MVC model binding on a type with no public properties

I have created strongly typed ID classes in my project as there is currently confusion with interchangeable string ID's which is causing bugs which are easy to miss.
I have changed all the string id parameters in my action methods to the new strongly typed to realise that the MVC model binder can not now bind the strings to the new type (despite implicit string conversion operators existing for this type).
e.g.
public ActionResult Index(JobId jobId)
{
//...
}
I have read around about creating custom model binders, but all the tutorials are about binding a POCO class when we know the names of the query parameters / form values.
I just want to be able to tell the framework 'if parameter is strongly typed id type, instantiate using this constructor', so it will always work no matter what the name of the parameter is.
Can anyone point me in the right direction?
EDIT
This is the base class that the strongly typed ID's inherit from:
public class StronglyTypedId
{
private readonly string _id;
public StronglyTypedId(string id)
{
_id = id;
}
public static bool operator ==(StronglyTypedId a, StronglyTypedId b)
{
if (ReferenceEquals(a, b))
{
return true;
}
if (((object)a != null) && ((object)b == null) || ((object)a == null))
{
return false;
}
return a._id == b._id;
}
public static bool operator !=(StronglyTypedId a, StronglyTypedId b)
{
return !(a == b);
}
public override string ToString()
{
return _id;
}
public override bool Equals(object obj)
{
if (!(obj is StronglyTypedId))
{
return false;
}
return ((StronglyTypedId)obj)._id == _id;
}
public Guid ToGuid()
{
return Guid.Parse(_id);
}
public bool HasValue()
{
return !string.IsNullOrEmpty(_id);
}
}
I figured out a way to do this just now using a custom model binder. This way will work no matter what the name of the parameter that needs to be bound:
public class JobIdModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(JobId))
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
string id = value.AttemptedValue;
return new JobId(id);
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
So it's pretty much the same as if you are implementing a custom binder for a specific model type, except this is the bit of magic var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName) which means it will work regardless of naming of parameters.
Then you simply register the binder in your Global.cs like so:
ModelBinders.Binders.Add(typeof(JobId), new JobIdModelBinder());

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");
}

ASP.NET MVC (3) Binding POST value to 'object' type makes the object a System.String[], why?

So, I have a search form in my website which posts Search.Value as a parameter where in the project there is a class called Search with a property Value which is an object. When the value is bound by the model binder, object is always System.String[] regardless if I pass in a single number, a single word or anything else.
Can someone explain to me why that is, or if I can make it stop doing that?
Here's the code:
// Search.cs
public sealed class Search {
private object _Value = null;
public object Value {
get {
return this._Value;
}
set {
if (value != null) {
this._Value = value;
this.IsInt32 = (this._Value is Int32);
this.IsString = (this._Value is String);
};
}
}
public bool IsInt32 { get; set; }
public bool IsString { get; set; }
}
// SearchController.cs
[HttpPost]
public ActionResult List(
[Bind(Prefix = "Search", Include = "Value")] Search Search) {
return this.View();
}
// Form HTML
<form action="/Administration/Search" method="post">
#Html.TextBox("Search.Value", Model.Search.Value, new { type = "search", placeholder = "Search", size = 48, required = string.Empty })
<input type="submit" value="⌕" />
</form>
UPDATE
Per #Darin's suggestion and example I've made a custom binder and it seems to be working so far. Here's the code if anyone else runs into this, modify as needed of course:
public class SearchModelBinder : DefaultModelBinder {
public override object BindModel(
ControllerContext ControllerContext,
ModelBindingContext BindingContext) {
if (BindingContext == null) {
throw new ArgumentNullException("BindingContext");
};
ValueProviderResult ValueResult = BindingContext.ValueProvider.GetValue(BindingContext.ModelName + ".Value");
if (ValueResult == null) {
return (null);
};
string Value = ValueResult.AttemptedValue;
if (String.IsNullOrEmpty(Value)) {
return (null);
};
int Int;
if (int.TryParse(Value, out Int)) {
return new Search {
Value = Convert.ToInt32(Value)
};
};
long Long;
if (long.TryParse(Value, out Long)) {
return new Search {
Value = Convert.ToInt64(Value)
};
};
return new Search {
Value = Value
};
}
}
No idea what you are trying to achieve, why are you using object, ... but you could write a custom model binder for this Search model. This model binder will assign directly the parameter that is sent in the request to the Value property:
public class SearchModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Value");
if (value != null)
{
return new Search
{
Value = value.AttemptedValue
};
}
return base.BindModel(controllerContext, bindingContext);
}
}
which you could register in Global.asax:
ModelBinders.Binders.Add(typeof(Search), new SearchModelBinder());

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