So I've this Fee abstract class in my Models (generated by EF with Database First in mind)
public abstract partial class Fee
{
public int FeeId { get; set; }
public int ProductId { get; set; }
public string Name { get; set; }
public decimal StandardPrice { get; set; }
}
It's inherited by a number of others classes to model different kinds of fees, e.g. QuarterlyFee that kind of stuff. I have also a Customer class in the model and customers can have custom rates, modeled by the class CustomerRate.
Now on the customer detail page I'm trying to display all the possible fees for a customer so the user can pick which rates are custom and which are standards, no bid deal I just create a ViewModel class PossibleFee to model that :
public class PossibleFee
{
public int CustomerId { get; set; }
public int FeeId { get; set; }
public Boolean CustomRate { get; set; }
public decimal CustomerPrice { get; set; }
public virtual Customer Customer { get; set; }
public virtual Fee Fee { get; set; }
}
And I added a property to the Customer class to hold the possible fees (ICollection PossibleFees), I populate that property in the Controller method (public ActionResult Details(int id = 0)), an EditorTemplate later, and bam I've a list of all possible fees for a customer depending on the products they use that the user can edit.
Now however on postback, when I hit the Save button, the Default Model Binder does its job and try to populate a ICollection of PossibleFee, and for each PossibleFee try to create a Fee object to populate the property of the same name.
Of course it doesn't work since Fee is abstract, the question is rather simple:How do I tell the DefaultModelBinder to ignore that property on postback, well because I don't need it on postback ?
I know I could simply recopy the fields I need on the EditorTenplate of PossibleFee in the class PossibleFee, or even remove abstract from Fee. But I want to know if I can tune the DefaultModelBinder to my needs without touching the model.
EDIT 2 : Yarx offered a cleaner solution in my opinion, see 2 posts bellow
EDIT : Well Carlos Corral Carvajal almost got it right, the method SetProperty is called after the ModelBinder tries to create the object, so I had to overwrite BindProperty instead
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Attributes.Contains(new ModelBinderIgnoreOnPostback()))
{
return;
}
base.BindProperty(controllerContext, bindingContext,propertyDescriptor);
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=false)]
public class ModelBinderIgnoreOnPostback : Attribute {}
Added the custom Attribute in case somebody needs it
My suggestion is to separate your ViewModels so that you have a separate ViewModel per Action. Have a ViewModel that's just for displaying information and one that is used for accepting information from a Post Back. Because in cases like this the majority of the fields you want to bind to on a PostBack are also needed in the ViewModel used to display the data in the first place the easiest thing to do is to inherit your ViewModels. See below for example.
public class PossibleFeeInputModel
{
public int CustomerId { get; set; }
public int FeeId { get; set; }
public Boolean CustomRate { get; set; }
public decimal CustomerPrice { get; set; }
public virtual Customer Customer { get; set; }
}
.
public class PossibleFeeViewModel : PossibleFeeInputModel
{
public virtual Fee Fee { get; set; }
}
This way you use PossibleFeeViewModel to display your view, but use PossibleFeeInputModel to bind against when you accept the PostBack.