【问题标题】:Is it correct way to use ModelState.Remove to deal with ModelState?使用 ModelState.Remove 处理 ModelState 是否正确?
【发布时间】:2011-10-14 03:56:20
【问题描述】:

我正在开发一个大型 MVC3 Web 应用程序,并且对 ModelState.IsValid 方法感到烦恼。

ModelState 几乎在我的所有控制器中都被使用,以验证发布的数据。 这些视图都基于包含不同类的 ViewModel,这些类显然包含可以标记为[Required] 的属性。

我遇到的问题是有时不需要所需的属性,我不得不使用ModelState.Remove 方法,以便ModelState.IsValid 变为真。

我的问题是使用ModelState.Remove,这是正确的做事方式还是有更有效的方法。

【问题讨论】:

标签: asp.net-mvc asp.net-mvc-3 modelstate


【解决方案1】:

如果您的财产并非总是需要,则不应使用[Required] 来装饰它。

进行验证的一个很好的替代方法是实现接口IValidatableObject

例如,假设您想让字段State 仅在国家/地区为United States 时才需要。你可以这样做:

public class AddressModel : IValidatableObject
{
    [Required]
    public string Country { get; set; }
    public string State { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if(Country == "United States" && String.IsNullOrEmpty(State))
        {
            yield return new ValidationResult("State is required for United States", new [] { nameof(State) });
        }
    }
}

注意:这种验证只适用于服务器端。

其他选择?

正如其他答案中提到的,如果视图和验证非常不同,有时创建 2 个或更多模型是个好主意。

【讨论】:

    【解决方案2】:

    这是我的解决方案 - ModelState 上的 RemoveFor() 扩展方法,模仿 MVC HTML 助手:

        public static void RemoveFor<TModel>(this ModelStateDictionary modelState, 
                                             Expression<Func<TModel, object>> expression)
        {
            string expressionText = ExpressionHelper.GetExpressionText(expression);
    
            foreach (var ms in modelState.ToArray())
            {
                if (ms.Key.StartsWith(expressionText + ".") || ms.Key == expressionText)
                {
                    modelState.Remove(ms);
                }
            }
        }
    

    这是它的使用方法:

    if (model.CheckoutModel.ShipToBillingAddress == true) 
    {
        // REUSE BILLING ADDRESS FOR SHIPPING ADDRESS
        ShoppingCart.ShippingAddress = ShoppingCart.BillingAddress;
    
        // REMOVE MODELSTATE ERRORS FOR SHIPPING ADDRESS
        ModelState.RemoveFor<SinglePageStoreModel>(x => model.CheckoutModel.ShippingAddress);
    }
    

    因此,在回答您的问题时,我相信在某些用例中这是正确的方法,并且像这样的强类型帮助器使它看起来更好看 - 并且更容易证明您是担心很多魔术字符串。

    【讨论】:

    • 你的“解决方案”太复杂了。
    • @Rookian 复杂在哪方面?您根本不喜欢删除 ModelState 的想法 - 或者您对 ModelState.Remove("CheckoutModel.ShippingAddress.City") 之类的代码没有问题,并且认为不需要这样做。我发现这是一种更安全的做事方式,如果我更改我在编译时发现的东西的名称。我一直讨厌必须删除模型状态,但有时你只需要这样做,而且我对这种方法更满意。好奇你更详细的意见是什么
    • 在我看来,您在错误的地方解决了问题。使解决方案变得比应有的困难。使用史蒂夫摩根已经提到的不同的输入模型(视图模型)。
    • 我喜欢这个解决方案。我经常用我不想在视图中显示或输入的 CreatedBy、CreatedDate、LastUpdatedBy 和 LastUpdatedDate 来装饰我的类和数据。这让我不必制作数十个视图模型并处理整个来回切换对象类型。谢谢。
    • 我喜欢这个解决方案,我必须往返一个模型被发布回来查看,这让我的生活更轻松。我发现了一个我认为的错误,我正在删除一个数组字段。以“.”结尾的匹配失败,所以我在 if 条件中添加了ms.Key.StartsWith(expressionText + "[")
    【解决方案3】:

    我完全支持史蒂夫·摩根先生

    因此,如果您的 ViewModel 并不总是需要某些属性为 Required,那么您不应该将其装饰为必需。

    我不知道你为什么想要这个问题,但我想在某些情况下,如果 PropertyTwo 有价值,你需要 PropertyOne 成为 Required 在这种情况下,您可能需要使用 CustomValidationAttribute 来检查这两个属性。

    我正在使用这样的东西:

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
    public class PropertyNeededAttribute : ValidationAttribute
    {
        private const string defaultErrorMessage = "'{0}' needs '{1}' to be valid.";
    
        public PropertyNeededAttribute(string originalProperty, string neededProperty)
            : base(defaultErrorMessage)
        {
            NeededProperty = neededProperty;
            OriginalProperty = originalProperty;
        }
    
        public string NeededProperty { get; private set; }
        public string OriginalProperty { get; private set; }
    
        public override object TypeId
        {
            get { return new object(); }
        }
    
        public override string FormatErrorMessage(string name)
        {
            return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
                                 OriginalProperty, NeededProperty);
        }
    
        public override bool IsValid(object value)
        {
            object neededValue = Statics.GetPropertyValue(value, NeededProperty);
            object originalValue = Statics.GetPropertyValue(value, OriginalProperty);
            if (originalValue != null && neededValue == null)
                return false;
            return true;
        }
    }
    

    注意:Statics.GetPropertyValue(...) 什么都不做,只是从属性中获取值进行比较。

    希望这有帮助:)

    【讨论】:

      【解决方案4】:

      从根本上说,您的问题是,虽然您的课程用 [Required] 装饰,但并非总是如此。如果您在不正确的上下文中进行操作,那么您确实应该使用未将属性定义为 [必需] 的类。

      您应该真正使用针对其特定用途正确定义的 ViewModel,这可能意味着复制某些类。 ViewModel 与 UI 的实现相关联,虽然它可能使用您的领域模型中的类,但这并不总是正确的做法。

      否则,选项是不使用 ModelState.IsValid,或者继续使用 ModelState.Remove。

      但从逻辑上讲,您的 ViewModel 是“可验证的”是有意义的,而不必忽略某些验证错误。

      【讨论】:

      • 不需要单独的视图模型,只是不要为此使用基于属性的验证。使用能够准确表达此逻辑并有条件地为模型状态添加错误的东西。
      • 需要一个单独的视图模型,因为它允许两个模型在视图更改时相互独立地更改,而不必担心更改两个位置。
      • 我想我是在几年前回复的——但我真正指的是AddressModel,如果 countryCd 是爱尔兰,则可能不需要Zip。我今天仍在使用这种方法,如果国家不需要,我会从 modelstate 中删除 zip 错误。这是一个无法使用单独视图模型的示例。
      【解决方案5】:

      如果您在两种不同的上下文中使用具有 [Required] 属性的同一视图模型,一种是需要该属性,另一种是不需要该属性,那么您需要手动将 ModelState 更改为你在做。

      另一种方法是使用不同的视图模型。可能有一个基类,其中包含所有属性,但有问题的必需属性除外。然后从中派生两个视图模型,一个具有需要的属性,一个具有不需要的属性(我知道这是重复的)。您可以决定将它们完全分开而不使用继承。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-10-26
        • 2021-10-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-23
        • 1970-01-01
        相关资源
        最近更新 更多