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.

其他提示

You can create a custom model binder which inherited from default model binder, and catch the Fee class and do specific actions on it:

public class CustomModelBinder : DefaultModelBinder
{

    protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
    {
        if (propertyDescriptor.PropertyType == typeof(Fee))
        {
            //Take desicions
        }

        base.SetProperty(controllerContext, bindingContext,
                            propertyDescriptor, value);
    }
}

And in the global.asax:

ModelBinders.Binders.DefaultBinder = new CustomModelBinder();

Hope it helps

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top