Attribute [Bind(Exclude=“”)] fails to prevent over

2019-02-07 12:57发布

问题:

What is the best way to prevent MVC 4 over-posting?

According to MS sources, the [Bind] attribute is supposed to be the easiest way to prevent over-posting by preventing incoming form values from making it to the database. With the latest version of MVC & EF this does not seem to be working as expected/advertised, unless I'm missing something major.

From Wrox Professional ASP.NET MVC 4 (Jon Galloway's Chapter 7), the following class should prevent over-posting:

[Bind(Exclude="IsAdmin")]
public class User
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public bool IsAdmin { get; set; }
}

But all the [Bind] attribute does is prevent the form submission values from binding to the model. The model then has a blank/default value, which is written back to the database. In this case, it would ensure that IsAdmin = false EVERY TIME you call .SaveChanges() using this model. Any "true" values are overwritten. That's a HUGE security failure.

The alternate syntax - placing [Bind] in the Edit controller action parameter - does the exact same thing:

public ActionResult Edit([Bind(Exclude = "IsAdmin")] User user)

All "true" values are overwritten when .SaveChanges() is called, contradicting K. Scott Allen's blog post on the topic: http://odetocode.com/blogs/scott/archive/2012/03/11/complete-guide-to-mass-assignment-in-asp-net-mvc.aspx

The only alternative seems to be a flurry of dedicated ViewModels all wired up with Automapper. While secure, that seems like a MASSIVE headache, especially as:

  • You may have different requirements for Create, Edit, Index, and Detail actions, requiring different ViewModels
  • You may need to expose some read-only fields (such as CreatedBy on the Edit action) that cannot have the [ReadOnly] attribute on the property because they are updated by the Create action

I know that somebody is going to respond by saying you should never bind data models to views, but that is the default template behavior and the way it's shown in nearly all documentation. And besides, MVC + EF was supposed to make life easier, not harder, and an ocean of ModelView classes wired up with AutoMapper is not what I consider easier.

So does anybody know how to make [Bind] function as advertised?

回答1:

I think you may have mislead by the Wrox book on this occasion. What you describe is the intended behaviour of the Bind/Exclude property. See http://msdn.microsoft.com/en-us/library/system.web.mvc.bindattribute.exclude(v=vs.108).aspx.

If you do not want to bind values to every property on your model, I believe that ViewModels are they way to go, even though as you rightly point out they are something of an overhead. Nevertheless, the advantages of using them are significant, and IMO in this sort of context, justify the extra development work. For example:

  • Allowing partial entity updates
  • presenting data from multiple entities
  • decoupling the UI from the domain model, allowing you to vary labels, validation rules, error messages

Automapper is one option for doing the mapping from the entity to view models, but if you are using Lazy Loading, beware. I discovered Automapper doesn't handle updates to EF Proxy classes in the way I hoped. In the end I removed AM and rolled my own mapping mechanism based on an IMappable interface and a generic utility class. In many cases it's not much more code to type to do that than to configure Automapper.



回答2:

If you revert back to manuel model binding, you should not have any problems. If you do not place an input for "IsAdmin" your model will retain its original value. This adds a few lines of extra code but saves a lot of time by not generating not maintaining ViewModels.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Guid id, FormCollection collection)
{
    var user = db.Users.Find(id);
    if (user != null)
        TryUpdateModel(user);
    else
        return HttpNotFound();
    if (ModelState.IsValid)
    {
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(user);
}