27

我在 ASP.NET MVC 2 中广泛使用 Data Annotation 验证。这个新功能节省了大量时间,因为我现在能够在一个地方定义客户端验证和服务器端验证。然而,当我做一些详细的测试时,我意识到如果我只依赖数据注释验证,那么有人很容易绕过服务器端验证。例如,如果我通过使用 [Required] 属性注释属性来定义一个必填字段,并在表单中为该必填字段放置一个文本框,用户可以简单地从 DOM 中删除该文本框(这可以通过 Firebug 轻松完成)现在,在 Controller 内部的 ModelBinding 期间,不会在该属性上触发 Data Annotation 验证。为确保触发“必需”验证,

大家对验证有什么建议?数据注释验证是否足够?或者是否需要重复验证以确保在所有情况下都会触发验证?

后续评论: 根据下面的答案,我似乎不能单独依赖模型绑定器和数据注释验证。由于我们得出的结论是需要额外的服务器端验证,我的服务层是否有一种简单的方法可以根据数据注释中定义的内容触发验证?似乎这将使我们两全其美……我们不需要重复验证代码,但即使 Model Binder 没有触发验证,我们仍将确保执行验证。

我将把这个后续评论作为一个单独的问题发布,因为它提出的问题与原来的问题不同。

4

5 回答 5

18

我认为要对安全性保持警惕,您应该选择将服务器验证作为优先事项,并确保这始终是您的后备方案。您的服务器验证应该在没有客户端验证的情况下工作。客户端验证更多地用于 UX,尽管这对您的设计至关重要,但它仅次于安全性。考虑到这一点,您会发现自己在重复您的验证。一个目标通常是尝试设计您的应用程序,以便尽可能集成服务器和客户端验证,以减少在服务器和客户端上进行验证所需的工作。但请放心,您必须两者都做。

如果绕过客户端验证(通过 DOM 操作)正在避免服务器验证(您似乎在指示),那么您对该实例的服务器验证可能无法正确使用。您应该在控制器操作或服务层中再次调用服务器验证。您描述的场景不应该破坏您的服务器验证。

对于您描述的场景,DataAnnotation 属性方法应该足够了。您似乎只需要进行一些代码更改,以确保在提交表单时也调用您的服务器验证。

于 2009-10-13T08:00:07.837 回答
7

我将 xVal 与 DataAnnotations 配对,并编写了自己的操作过滤器,用于检查任何实体类型参数以进行验证。因此,如果回发中缺少某些字段,则此验证器将填充 ModelState 字典,从而使模型无效。

先决条件:

  • 我的实体/模型对象都实现IObjectValidator了声明Validate()方法的接口。
  • 我的属性类被称为ValidateBusinessObjectAttribute
  • xVal 验证库

动作过滤器代码:

public void OnActionExecuting(ActionExecutingContext filterContext)
{
    IEnumerable<KeyValuePair<string, object>> parameters = filterContext.ActionParameters.Where<KeyValuePair<string, object>>(p => p.Value.GetType().Equals(this.ObjectType ?? p.Value.GetType()) && p.Value is IObjectValidator);
    foreach (KeyValuePair<string, object> param in parameters)
    {
        object value;
        if ((value = param.Value) != null)
        {
            IEnumerable<ErrorInfo> errors = ((IObjectValidator)value).Validate();
            if (errors.Any())
            {
                new RulesException(errors).AddModelStateErrors(filterContext.Controller.ViewData.ModelState, param.Key);
            }
        }
    }
}

我的控制器动作是这样定义的:

[ValidateBusinessObject]
public ActionResult Register(User user, Company company, RegistrationData registrationData)
{
    if (!this.ModelState.IsValid)
    {
        return View();
    }
    ...
}
于 2009-10-13T13:32:49.983 回答
2

DataAnnotation 肯定是不够的。我还广泛使用它来预先验证我对域模型的调用,以获得更好的错误报告并尽早失败。

但是,您可以自己调整 DataAnnotation 模型,以确保必须发布带有 [Required] 的属性。(将在今天晚些时候跟进代码)。

更新 获取 DataAnnotations Model Binder 的源并在 DataAnnotationsModelBinder.cs 中找到此行

// Only bind properties that are part of the request
if (bindingContext.ValueProvider.DoesAnyKeyHavePrefix(fullPropertyKey)) {

将其更改为

// Only bind properties that are part of the request
bool contextHasKey = bindingContext.ValueProvider.DoesAnyKeyHavePrefix(fullPropertyKey);
bool isRequired = GetValidationAttributes(propertyDescriptor).OfType<RequiredAttribute>().Count() > 0;
if (contextHasKey || (!contextHasKey && isRequired)) {
于 2009-10-13T06:44:40.313 回答
2

我通过从 xVal 的 DataAnnotationsRuleProvider 和 Microsoft 的 DataAnnotationsModelBinder(以及 Martijn 的评论)复制模式,为 MVC 1.0 编写了自己的 ValidationService。服务接口如下:

public interface IValidationService
{
    void Validate(object instance);

    IEnumerable<ErrorInfo> GetErrors(object instance);
}

public abstract class BaseValidationService : IValidationService
{
    public void Validate(object instance)
    {
        var errors = GetErrors(instance);

        if (errors.Any())
            throw new RulesException(errors);
    }

    public abstract IEnumerable<ErrorInfo> GetErrors(object instance);
}

该服务是一个验证运行程序,它遍历它接收到的对象实例的属性树,并实际执行它在每个属性上找到的验证属性,当属性无效时构建一个 ErrorInfo 对象列表。(我会发布整个源代码,但它是为客户编写的,我还不知道我是否被授权这样做。)

然后,您可以让您的控制器、业务逻辑服务在您准备好时显式调用验证,而不是完全依赖模型绑定器进行验证。

您还应该注意其他几个陷阱:

  • 数据注释中的默认 DataTypeAttribute 实际上并不进行任何数据类型验证,因此您需要编写一个实际使用 xVal 正则表达式(或其他内容)的新属性来执行服务器端数据类型验证。
  • xVal 不会遍历属性来创建客户端验证,因此您可能希望在此处进行一些更改以获得更强大的客户端验证。

如果我被允许并且有时间,我会尝试提供更多资源......

于 2009-11-04T16:04:22.007 回答
2

请参阅使用数据注释的代码项目服务器端输入验证

输入验证可以在 ASP.NET MVC 的客户端自动完成,也可以根据规则显式验证模型。本技巧将描述如何在 ASP.NET 应用程序的服务器端或 WPF 应用程序的存储库代码中手动完成。

        // Use the ValidationContext to validate the Product model against the product data annotations
        // before saving it to the database
        var validationContext = new ValidationContext(productViewModel, serviceProvider: null, items:null);
        var validationResults = new List<ValidationResult>();

        var isValid = Validator.TryValidateObject(productViewModel, validationContext,validationResults, true);
于 2012-12-06T21:04:42.087 回答