I m trying to create a custom model binder with below code:
public class TransactionModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
Object Model = bindingContext.Model;
//Do custom logic
return Model;
}
}
In global.asax, I m adding:
ModelBinders.Binders.Add(typeof(TransViewModel), new TransactionModelBinder());
Issue:
I m not sure how to get Model. I tried bindingContext.Model but it is null.
Please also guide if my line of code in Global.asax is fine.
See this article:
public class HomeCustomDataBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(HomePageModels))
{
HttpRequestBase request = controllerContext.HttpContext.Request;
string title = request.Form.Get("Title");
string day = request.Form.Get("Day");
string month = request.Form.Get("Month");
string year = request.Form.Get("Year");
return new HomePageModels
{
Title = title,
Date = day + "/" + month + "/" + year
};
//// call the default model binder this new binding context
//return base.BindModel(controllerContext, newBindingContext);
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
Well, if you intend to write the entire binder from scratch, you will not have a model. Instead you'd actually be the one creating the model (since that's what the binder is for), from the form data. Such as:
return new SomeModel
{
OneProp = request.Form["OneProp"],
AnotherProp = request.Form["AnotherProp"]
}
Alternatively you can inherit from DefaultModelBinder instead of IModelBinder, which you can use to extend only certain behavior instead of actually handling the construction of the model.
EDIT:
From your comments I'm understanding that you are only wanting to process one property in several viewmodels you might have (possibly multiple viewmodels have decimals that come from the view in a different format than what MVC expects for decimals.
In that case I'd actually use a different approach. Instead of registering the ModelBinder in global.asax, I'd remove it from there and do it declaratively on the actual properties that need that special format.
Such as:
[PropertyBinder(typeof(MyDecimalBinder))]
public decimal SomePropInAViewModel {get; set;}
This is based on a common approach of creating the PropertyBindingAttribute:
http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes or https://stackoverflow.com/a/12683210/1373170
And with a ModelBinder similar to this:
public class MyDecimalBinder : DefaultModelBinder {
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {
// use the propertyDescriptor to make your modifications, by calling SetProperty()
...
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
Now, if this is something you want applied to ALL decimals, you might also want to check out Phil Haack's full implementation on handling decimals using a custom binder:
http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx/
Related
I have a view model that looks like this:
class MyViewModel
{
public string Stuff { get; set; }
public IFoo Foo { get; set; }
}
The GET requests all work fine because I supply both, Stuff and IFoo to the view.
However, when posting back, MVC says it can't make an object of an interface.
Now, I have two options:
a) Change the type of the Foo property to a concrete implementation, which is no big deal; or
b) Do the model binding myself, which is an overkill for what I want. I really don't care about posting back the IFoo member.
How do I tell MVC not to post back IFoo? I don't have any HTML controls also representing the IFoo. Properties from the IFoo, I use as hidden thingies in the view.
You can exclude properties from binding.
Via the model
[Bind(Exclude="Foo")]
class MyViewModel
{
public string Stuff { get; set; }
public IFoo Foo { get; set; }
}
Via the action
[HttpPost]
public ActionResult MyGreatAction([Bind(Exclude="Foo")]MyViewModel model)
{
}
I had a similar problem and found this link to be of super help to me.. Custom Model Binder But just in case the link goes dead sometime in the future here is the thought process behind it.
Posting a list of interfaces
If you try to post this form, MVC will throw this error: Cannot create an instance of an interface. This is because the default model binder works by creating an instance of your model (and any properties it has) and mapping the posted field names to it; Section.Title maps to the Title property on the Section object, for example. However, Section.SectionFields[0] presents a problem – you cannot create an instance of an interface (or an abstract class).
The solution is to create and register a custom model binder for the IField class...
First create a model binder and inherit from the DefaultModelBinder class:
public class IFieldModelBinder : DefaultModelBinder {
protected override object CreateModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType) {
// Our work here
}
}
Secondly, register it with your application – this is usually done in Application_Start in Global.asax.
protected void Application_Start(Object sender, EventArgs e) {
ModelBinders.Binders.Add(typeof(IField), new IFieldModelBinder());
}
Next cast each IField into its concrete type. I think she used reflection so she added to properties to her Interface FieldClassName, and FieldAssemblyName. Then she put in 2 hidden fields for Class name and Assembly name in her editor template.
#model SectionSummary
#Html.HiddenFor(x => x.FieldClassName)
#Html.HiddenFor(x => x.FieldAssemblyName)
Now, whenever this particular field is posted, the custom model binder will have the information about the actual type available – which means we can use it to cast the IField object and return a model that can be instantiated.
public class IFieldModelBinder : DefaultModelBinder
{
protected override object CreateModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType)
{
// Get the submitted type - should be IField
var type = bindingContext.ModelType;
// Get the posted 'class name' key - bindingContext.ModelName will return something like Section.FieldSections[0] in our particular context, and 'FieldClassName' is the property we're looking for
var fieldClassName = bindingContext.ModelName + ".FieldClassName";
// Do the same for the assembly name
var fieldAssemblyName = bindingContext.ModelName + ".FieldAssemblyName";
// Check that the values aren't empty/null, and use the bindingContext.ValueProvider.GetValue method to get the actual posted values
if (!String.IsNullOrEmpty(fieldClassName) && !String.IsNullOrEmpty(fieldAssemblyName))
{
// The value provider returns a string[], so get the first ([0]) item
var className = ((string[])bindingContext.ValueProvider.GetValue(fieldClassName).RawValue)[0];
// Do the same for the assembly name
var assemblyName =
((string[])bindingContext.ValueProvider.GetValue(fieldAssemblyName).RawValue)[0];
// Once you have the assembly and the class name, get the type - I am overwriting the IField object that came in, but I do not think you have to do that
modelType = Type.GetType(className + ", " + assemblyName);
// Finally, create an instance of this type
var instance = Activator.CreateInstance(modelType);
// Update the binding context's meta data
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, modelType);
// Return the instance - which will now be a SummaryField or CommentField - rather than an IField
return instance;
}
return null;
}
}
You will now be able to post your List object – for each IField, the custom model binder will be called to figure out what the concrete type of each object is.
I have noticed the following behavior and I wonder if anyone can explain why it is happening and how it can be prevented.
In my partial classes I have added various read-only properties that are not present in the database. When the object is created, these properties are accessed before we get to the controller's Create method. However, properties declared with just get; set; are not accessed.
To elaborate, using very simple examples, if I have the following properties:
public bool getBoolProperty {
get {
return true;
}
}
public bool getSetBoolProperty {
get; set;
}
If I then put breakpoints on the properties, when the object is created the breakpoint is hit for the first property but not the second. However, if I have a method:
public bool getBoolProperty() {
return true;
}
then this is not accessed.
I have tried all sorts of variations and annotations
// empty set
public bool getBoolProperty {
get {
return true;
}
set {}
}
// not mapped attribute
[NotMapped]
public bool getBoolProperty {
get {
return true;
}
}
// getting private variable
private bool _boolProperty = true;
public bool getPrivateBoolProperty {
get {
return _boolProperty;
}
}
I've tried declaring the properties virtual, yet all variations, except the get; set; variety, are accessed when the object gets created. This behaviour also occurs for integer properties
public virtual int getIntProperty {
get {
return 1;
}
}
and date/time properties,
public virtual DateTime getDateProperty {
get {
return DateTime.Now;
}
}
but not for string properties
public string getStringProperty {
get {
return "Hello String";
}
}
or other entity properties
public Item getItem {
get {
return new Item();
}
}
The problem I have is that some of these properties can involve some logic that might need database access eg
public bool HasChildren {
get {
return this.Children !== null && this.Children.Count > 0;
}
}
which, at the time of creation, the entity won't have.
Obviously I can get around this by making everything a method, but I would be interested to know why ASP.NET MVC accesses these properties on object creation (Some sort of internal validation perhaps), and I would be interested to know if there are any methods of preventing this, possibly by way of an annotation I haven't come across.
My project is database first, C#, EF 4.1, MVC
Edit
I should also point out I'm using POCO entities and accessing the database through stored procedures/function imports.
One thought occurred to me is that these properties are being accessed as part of the change tracking system as discussed here. It seems that what is probably happening is a snapshot is being taken of the newly created item.
I tried turning off proxy creation
dbContext.ContextOptions.ProxyCreationEnabled = false;
but the properties are still getting accessed on creation.
Edit 20130322
Following #ladislavmrnka's suggestion of exploring the stack trace I got this:
at System.ComponentModel.ReflectPropertyDescriptor.GetValue(Object component)
at System.Web.Mvc.AssociatedMetadataProvider.<>c__DisplayClassb.<GetPropertyValueAccessor>b__a()
at System.Web.Mvc.ModelMetadata.get_Model()
at System.Web.Mvc.DataAnnotationsModelValidator.<Validate>d__1.MoveNext()
at System.Web.Mvc.ModelValidator.CompositeModelValidator.<Validate>d__5.MoveNext()
at System.Web.Mvc.DefaultModelBinder.OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
at System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Object model)
at System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
at System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
at System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
at System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
at System.Web.Mvc.Controller.ExecuteCore()
at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
at System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext)
at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.<BeginProcessRequest>b__5()
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass1.<MakeVoidDelegate>b__0()
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.MvcHandler.<>c__DisplayClasse.<EndProcessRequest>b__d()
at System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f)
at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action)
at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult)
at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
In it you will notice the calls to a validator or two. As a test I changed my bool property to a nullable bool property:
public bool? getBoolProperty {
get {
return true;
}
}
This time the property was not accessed when the object was created. This is my desired behaviour, however, I don't want to have to change all my custom properties to be nullable, so my question now becomes...
Is there any way to tell the framework not to validate a property? Perhaps via an attribute. This answer almost answers the question, but since I am using database first, I don't seem to have a ValidateOnSaveEnabled property to turn off. Perhaps I need to revisit my models.
As with most of EF's problems, the answer lies with view models.
Because most of the properties that were being accessed were not relevant until the model had been created and persisted to the db, I created a view model especially for the Create view which only contained the absolute minimum number of properties needed to create the model. I modified the controller's Create method to accept my new view model and voila, on creating this new model there were no accesses to any of the irrelevant properties on the main model.
So the answer to my original question appears to be that entity framework performs validation on all non-nullable scalar properties when an object is created and one way to avoid this is as explained above.
I have a custom model binder which is invoked for a particular parameter going into an action method:
public override ActionResult MyAction(int someData, [ModelBinder(typeof(MyCustomModelBinder))]List<MyObject> myList ... )
This works well - the binder is called as expected. However, I want to invoke the default model binder for some addtional values that are in the Request.Form collection. The form keys are named
like this:
dataFromView[0].Key
dataFromView[0].Value
dataFromView[1].Key
dataFromView[1].Value
The default model binder nicely converts these values into an IDictionary if I add an IDictionary as a parameter on the action method.
However, I want to manipulate these values at the model binder level (along with the original List object above).
Is there a way to get the default model binder to create this dictionary from the form values for my in the BindModel() method of my custom model binder?
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//Get the default model binder to provide the IDictionary from the form values...
}
I've tried to using the bindingContext.ValueProvider.GetValue but it always seems to return null when I'm trying to cast to an IDictionary.
Here's a potential solution I found (by looking at the default model binder source code) which allows you to use the default model binders functionality for creating a Dictionary, List etc.
Create a new ModelBindingContext detailing the binding values you require:
var dictionaryBindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, typeof(IDictionary<long, int>)),
ModelName = "dataFromView", //The name(s) of the form elements you want going into the dictionary
ModelState = bindingContext.ModelState,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
var boundValues = base.BindModel(controllerContext, dictionaryBindingContext);
Now the default model binder is invoked with the binding context you have specified and will return the bound object as normal.
Seems to work so far...
If your model binder needs to work with some other form data this means that you haven't positioned this model binder on the correct type. The correct type for your model binder would be the following:
public class MyViewModel
{
public IDictionary<string, string> DataFromView { get; set; }
public List<MyObject> MyList { get; set; }
}
and then:
public class MyCustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext);
// do something with the model
return model;
}
}
and then:
[HttpPost]
public ActionResult Index([ModelBinder(typeof(MyCustomModelBinder))] MyViewModel model)
{
...
}
This assumes the following data is posted:
dataFromView[0].Key
dataFromView[0].Value
dataFromView[1].Value
dataFromView[1].Key
myList[0].Text
myList[1].Text
myList[2].Text
I'm trying to create a tagging system for my project. I need the pass a string (for ex: "test1, test2, test3") which will be binded to an entity as a list.
I'm using EF and my view inherits an entity, defined in EF. Without creating a view model, is it possible to do that?
Quite honestly view models is the way to go here.
But because you asked I will try to answer. IIRC EF models are partial classes, meaning that you could add properties to them, like this:
public partial class MyEFModel
{
public IEnumerable<string> List
{
get
{
return SomeStringProperty.Split(',');
}
set
{
SomeStringProperty = string.Join(",", value.ToArray());
}
}
}
Another way to achieve this is to write a custom model binder, like this:
public class MyBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value != null)
{
return value.AttemptedValue.Split(',');
}
return base.BindModel(controllerContext, bindingContext);
}
}
public class HomeController : Controller
{
public ActionResult Index(
[ModelBinder(typeof(MyBinder))] IEnumerable<string> values
)
{
return View();
}
}
and then /home/index?values=val1,val2,val3 should bind correctly to the list.
There are couple of ways to achieve this:
Custom Action Filter
Custom Model Binder
These implementations can be found here:
Is it possible to change the querystring variable in ASP.NET MVC path before it hits the controller?
I have the following controller action:
[HttpPost]
public ViewResult DoSomething(MyModel model)
{
// do something
return View();
}
Where MyModel looks like this:
public class MyModel
{
public string PropertyA {get; set;}
public IList<int> PropertyB {get; set;}
}
So DefaultModelBinder should bind this without a problem. The only thing is that I want to use special/custom binder for binding PropertyB and I also want to reuse this binder. So I thought that solution would be to put a ModelBinder attribute before the PropertyB which of course doesn't work (ModelBinder attribute is not allowed on a properties). I see two solutions:
To use action parameters on every single property instead of the whole model (which I wouldn't prefer as the model has a lot of properties) like this:
public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
To create a new type lets say MyCustomType: List<int> and register model binder for this type (this is an option)
Maybe to create a binder for MyModel, override BindProperty and if the property is "PropertyB" bind the property with my custom binder. Is this possible?
Is there any other solution?
override BindProperty and if the
property is "PropertyB" bind the
property with my custom binder
That's a good solution, though instead of checking "is PropertyB" you better check for your own custom attributes that define property-level binders, like
[PropertyBinder(typeof(PropertyBBinder))]
public IList<int> PropertyB {get; set;}
You can see an example of BindProperty override here.
I actually like your third solution, only, I would make it a generic solution for all ModelBinders, by putting it in a custom binder that inherits from DefaultModelBinder and is configured to be the default model binder for your MVC application.
Then you would make this new DefaultModelBinder automatically bind any property that is decorated with a PropertyBinder attribute, using the type supplied in the parameter.
I got the idea from this excellent article: http://aboutcode.net/2011/03/12/mvc-property-binder.html.
I'll also show you my take on the solution:
My DefaultModelBinder:
namespace MyApp.Web.Mvc
{
public class DefaultModelBinder : System.Web.Mvc.DefaultModelBinder
{
protected override void BindProperty(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor);
if (propertyBinderAttribute != null)
{
var binder = CreateBinder(propertyBinderAttribute);
var value = binder.BindModel(controllerContext, bindingContext, propertyDescriptor);
propertyDescriptor.SetValue(bindingContext.Model, value);
}
else // revert to the default behavior.
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
IPropertyBinder CreateBinder(PropertyBinderAttribute propertyBinderAttribute)
{
return (IPropertyBinder)DependencyResolver.Current.GetService(propertyBinderAttribute.BinderType);
}
PropertyBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor)
{
return propertyDescriptor.Attributes
.OfType<PropertyBinderAttribute>()
.FirstOrDefault();
}
}
}
My IPropertyBinder interface:
namespace MyApp.Web.Mvc
{
interface IPropertyBinder
{
object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext, MemberDescriptor memberDescriptor);
}
}
My PropertyBinderAttribute:
namespace MyApp.Web.Mvc
{
public class PropertyBinderAttribute : Attribute
{
public PropertyBinderAttribute(Type binderType)
{
BinderType = binderType;
}
public Type BinderType { get; private set; }
}
}
An example of a property binder:
namespace MyApp.Web.Mvc.PropertyBinders
{
public class TimeSpanBinder : IPropertyBinder
{
readonly HttpContextBase _httpContext;
public TimeSpanBinder(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
MemberDescriptor memberDescriptor)
{
var timeString = _httpContext.Request.Form[memberDescriptor.Name].ToLower();
var timeParts = timeString.Replace("am", "").Replace("pm", "").Trim().Split(':');
return
new TimeSpan(
int.Parse(timeParts[0]) + (timeString.Contains("pm") ? 12 : 0),
int.Parse(timeParts[1]),
0);
}
}
}
Example of the above property binder being used:
namespace MyApp.Web.Models
{
public class MyModel
{
[PropertyBinder(typeof(TimeSpanBinder))]
public TimeSpan InspectionDate { get; set; }
}
}
#jonathanconway's answer is great, but I would like to add a minor detail.
It's probably better to override the GetPropertyValue method instead of BindProperty in order to give the validation mechanism of the DefaultBinder a chance to work.
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
PropertyBinderAttribute propertyBinderAttribute =
TryFindPropertyBinderAttribute(propertyDescriptor);
if (propertyBinderAttribute != null)
{
propertyBinder = CreateBinder(propertyBinderAttribute);
}
return base.GetPropertyValue(
controllerContext,
bindingContext,
propertyDescriptor,
propertyBinder);
}
It has been 6 years since this question was asked, I would rather take this space to summarize the update, instead of providing a brand new solution. At the time of writing, MVC 5 has been around for quite a while, and ASP.NET Core has just come out.
I followed the approach examined in the post written by Vijaya Anand (btw, thanks to Vijaya): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes. And one thing worth noting is that, the data binding logic is placed in the custom attribute class, which is the BindProperty method of the StringArrayPropertyBindAttribute class in Vijaya Anand's example.
However, in all the other articles on this topic that I have read (including #jonathanconway's solution), custom attribute class is only a step stone that leads the framework to find out the correct custom model binder to apply; and the binding logic is placed in that custom model binder, which is usually an IModelBinder object.
The 1st approach is simpler to me. There may be some shortcomings of the 1st approach, that I haven't known yet, though, coz I am pretty new to MVC framework at the moment.
In addition, I found that the ExtendedModelBinder class in Vijaya Anand's example is unnecessary in MVC 5. It seems that the DefaultModelBinder class which comes with MVC 5 is smart enough to cooperate with custom model binding attributes.