How do you exclude properties from binding when ca

2019-01-27 12:37发布

问题:

I have a view model sent to the edit action of my controller. The ViewModel contains references to EntityObjects. (yea i'm fine with it and don't need to want to duplicate all the entities properties in the viewmodel).

I instantiate the view model and then call UpdateModel. I get an error that a property is "null" which is fine since it is a related model. I am trying to exclude the property from being bound during model binding. On debugging it I see in the entity where the model binder is trying to set the value of the property to null.

Here is my edit action:

var model = new SimplifiedCompanyViewModel(id);

var excludeProperties = new string[] { 
   "Entity.RetainedEarningsAccount.AccountNo"
   ,"Property.DiscountEarnedAccount.ExpenseCodeValue"
   ,"Entity.EntityAlternate.EntityID"
   ,"Property.BankAccount.BankAccountID"
   ,"Entity.PLSummaryAccount.AccountNo"
   ,"Property.RefundBank.BankAccountID"
   ,"Company.Transmitter.TCC"
};

try
{
    UpdateModel<SimplifiedCompanyViewModel>(model, String.Empty, null, excludeProperties);

    if (ModelState.IsValid)
    {
       //db.SaveChanges();
    }
       return RedirectToAction("Index");
}
catch
{
    return View(model);
}

I have looked at a few other issues about specifying a "prefix" but I don't think that is the issue since I am telling it to bind to the viewmodel instance not just the entity object.

Am I excluding the properties correctly? Strange thing is is only seems to happen on this item. I suspect it may be an issue with the fact that there is actually no refund bank related to my entity. But I have other related items that don't exist and don't see the same issue.

More info... since I'm told me model isn't designed well.

The Company is related to a BankAccount. The Company view shows the currently related BankAccount.BankAccountId and there is a hidden field with the BankAccount.Key. I use jQueryUI autocomplete feature to provide a dropdown of bank account displaying the BankAccount.BankAccountId and when one is selected the jQuery code changes the hidden field to have the correct Key value. So, when this is posted I don't want the current bankaccounts BankAccountID modified, hence I want it to skip binding that field.

If I exclude BankAccountId in the model then on the BankAccount edit view the user would never be able to change the BankAccountId since it won't be bound. I'm not sure how this indicates a poor model design.

回答1:

Another option here is simply don't include this attribute in your view and it won't be bound. Yes - you are still open to model injection then if someone creates it on the page but it is another alternative. The default templates in MVC will create your EditorFor, etc as separate items so you can just remove them. This prevents you from using a single line view editor with EditorForModel, but the templates don't generate it that way for you anyways.

EDIT (adding above comment)

DRY generally applies to logic, not to view models. One view = one view model. Use automapper to easily map between them. Jimmy Bogard has a great attribute for this that makes it almost automatic - ie you create the view model, load up your Customer entity for example, and return it in the action method. The AutpMap attribute will then convert it to a ViewModel. See lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models



回答2:

Use the Exclude property of the Bind attribute:

[Bind(Exclude="Id,SomeOtherProperty")]
public class SimplifiedCompanyViewModel
{
    public int Id { get; set; }

    // ...
}

This is part of the System.Web.Mvc namespace. It takes a comma-separated list of property names to exclude when binding.

Also you should consider using TryUpdateModel instead of UpdateModel. You can also just have the default model binder figure it out by passing it as an argument to the constructor:

public ActionResult Create([Bind(Exclude="Id")]SimplifiedCompanyViewModel model)
{
    // ...
}


回答3:

A very simple solution that I figured out.

 try
{
   UpdateModel<SimplifiedCompanyViewModel>(model, String.Empty, null, excludeProperties);
   ModelState.Remove("Entity.RetainedEarningsAccount.AccountNo");
   ModelState.Remove("Property.DiscountEarnedAccount.ExpenseCodeValue");
   ModelState.Remove("Entity.EntityAlternate.EntityID");
   ModelState.Remove("Property.BankAccount.BankAccountID");
   ModelState.Remove("Entity.PLSummaryAccount.AccountNo");
   ModelState.Remove("Property.RefundBank.BankAccountID");
   ModelState.Remove("ompany.Transmitter.TCC");

    if (ModelState.IsValid)
    {
       //db.SaveChanges();
    }
       return RedirectToAction("Index");
}
catch
{
    return View(model);
}


回答4:

Try the Exclude attribute.
I admit that I haven't ever used it.

[Exclude]
public Entity Name {get; set;}