【问题标题】:MVC Server-side Validation of RadioButton and DropDownListRadioButton 和 DropDownList 的 MVC 服务器端验证
【发布时间】:2019-10-03 21:46:15
【问题描述】:

使用 ASP.NET Core 2.2 Razor 页面,我正在探索将单选按钮和下拉列表绑定到页面模型。

很多人都在询问客户端验证以“让它发挥作用”。

我的问题是:当我查看这段代码时。绑定引擎是否正在执行任何服务器端检查?

@foreach (var gender in Model.Genders)
{
    <input type="radio" asp-for="Gender" value="@gender" id="Gender@(gender)" /> @gender
}

@Html.DropDownListFor(x => x.Country, new List<SelectListItem>
{
    new SelectListItem() {Text = "Canada", Value="CA"},
    new SelectListItem() {Text = "USA", Value="US"},
    new SelectListItem() {Text = "Mexico", Value="MX"}
})  

是什么阻止了某人发布性别“bababa”和国家/地区“xxx”,这可能会导致我的代码和数据库中出现未定义的行为?

如果上面的代码正在做这样的验证,我会感到惊讶(如果我错了,请纠正我),我找不到相关的帖子,因为每个人都在询问客户端验证。

这里推荐的方法是什么?

【问题讨论】:

    标签: c# .net asp.net-mvc asp.net-core razor


    【解决方案1】:

    服务器端和客户端验证很重要,您总是需要实施服务器端验证,也许您的客户端验证可以省略但服务器端验证永远不会,您发布的代码不执行任何服务器端验证

    【讨论】:

    • 这就是我要说的。发现很多人在谈论单选按钮和下拉列表,但没有任何服务器端验证!什么是正确的方法? CustomValidator 验证列表?然后是我不能将动态列表或对象传递给属性的问题,必须是静态数据。每个使用收音机或下拉菜单的人“应该”有解决方案,对吧?或者那里有很多易受攻击的网站。
    • 发现这个用于枚举验证,它涵盖了一些单选按钮案例。将不得不继续寻找下拉列表。 stackoverflow.com/questions/26425242/…
    【解决方案2】:

    想出了我自己的优雅解决方案,因为我什么都没发现。

    使用下面的辅助类,我将用它来声明我的模型

    [BindProperty]
    public InputList Gender { get; set; } = new InputList(new[] { "Man", "Woman" });
    
    [BindProperty]
    public InputList Country { get; set; } = new InputList(new NameValueCollection()
    {
        { "", "--Select--" },
        { "CA", "Canada" },
        { "US", "USA" },
        { "MX", "Mexico" }
    });
    

    在我的页面上插入单选按钮和下拉列表

    @foreach (var item in Model.Gender.ListItems)
    {
        <input type="radio" asp-for="Gender.Value" value="@item.Value" id="Gender@(item.Value)" /><label for="Gender@(item.Value)" style="padding-right:15px;"> @item.Text </label>
    }
    <span asp-validation-for="Gender" class="text-danger"></span>
    
    @Html.DropDownListFor(x => x.Country.Value, Model.Country.ListItems)
    <span asp-validation-for="Country" class="text-danger"></span>
    

    瞧!验证在客户端和服务器端都有效,确保发布的值有效。

    当然,可以将“Man”和“Woman”移动到常量中,并且可以将国家列表移动到一个单独的类中,为整个应用程序生成一次。

    这是 InputList 帮助器类。

    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel.DataAnnotations;
    using Microsoft.AspNetCore.Mvc.Rendering;
    
    namespace EmergenceGuardian.WebsiteTools.Web
    {
        /// <summary>
        /// Represents a list of items to display as radio buttons or drop down list that can be bound to a web page and validated.
        /// </summary>
        [InputListValidation]
        public class InputList
        {
            /// <summary>
            /// Initializes a new instance of InputList with specified list of items that will be used for both the value and text.
            /// </summary>
            /// <param name="values">A list of string values reprenting valid values.</param>
            /// <param name="required">Whether this field is required.</param>
            public InputList(IEnumerable<string> values, bool required = true)
            {
                Required = required;
                foreach (var item in values)
                {
                    ListItems.Add(new SelectListItem(item, item));
                }
            }
    
            /// <summary>
            /// Initializes a new instance of InputList with specified list of SelectListItem objects.
            /// </summary>
            /// <param name="values">A list of SelectListItem objects representing display text and valid values.</param>
            /// <param name="required">Whether this field is required.</param>
            public InputList(IEnumerable<SelectListItem> values, bool required = true)
            {
                Required = required;
                ListItems.AddRange(values);
            }
    
            /// <summary>
            /// Initializes a new instance of InputList with a NameValueCollection allowing quick collection initializer.
            /// </summary>
            /// <param name="values">The NameValueCollection containing display texts and valid values.</param>
            /// <param name="required">Whether this field is required.</param>
            public InputList(NameValueCollection values, bool required = true)
            {
                Required = required;
                foreach (var key in values.AllKeys)
                {
                    ListItems.Add(new SelectListItem(values[key], key));
                }
            }
    
            /// <summary>
            /// Gets or sets whether this field is required.
            /// </summary>
            public bool Required { get; set; }
            /// <summary>
            /// Gets or sets the list of display text and valid values, used for display and validation.
            /// </summary>
            public List<SelectListItem> ListItems { get; set; } = new List<SelectListItem>();
            /// <summary>
            /// Gets or sets the user input value. This value can be bound to the UI and validated by InputListValidation.
            /// </summary>
            public string Value { get; set; }
        }
    
        /// <summary>
        /// Validates an InputList class to ensure Value is contained in ListItems.
        /// </summary>
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
        sealed public class InputListValidationAttribute : ValidationAttribute
        {
            private const string DefaultErrorMessage = "Selected value is invalid.";
            private const string DefaultRequiredErrorMessage = "The {0} field is required.";
    
            public InputListValidationAttribute()
            {
            }
    
            /// <summary>
            /// Validates whether InputList.Value contains a valid value.
            /// </summary>
            protected override ValidationResult IsValid(object value, ValidationContext validationContext)
            {
                var input = value as InputList;
                if (input != null)
                {
                    if (string.IsNullOrEmpty(input.Value))
                    {
                        if (input.Required)
                        {
                            return new ValidationResult(string.Format(ErrorMessage ?? DefaultRequiredErrorMessage, validationContext.MemberName));
                        }
                    }
                    else if (input.ListItems?.Any(x => x.Value == input.Value) == false)
                    {
                        return new ValidationResult(ErrorMessage ?? DefaultErrorMessage);
                    }
    
                }
                return ValidationResult.Success;
            }
        }
    }
    

    【讨论】:

    • 让模型自己访问持久层并不是最佳实践。作品?当然可以,但是还有其他方法,您可以探索和实现一些更流畅、更容易使用 FluentValidation 实现的东西
    • FluentValidation 可以处理验证部分。对于国家的下拉列表,我仍然需要传递一个 SelectListItem 列表,并且我不想每次使用它时都重复该列表。我认为 FluentValidation 不会对此有所帮助。
    • FluentValidator 无法让我轻松访问包含国家/地区列表的类,我还没有找到“IsInList”方法,到目前为止只会使事情复杂化而没有给出任何明确的解决方案。但是上面的代码也存在问题:您可以绑定到 InputList.Required 并覆盖验证规则!然后是我碰到的这个错误。总的来说,两天后,我仍然没有找到令人满意的解决方案。 github.com/aspnet/AspNetCore/issues/10330
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多