ASP.NET MVC:数据注释验证是否足够?

时间:2009-10-13 06:17:57

标签: asp.net-mvc validation asp.net-mvc-2 data-annotations

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

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

后续评论 根据以下答案,似乎我不能单独依赖Model Binder和Data Annotation验证。由于我们得出结论认为需要额外的服务器端验证,我的服务层是否有一种简单的方法可以根据数据注释中定义的内容触发验证?似乎这将使我们两个词中最好的...我们不需要重复验证代码,但我们仍然会确保即使Model Binder没有触发它也会执行验证。

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

5 个答案:

答案 0 :(得分:18)

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

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

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

答案 1 :(得分:7)

我将xVal与DataAnnotations配对,并编写了我自己的Action过滤器,用于检查任何实体类型参数以进行验证。因此,如果回发中缺少某个字段,则此验证程序将填充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();
    }
    ...
}

答案 2 :(得分:2)

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

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

<强>更新 获取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)) {

答案 3 :(得分: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不走路 用于创建客户端的属性 验证,所以你可能想做 一些变化,以获得更强大 客户端验证。

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

答案 4 :(得分:1)

请参阅codeProject Server-side Input Validation using Data Annotations

  

输入验证可以在客户端自动完成   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);