使用 DataAnnotations 有条件地验证 ASP.NET MVC 模型的某些部分?

发布于 2024-08-03 12:28:41 字数 504 浏览 7 评论 0 原文

我的页面上有某些面板在某些情况下会隐藏。

例如,我可能有“帐单地址”和“送货地址”,但如果选中“ShippingSameAsBilling”复选框,我不想验证“送货地址”。

我正在尝试使用新的 DataAnnotations 功能可实现此目的。

我需要在“送货地址”未显示时阻止对其进行验证,并且需要找到实现此目的的方法。我主要谈论的是服务器端,而不是使用 jquery

我怎样才能实现这个目标?我有几个与自定义模型绑定相关的想法,但我当前的最佳解决方案如下。对这个方法有什么反馈吗?

I have certain panels on my page that are hidden under certain circumstances.

For instance I might have a 'billing address' and 'shipping address' and I dont want to validate 'shipping address' if a 'ShippingSameAsBilling' checkbox is checked.

I am trying to use the new DataAnnotations capabilities of ASP.NET MVC 2 (preview 1) to achieve this.

I need to prevent validation of the 'shipping address' when it is not displayed and need to find the way way to achieve this. I am talking mainly server side as opposed to by using jquery.

How can I achieve this? I have had several ideas, related to custom model binding but my current best solution is below. Any feedback on this method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

黯然#的苍凉 2024-08-10 12:28:41

对于 CheckoutModel,我使用这种方法(大多数字段隐藏):

[ModelBinder(typeof(CheckoutModelBinder))]
public class CheckoutModel : ShoppingCartModel
{        
    public Address BillingAddress { get; set; }
    public Address ShippingAddress { get; set; }
    public bool ShipToBillingAddress { get; set; }
}

public class Address
{
    [Required(ErrorMessage = "Email is required")]
    public string Email { get; set; }

    [Required(ErrorMessage = "First name is required")]
    public string FirstName { get; set; }

    [Required()]
    public string LastName { get; set; }

    [Required()]
    public string Address1 { get; set; }
}

自定义模型绑定器会删除以“ShippingAddress”开头的字段的所有 ModelState 错误(如果找到)。然后“TryUpdateModel()”将返回 true。

    public class CheckoutModelBinder : DefaultModelBinder
    {
        protected override void OnModelUpdated(ControllerContext controllerContext,
                                               ModelBindingContext bindingContext) {

            base.OnModelUpdated(controllerContext, bindingContext);

            var model = (CheckoutModel)bindingContext.Model;

            // if user specified Shipping and Billing are the same then 
            // remove all ModelState errors for ShippingAddress
            if (model.ShipToBillingAddress)
            {
                var keys = bindingContext.ModelState.Where(x => x.Key.StartsWith("ShippingAddress")).Select(x => x.Key).ToList();
                foreach (var key in keys)
                {
                    bindingContext.ModelState.Remove(key);
                }
            }
        }    
    }

还有更好的解决方案吗?

For the CheckoutModel I am using this approach (most fields hidden):

[ModelBinder(typeof(CheckoutModelBinder))]
public class CheckoutModel : ShoppingCartModel
{        
    public Address BillingAddress { get; set; }
    public Address ShippingAddress { get; set; }
    public bool ShipToBillingAddress { get; set; }
}

public class Address
{
    [Required(ErrorMessage = "Email is required")]
    public string Email { get; set; }

    [Required(ErrorMessage = "First name is required")]
    public string FirstName { get; set; }

    [Required()]
    public string LastName { get; set; }

    [Required()]
    public string Address1 { get; set; }
}

The custom model binder removes all ModelState errors for fields beginning with 'ShippingAddress' if it finds any. Then 'TryUpdateModel()' will return true.

    public class CheckoutModelBinder : DefaultModelBinder
    {
        protected override void OnModelUpdated(ControllerContext controllerContext,
                                               ModelBindingContext bindingContext) {

            base.OnModelUpdated(controllerContext, bindingContext);

            var model = (CheckoutModel)bindingContext.Model;

            // if user specified Shipping and Billing are the same then 
            // remove all ModelState errors for ShippingAddress
            if (model.ShipToBillingAddress)
            {
                var keys = bindingContext.ModelState.Where(x => x.Key.StartsWith("ShippingAddress")).Select(x => x.Key).ToList();
                foreach (var key in keys)
                {
                    bindingContext.ModelState.Remove(key);
                }
            }
        }    
    }

Any better solutions?

注定孤独终老 2024-08-10 12:28:41

我可以看到你的困境。我正在寻找其他验证解决方案,涉及复杂的验证规则,这些规则可能适用于给定模型对象上的多个属性,甚至可能适用于对象图中不同模型对象的许多属性(如果您不幸要验证链接对象)像这样)。

IDataErrorInfo 接口的限制是,当所有属性都没有错误时,模型对象就满足有效状态。这就是说,有效对象是其所有属性也有效的对象。但是,我可能会遇到这样的情况:如果属性 A、B 和 C 有效,则整个对象有效。但是如果属性 A 无效但 B > C 是,则该对象满足有效性。我根本无法使用 IDataErrorInfo 接口/DataAnnotations 属性来描述此条件/规则。

所以我找到了这个委托方法。现在,MVC 中的许多有用的进步在撰写本文时还不存在,但核心概念应该对您有所帮助。我们不使用属性来定义对象的验证条件,而是创建委托函数来验证更复杂的需求,并且因为它们是委托的,所以我们可以重用它们。当然,这需要更多工作,但是委托的使用意味着我们应该能够编写验证规则代码一次并且将所有验证规则存储在一个地方(可能是服务层)并且(最酷的一点)甚至使用 MVC 2 DefaultModelBinder 自动调用验证(无需大量检查我们的控制器操作 - 就像 Scott 的博客所说,我们可以使用 DataAnnotations > 请参阅 “强类型 UI 助手”标题之前的最后一段)!

我确信您可以使用 FuncPredicate 之类的匿名委托来增强上面文章中建议的方法,并为以下内容编写自定义代码块:验证规则将启用跨属性条件(例如,您提到的条件,如果您的 ShippingSameAsBilling 属性为 true,那么您可以忽略送货地址的更多规则等)。

DataAnnotations 用于使用很少的代码使对象的简单验证规则非常简单。但随着您的需求的发展,您将需要验证更复杂的规则。 MVC2 模型绑定器中的新虚拟方法应该继续为我们提供将未来的验证发明集成到 MVC 框架中的方法。

I can see your predicament. I'm looking for other validation solutions also with regard to complex validation rules that might apply to more than one property on a given model object or even many properties from different model objects in a object graph (if your unlucky enough to be validating linked objects like this).

The limitation of the IDataErrorInfo interface is that a model object satisfies the valid state simply when none of the properties have errors. This is to say that a valid object is one where all of it's properties are also valid. However, i may have a situation where if property A, B and C are valid - then the whole object is valid.. but also if property A is not valid but B and C are, then the object satisfies validity. I simply have no way of describing this condition/rule with the IDataErrorInfo interface / DataAnnotations attributes.

So i found this delegate approach. Now many of the helpful advancements in MVC didn't exist at the time of writing this article but the core concept should help you. Rather than using attributes to define the validation conditions of an object we create delegate functions that validate more complex requirements and because they're delegated we can re-use them. Sure it's more work, but the use of delegates means that we should be able to write validation rule code once and store all the validation rules in the one place (maybe service layer) and (the kool bit) even use the MVC 2 DefaultModelBinder to invoke the validation automatically (without heaps of checking in our controller actions - like Scott's blog says we can do with DataAnnotations. Refer to the last paragraph before the 'Strongly Typed UI Helpers' heading)!

I'm sure you can beef the approach suggested in the above article up a little with anonymous delegates like Func<T> or Predicate<T> and writing custom code blocks for the validation rules will enable cross-property conditions (for example the condition you referred to where if your ShippingSameAsBilling property is true then you can ignore more rules for the shipping address, etc).

DataAnnotations serves to make simple validation rules on objects really easy with very little code. But as your requirements develop you will need to validate on more complex rules. The new virtual methods in the MVC2 model binder should continue to provide us with ways of integrating our future validation inventions into the MVC framework.

风透绣罗衣 2024-08-10 12:28:41

确保您不希望验证的字段不会发布到操作中。我们仅验证实际发布的字段。

编辑:(由提问者)

此行为在 MVC2 RC2 中已更改:

默认验证系统验证
整个模型默认验证
ASP.NET MVC 1.0 和中的系统
RC 之前的 ASP.NET MVC 2 预览
2 仅验证模型属性
已发布到服务器。在 ASP.NET 中
MVC 2,新的行为是所有
模型属性在以下情况下得到验证:
该模型经过验证,无论
是否发布了新值。
依赖于的应用程序
ASP.NET MVC 1.0 行为可能需要
变化。欲了解更多信息
此更改,请参阅条目 输入
验证与模型验证

Brad Wilson 博客上的 ASP.NET MVC。

Make sure the fields you don't want validated are not posted to the action. We only validate the fields that were actually posted.

Edit: (by questioner)

This behavior has changed in MVC2 RC2 :

Default validation system validates
entire model The default validation
system in ASP.NET MVC 1.0 and in
previews of ASP.NET MVC 2 prior to RC
2 validated only model properties that
were posted to the server. In ASP.NET
MVC 2, the new behavior is that all
model properties are validated when
the model is validated, regardless of
whether a new value was posted.
Applications that depend on the
ASP.NET MVC 1.0 behavior may require
changes. For more information about
this change, see the entry Input
Validation vs. Model Validation
in
ASP.NET MVC on Brad Wilson’s blog.

孤星 2024-08-10 12:28:41

对于更复杂的情况,我从简单的 DataAnnotations 转向以下内容: 对访问者和扩展方法进行验证

如果您想使用 DataAnnotations,您可以替换如下内容:

public IEnumerable<ErrorInfo> BrokenRules (Payment payment)
{   
    // snip... 
    if (string.IsNullOrEmpty (payment.CCName))
    {
      yield return new ErrorInfo ("CCName", "Credit card name is required");
    }
}

使用通过 DataAnnotations 按名称验证属性的方法(我没有 atm)。

For the more complex cases I moved away from simple DataAnnotations to the following: Validation with visitors and extension methods.

If you want to make use of your DataAnnotations you would replace something like the following:

public IEnumerable<ErrorInfo> BrokenRules (Payment payment)
{   
    // snip... 
    if (string.IsNullOrEmpty (payment.CCName))
    {
      yield return new ErrorInfo ("CCName", "Credit card name is required");
    }
}

with a method to validate a property by name via DataAnnotations (which I don't have atm).

美人迟暮 2024-08-10 12:28:41

我创建了一个部分模型绑定器,仅验证提交的密钥。出于安全原因(如果我要更进一步),我将创建一个数据注释属性来标记允许从模型中排除哪些字段。然后,OnModelUpdated 检查字段属性以确保没有发生不需要的低发布情况。

public class PartialModelBinder : DefaultModelBinder
{
    protected override void OnModelUpdated(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        // default model binding to get errors
        base.OnModelUpdated(controllerContext, bindingContext);

        // remove errors from filds not posted
        // TODO: include request files
        var postedKeys = controllerContext.HttpContext.Request.Form.AllKeys;
        var unpostedKeysWithErrors = bindingContext.ModelState
            .Where(i => !postedKeys.Contains(i.Key))
            .Select(i=> i.Key).ToList();
        foreach (var key in unpostedKeysWithErrors)
        {
            bindingContext.ModelState.Remove(key);
        }
    }    
}

I created a partial model binder that only validates the keys that were submitted. For security reasons (if I was going to take this a step farther) I'd create a data annotation attribute that marks which fields are allowed to be excluded from a model. Then, OnModelUpdated check field attributes to ensure there is no undesired underposting going on.

public class PartialModelBinder : DefaultModelBinder
{
    protected override void OnModelUpdated(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        // default model binding to get errors
        base.OnModelUpdated(controllerContext, bindingContext);

        // remove errors from filds not posted
        // TODO: include request files
        var postedKeys = controllerContext.HttpContext.Request.Form.AllKeys;
        var unpostedKeysWithErrors = bindingContext.ModelState
            .Where(i => !postedKeys.Contains(i.Key))
            .Select(i=> i.Key).ToList();
        foreach (var key in unpostedKeysWithErrors)
        {
            bindingContext.ModelState.Remove(key);
        }
    }    
}
风为裳 2024-08-10 12:28:41

这与DataAnnotations无关,但您是否看过Fluent Validation项目?它使您可以对验证进行细粒度控制,如果您进行对象到对象验证,则两个对象的聚合对象将帮助您继续进行。

而且它似乎是在考虑到 MVC 的情况下构建的,但它也有自己的“运行时”,因此您也可以在其他 .NET 应用程序中使用它,这是我书中的另一个好处。

This isn't related to DataAnnotations but have you looked at the Fluent Validation project? It gives you fine grain control over your validation and if you have object-to-object validation an aggregate object of the two objects will get you going.

Also it seems to have been build with MVC in mind but it also has its own "runtime" so that you can use it in other .NET applications as well which is another bonus in my book.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文