【问题标题】:bypassing model validation in controller?绕过控制器中的模型验证?
【发布时间】:2013-10-10 08:10:20
【问题描述】:

我已经为我的数据库创建了一个 ADO.NET 模型。 使用 CRUD(实体框架并使用我创建的 ADO.NET 实体模型)创建了一个新控制器。

在我的数据库中,我有一个简单的用户表。表中的密码行将保存使用 SimpleCrypto (PBKDF2) 加密的用户密码。

在我的 ADO.NET Users.cs 模型中,我添加了以下验证:

[Required]
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 6)]
[Display(Name = "Password")]
public string Password { get; set; }

这适用于浏览器中的 jQuery 并进行验证。 但是在我的控制器中,我正在加密密码,然后密码字符串的长度将超过 20 个字符。

var crypto = new SimpleCrypto.PBKDF2();
var encryptedPass = crypto.Compute(user.Password);

user.Password = encryptedPass;
user.PasswordSalt = crypto.Salt;

_db.Users.Add(user);
_db.SaveChanges();

这给了我“一个或多个实体的验证失败。”-错误。

我可以将用户复制到“var newUser”,然后在那里设置所有属性,但在这种情况下是否有更简单的方法绕过模型验证?

编辑:如果我删除模型中密码道具的验证,那么一切正常。所以是验证给了我错误,因为我将密码从 6-20 长度字符更改为 +100 长度字符,因为控制器中的加密。

编辑:插入此问题的完整控制器部分。

[HttpPost]
public ActionResult Create(Users user)
{
    if (!ModelState.IsValid)
    {
        return View();
    }
    if (_db.Users.FirstOrDefault(u => u.Email == user.Email) != null)
    {
        ModelState.AddModelError("", "User already exists in database!");
        return View();
    }

    var crypto = new SimpleCrypto.PBKDF2();
    var encryptedPass = crypto.Compute(user.Password);

    user.Password = encryptedPass;
    user.PasswordSalt = crypto.Salt;

    _db.Users.Add(user);
    _db.SaveChanges();

    return RedirectToAction("Index", "User");
}

【问题讨论】:

标签: c# asp.net-mvc entity-framework asp.net-mvc-4


【解决方案1】:

这是 ViewModel 进入游戏的地方。您应该创建一个模型,然后将其传递给视图并将其映射回域模型。

视图模型:

public class RegisterModel
{
    [Required]
    public string UserName { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [StringLength(20, MinimumLength = 6)]
    [Display(Name = "Password")]
    public string Password { get; set; }
}

领域模型(您当前的User 模型):

public class User
{
    // other properties..

    [Required]
    public string Password { get; set; }
}

您可以像这样在控制器中使用这些模型:

GET 操作:

public ActionResult Register()
{
    var registerModel = new RegisterModel();
    return View(registerModel)
}

这样的观点:

@model RegisterModel

@Html.LabelFor(model => model.UserName)
@Html.TextBoxFor(model => model.UserName)
@Html.ValidationMessageFor(model => model.UserName)

@Html.LabelFor(model => model.Password)
@Html.PasswordFor(model => model.Password)
@Html.ValidationMessageFor(model => model.Password)

还有POST 操作:

[HttpPost]
public ActionResult Register(RegisterModel registerModel)
{
    // Map RegisterModel to a User model.       
    var user = new User
                   {
                        UserName = registerModel.UserName,
                        Password = registerModel.Password   // Do the hasing here for example.
                    };
    db.Users.Add(user);
    db.SaveChanges();                           
}

【讨论】:

  • 谢谢。那么愚蠢的问题。如何将 RegisterModel 与视图“绑定”,将 User 模型仅与控制器“绑定”?
  • 很抱歉劫持了这个问题,但您不应该将两个不同的模型绑定到您的视图和控制器。您会将RegisterModel 传递给您的视图,并且您会期望RegisterModel 返回-MVC 有它自己的绑定处理程序正是出于这个原因。我建议您多阅读:asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-3
  • @user1281991 我用两个控制器操作和一个视图的示例更新了我的答案。
【解决方案2】:

您说您的密码加密正在控制器中进行。在这种情况下,您不应该在 验证之后加密吗?例如:

public ActionResult SomeControllerAction(UserViewModel user)
{
    if (!ModelState.IsValid)
    {
        // at this point the human readable (and assuming < 20 length) password
        // would be getting validated
        return View(user);
    }

    // now when you're writing the record to the DB, encrypt the password
    var crypto = new SimpleCrypto.PBKDF2();
    var encryptedPass = crypto.Compute(user.Password);

    user.Password = encryptedPass;
    user.PasswordSalt = crypto.Salt;

    _db.Users.Add(user);
    _db.SaveChanges();

    // return or redirect to whatever route you need
}

如果您想专门控制验证,请尝试在您的视图模型类上实现 IValidatableObject 并在此处执行验证,而不是通过属性。例如:

public class UserViewModel : IValidatableObject
{
    public string Username { get; set; }
    public string Password { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        // validate the unencrypted password's length to be < 20
        if (this.Password.Length > 20)
        {
            yield return new ValidationResult("Password too long!");
        }
    }        
}

【讨论】:

  • 您好,谢谢。我是 MVC 的新手。我在上面的问题中粘贴了控制器部分。
  • @user1281991 没问题,看起来您的验证在数据库级别失败 - 您正在加密的密码长度超过了 User.Password DB 表列可以合理容纳的长度。这个项目代码优先还是数据库优先?
  • 也不能是 DB 中的长度会导致问题。 DB中的密码长度设置为300。在Visual Studio中尝试调试,我输入的随机密码被加密为:“HS90iFjoivSHcc1D6Ynt6liYKr+VKpcOu1nPwUhe5qPqGbjRUfEzff93VdsicETbFOnNmyaxyc6VrVimiQGNww==”,我猜这就像100个字符?如果我在我的模型中删除密码验证,那么它可以工作吗?没有错误发生,一切都在数据库中存储良好
【解决方案3】:

如果我理解正确,您有一个带有密码字段的数据库表。
根据您的模型,此密码字段长度为 20 个字符

[StringLength(20, MinimumLength = 6)]

并且您想插入一个大于 20 个字符的值。
如果实体框架没有阻止你,那么你会得到一个数据库错误。(实体框架不知道你的数据模型和数据库之间存在不匹配,它不想冒险推动插入)

但是,我假设您要在视图模型上指定客户端验证规则,而不是在数据库上指定长度约束。
我希望您明白为什么这是一个令人困惑的设置。

我的建议是拆分您的 viewModel 和模型,以便您可以发布一个未加密密码为 maxlength 20 的 viewModel,您可以将其转换为长度为 100 的模型密码。

如果您觉得太麻烦,您可以创建一个未映射的密码属性,您可以在发布时从 html 设置该密码属性,然后将其转换为控制器中的密码属性。
您的课程可能如下所示:

public class RegisterModel
{
    [Required]
    public string UserName { get; set; }

    [Required]
    [NotMapped]
    [StringLength(20, MinimumLength = 6)]
    [Display(Name = "Password")]
    public string PlainTextPassword { get; set; }

    [Required]
    [StringLength(300)]//This is optional
    [DataType(DataType.Password)]
    public string Password { get; set; }
}

【讨论】:

  • 是的,你理解我的正确。但是,Password 字段的实际最大长度是 300。我想我需要阅读有关 ViewModels 的信息。谢谢
  • 您不需要视图模型本身。 stringLength 属性触发实体框架验证以及客户端验证。因此,如果您说 StringLength 20 您的客户端验证触发器以及提交更改时的实体框架检查。通过使用未映射的属性(EF 不使用),您可以在 PlainTextPassword 上触发客户端验证,但将实体框架绑定在 Password 上。如果需要,可以添加 Stringlength 300。
  • 混合域和 ViewModel 是一种不好的做法。它可能看起来是一个不错且快速的解决方案,但最终它会导致令人头疼的问题,并且领域模型充满了未映射到数据库的属性。特别是当您想在多个视图上使用一个域模型时,但只是有点不同。您可能想阅读this article
  • 恕我直言,如果您的模型可以重用为您的视图模型,您应该这样做以避免开销,复制粘贴属性和映射代码。如果在某些时候它让您感到头疼,您可以轻松地将您的模型重构为视图模型 + 模型。我知道有些人会不同意我的观点,但我认为总是选择模型 + 视图模型 a yagni。应该逐案查看。
  • 同意。只要您了解其中的陷阱,我认为混合域模型和视图模型并不是不好的做法。但是对我来说,只要 View 上的字段比 Model 中的少,或者出现这样的问题 - ViewModel 的时候
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多