I have a type:
public class IssueForm
{
Order Order {get; set;}
Item Item {get; set;}
Range Range {get; set;}
}
I created a custom model binder due to requirements on Order and Item, but Range could still use the Default Model Binder.
Is there a way from within my custom model binder to call the default model binder to return a Range object? I think I just have to just setup ModelBindingContext correctly, but I don't know how.
EDIT
Looking at the first comment and answer -- it seems like inheriting from the default model binder could be useful.
To add more specifics for my setup so far I have:
public IssueFormModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Order = //code to pull the OrderNumber from the context and create an Order
Item = //code to pull the ItemNumber from the context and create an Item
IssueForm form = IssueFormFactory.Create(Order, Item);
form.Range = // ** I'd like to replace my code with a call to the default binder **
return form
}
}
This might be a stupid way of doing it... this is my first model binder. Just pointing out my current implementation.
EDIT #2
So the answers to override BindProperty will work if I can hook into like a "I'm all done binding" method and call the Factory method with the properties.
I guess I really should look at the DefaultModelBinder implementation and quit being stupid.
override the BindProperty from the DefaultModelBinder:
public class CustomModelBinder:DefaultModelBinder
{
protected override void BindProperty( ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor )
{
if (propertyDescriptor.PropertyType == typeof(Range))
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
// bind the other properties here
}
}
Try something like this:
public class CustomModelBinder : DefaultModelBinder {
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {
if(propertyDescriptor.Name == "Order") {
...
return;
}
if(propertyDescriptor.Name == "Item") {
...
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
I think I would have registered two different custom model binders, one for Order and one for Item, and let the default model binder handle the Range and the IssueForm.
Related
For the code below, I am wondering how Employee type affects by prefixed [] brackets
public ActionResult SaveEmployee([ModelBinder(typeof(MyEmployeeModelBinder))]Employee e, string BtnSubmit)
{
// some logic
}
MyEmployeeModelBinder is a custom model binder, that will execute instead of default model binder in Employee e.
public class MyEmployeeModelBinder: DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
...
}
}
The Employee type affects bindingContext properties in BindModel(), like bindingContext.ModelMetadata and bindingContext.ModelType, that contains information about the model. Take a look at ModelMetaData: https://msdn.microsoft.com/pt-br/library/system.web.mvc.modelmetadata%28v=vs.108%29
I have a custom model binder inheriting from DefaultModelBinder. What I want it to do is to set a property on the model, that can't be resolved by the DefaultModelBinder. It looks like this:
public class FooModelBinder : DefaultModelBinder {
public override void BindModel(ControllerContext controllerContext, ModelBindingContext modelBindingContext)
{
var model = base.BindModel(controllerContext, bindingContext);
((IFooModel)model).Bar = GetBarFromSomewhere();
return model;
}
}
However, since the Bar property in IFooModel cannot be null and I'm using FluentValidation with a rule saying that, the ModelState will be invalid after I've called base.BindModel.
So I would like to either avoid validating the model when calling base.BindModel, or at least clear the errors and re-validate the model after I've set the Bar property.
I've tried resolving the validators and validating the model, but I can't seem to get it to actually run the validation, and it doesn't result in any errors (even when it should):
var validators = bindingContext.ModelMetadata.GetValidators(controllerContext);
foreach(var validator in validators) {
foreach (var result in validator.Validate(model)) {
bindingContext.ModelState.AddModelError(result.MemberName, result.Message);
}
}
After running this before I return model, validators contains a FluentValidationModelValidator, but when I call validator.Validate I don't get any errors. I have another property on my model which did cause an error when I ran base.BindModel earlier, so I would expect the same error to occur here.
Instead of overriding the BindModel method you could try overriding the BindProperty method:
public class FooModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "Bar")
{
var model = bindingContext.Model as IFooModel;
if (model != null)
{
model.Bar = GetBarFromSomewhere();
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
Background:
In my MVC post back action methods I am receiving command objects rather than view models. The idea is that these command objects (which roughly equate to transaction scripts) will be set up and ready to execute upon entering the action method, with the model binder having set parameters which are used during the execution process:
public class MyCommand : IMyCommand
{
// In this case Value1 and Value2 are being set by the default model binder from posted form values - wonderful :)
public String Value1 { get; set; }
public String Value2 { get; set; }
public CommandResult ExecuteCommand()
{
// Does something awesome...
}
}
To make things a little more complex, my command objects have dependencies (services, repositories etc) which are required in their respective constructors; so I had to create a custom model binder which used the default DependencyResolver (which was already set up with my IoC container) to construct the model objects:
public class DependencyModelBinder : DefaultModelBinder
{
protected override Object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return DependencyResolver.Current.GetService(modelType);
}
}
And set up in Global.asax.cs like so:
ModelBinders.Binders.DefaultBinder = new DependencyModelBinder();
Again this all works fine, the dependencies are injected into the constructor and then the default model binder takes over to set the properties as usual.
The Issue:
The problem I have is that all of my command objects have a 'SessionId' GUID parameter (which comes from a cookie), and the first thing they do is try to resolve a session object from this id using an injected service.
public class MyCommand : IMyCommand
{
public MyCommand (ISessionRepository sessionRepository) { ... }
public Guid SessionId { get; set; } // Set by model binder from a cookie...
public CommandResult Execute()
{
Session session = SessionRepository.Get(SessionId);
if (session == null)
// Do something not so awesome...
}
}
I wanted to remove this repetition, so I created a second model binder which would take care of this lookup in the repository, meaning my command objects could have a Session property directly (removing the constructor dependency for the session repository).
public class SessionModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var sessionRepository = DependencyResolver.Current.GetService<ISessionRepository>();
return sessionRepository.Get((Guid)controllerContext.HttpContext.Request["SessionId"]);
}
}
My Global.asax.cs file now looking like so:
ModelBinders.Binders.DefaultBinder = new DependencyModelBinder();
ModelBinders.Binders.Add(typeof(Session), new SessionModelBinder());
Having tested the SessionModelBinder in isolation, I know it works. However when using it in conjunction with the DependencyModelBinder, it is never called. How can I get MVC to use my DependencyModelBinder when constructing model objects, but have it use my SessionModelBinder when binding session properties on them? Or does anyone know a better approach to this?
You could use the GetPropertyValue method in your original model binder to provide a value for the Session property:
public class DependencyModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return DependencyResolver.Current.GetService(modelType);
}
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
if (propertyDescriptor.Name == "Session")
{
var sessionRepository = DependencyResolver.Current.GetService<ISessionRepository>();
return sessionRepository.Get(controllerContext.HttpContext.Request["SessionId"]);
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
I would like to write my own model binder for DateTime type. First of all I'd like to write a new attribute that I can attach to my model property like:
[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}
This is the easy part. But the binder part is a bit more difficult. I would like to add a new model binder for type DateTime. I can either
implement IModelBinder interface and write my own BindModel() method
inherit from DefaultModelBinder and override BindModel() method
My model has a property as seen above (Birth). So when the model tries to bind request data to this property, my model binder's BindModel(controllerContext, bindingContext) gets invoked. Everything ok, but. How do I get property attributes from controller/bindingContext, to parse my date correctly? How can I get to the PropertyDesciptor of property Birth?
Edit
Because of separation of concerns my model class is defined in an assembly that doesn't (and shouldn't) reference System.Web.MVC assembly. Setting custom binding (similar to Scott Hanselman's example) attributes is a no-go here.
you can change the default model binder to use the user culture using IModelBinder
public class DateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
return value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
}
}
public class NullableDateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);
return value == null
? null
: value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
}
}
And in the Global.Asax add the following to Application_Start():
ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new NullableDateTimeBinder());
Read more at this excellent blog that describe why Mvc framework team implemented a default Culture to all users.
I had this very big problem myself and after hours of try and fail I got a working solution like you asked.
First of all since having a binder on just a property is not possibile yuo have to implement a full ModelBinder. Since you don't want the bind all the single property but only the one you care you can inherit from DefaultModelBinder and then bind the single property:
public class DateFiexedCultureModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(DateTime?))
{
try
{
var model = bindingContext.Model;
PropertyInfo property = model.GetType().GetProperty(propertyDescriptor.Name);
var value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
if (value != null)
{
System.Globalization.CultureInfo cultureinfo = new System.Globalization.CultureInfo("it-CH");
var date = DateTime.Parse(value.AttemptedValue, cultureinfo);
property.SetValue(model, date, null);
}
}
catch
{
//If something wrong, validation should take care
}
}
else
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}
In my example I'm parsing date with a fiexed culture, but what you want to do is possible. You should create a CustomAttribute (like DateTimeFormatAttribute) and put it over you property:
[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}
Now in the BindProperty method, instead of looking for a DateTime property you can look for a property with you DateTimeFormatAttribute, grab the format you specified in the constructor and then parse the date with DateTime.ParseExact
I hope this helps, it took me very long to come with this solution. It was actually easy to have this solution once I knew how to search it :(
I don't think you should put locale-specific attributes on a model.
Two other possible solutions to this problem are:
Have your pages transliterate dates from the locale-specific format to a generic format such as yyyy-mm-dd in JavaScript. (Works, but requires JavaScript.)
Write a model binder which considers the current UI culture when parsing dates.
To answer your actual question, the way to get custom attributes (for MVC 2) is to write an AssociatedMetadataProvider.
You could implement a custom DateTime Binder like so, but you have to take care about the assumed culture and value from the actual client request. May you get an Date like mm/dd/yyyy in en-US and want it to convert in the systems culture en-GB (which it would be like dd/mm/yyyy) or an invariant culture, like we do, then you have to parse it before and using the static facade Convert to change it in its behaviour.
public class DateTimeModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName);
var modelState = new ModelState {Value = valueResult};
var resDateTime = new DateTime();
if (valueResult == null) return null;
if ((bindingContext.ModelType == typeof(DateTime)||
bindingContext.ModelType == typeof(DateTime?)))
{
if (bindingContext.ModelName != "Version")
{
try
{
resDateTime =
Convert.ToDateTime(
DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture,
DateTimeStyles.AdjustToUniversal).ToUniversalTime(), CultureInfo.InvariantCulture);
}
catch (Exception e)
{
modelState.Errors.Add(EnterpriseLibraryHelper.HandleDataLayerException(e));
}
}
else
{
resDateTime =
Convert.ToDateTime(
DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture), CultureInfo.InvariantCulture);
}
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return resDateTime;
}
}
Anyway, culture dependend DateTime parsing in a stateless Application can by a cruelty...Especially when you work with JSON on javascript clientside and backwards.
Given the following Model,
public class A
{
public string Name { get; set; }
}
public class B
{
public string Address { get; set; }
public A InstanceOfA { get; set; }
}
View,
<%= Html.TextBox("A.Name") %>
and Controller
UpdateModel<B>(b, collection.ToValueProvider());
my b instance will contain a property of A with an empty string for Name.
Is there anyway to have UpdateModel set the A property to null if no value has been entered for name?
To clarify, this is a simple case, my real world scenario contains data models with hundreds of properties of this ilk. The definition of these data models is out of my hands. Therefore I need a solution for the general case, ie don't create a property if no values have been entered.
Further clarification: i need this to work in edit scenarios aswell, i.e. an instance of b with A.Name set to "foo" is edited to set A.Name to "", i want A to be null.
I just discovered this behavior (by accident thanks to a check constraint) and I think it is a mistake. I wonder how many devs are now inadvertently saving empty strings into their db instead of null? :-)
Anyway, I'm going to explore this a bit more and see if there is a better solution.
Update:
Here is a solution:
using System.ComponentModel;
using System.Web.Mvc;
namespace CustomerWebsite.Mvc
{
public sealed class EmptyStringToNullModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext,
ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
if ((value != null)
&& (value.GetType() == typeof(string)
&& ((string)value).Length == 0))
{
value = null;
}
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
}
}
}
And in Application_Start put this:
ModelBinders.Binders.DefaultBinder = new EmptyStringToNullModelBinder();
This is kinda related and I thought it could help those who somehow reached this page. I am working with ASP.NET MVC 3 and in this version when an empty text box is posted to the server it's bound as null to the model.
I had to write the opposite model binder - one that changes null strings to empty strings.
I took the above code by Andrew Peters (thanks for that!) and changed it to the following.
Things to notice:
As mentioned already - this model binder does the opposite.
Instead of checking the type of the value I'm checking it via the propertyDescriptor, which I think is better, because this is it's purpose - to describe the value. In my case it's essential because I can't examine a value that's null. Through the descriptor I can find all that I need.
And here's code:
public sealed class CustomModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext,
ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
if (value == null && propertyDescriptor.PropertyType == typeof(string))
value = string.Empty;
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
}
}
I hope this helps someone.
UpdateModel initialize a property when this property is in the FormCollection. In order to prevent UpdateModel from setting some properties to empty values you can delete all items from your FormCollection where value is empty. To do this you can add an extension method to the NameValueCollection type.
It appears that ASP.NET MVC 2 Preview 1 will do this for you when doing modelbinding.
ie. convert automatically ?Foo=&Bar=cat to
model.Foo = null;
model.Bar = "cat":
I actually prefer to get an empty string over a null so I'm not sure yet if its the final design but there does seem to be a change.
Thanks for your answer, I changed it a little bit so that not all string properties are changed, and properties that are not string can be changed too. Thanks to DefaultValueAttribute.
public class DefaultValueModelBinder: DefaultModelBinder
{
protected override void SetProperty( ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
object value )
{
if( value == null )
{
var defaultValue = (DefaultValueAttribute)propertyDescriptor.Attributes[ typeof(DefaultValueAttribute) ];
if( defaultValue != null )
value = defaultValue.Value;
}
base.SetProperty( controllerContext, bindingContext, propertyDescriptor, value );
}
}