指南的验证

发布于 2024-12-01 08:30:00 字数 225 浏览 1 评论 0原文

我有一个强类型视图,上面有一个 DropDownListFor 属性。

下拉列表中的每个项目都由一个 GUID 表示。

我所追求的是一种验证用户是否从下拉列表中选择项目的方法。目前我还没有看到使用数据注释来做到这一点。

无论如何,是否可以使用数据注释来实现此目的,以便客户端和服务器端验证可以工作。

我猜我需要制定一个自定义方法来执行此操作,但想知道是否已经存在任何东西。

I have a strongly-typed view which has a DropDownListFor attribute on it.

Each item in the dropdown list is represented by a GUID.

What I'm after is a way to validate if a user selects an item from the dropdown list. At present i don't see anyway of doing this using Data Annotations.

Is there anyway of achieving this using Data Annotations so client and server side validation would work.

I'm guessing i need to make a custom method to do this but was wondering if anything already existed.

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

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

发布评论

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

评论(10

难以启齿的温柔 2024-12-08 08:30:00

实际上,您不能将 Required 属性与 GUID 一起使用(没有我下面提到的方法),因为它们继承自 struct,因此它们的默认值实际上是一个实例Guid.Empty,它将满足 Required 属性的要求。话虽这么说,你可以得到你想要的东西,你只需要让你的属性可以为空,以这个为例...

public class Person
{
    [Required] //Only works because the Guid is nullable
    public Guid? PersonId { get; set;}
    public string FirstName { get; set;}
    public string LastName { get; set;}
}

通过将 GUID 标记为可空(使用?,或者如果你更喜欢长的方式,可以使用 Nullable),你可以让当绑定浏览器发送的内容时,它保持为空。在您的情况下,只需确保下拉列表的默认选项的值使用空字符串作为它的值。

编辑:此方法的唯一警告是您最终必须在任何地方使用类似 Person.GetValueOfDefault() 的东西,并可能测试 Guid.Empty。我厌倦了这样做,最终创建了自己的验证属性来帮助简化验证 Guid(以及任何其他具有我想要视为无效的默认值的类型,例如 int、DateTime 等)。 但是我还没有客户端验证来配合此操作,因此验证仅在服务器上进行。 这可以与 [Required] 结合使用(旨在不如果您同意使用可为 null 的类型,则可以使用 [必需] 的重复功能。这意味着您仍然必须使用 GetValueOrDefault(),但至少您不必再测试 Guid.Empty。 Gist 链接有一些带有示例的 XMLDocs,为了简洁起见,我将它们留在这里。我目前正在将它与 ASP.NET Core 一起使用。

编辑:更新以修复 Nullable<> 的错误以及将 null 视为无效的错误。添加了支持类来处理客户端验证。完整代码请参见要点。

要点: RequireNonDefaultAttribute

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class RequireNonDefaultAttribute : ValidationAttribute
{
    public RequireNonDefaultAttribute()
        : base("The {0} field requires a non-default value.")
    {
    }

    public override bool IsValid(object value)
    {
        if (value is null)
            return true; //You can flip this if you want. I wanted leave the responsability of null to RequiredAttribute
        var type = value.GetType();
        return !Equals(value, Activator.CreateInstance(Nullable.GetUnderlyingType(type) ?? type));
    }
}

Actually, you can't use Required attribute with GUIDs (without the method I mention below) because they inherit from struct, and as such their default value is actually an instance of Guid.Empty, which will satisfy the requirements of the Required attribute. Now that being said, it is possible to get what you want you just need to make your property nullable, take this for example...

public class Person
{
    [Required] //Only works because the Guid is nullable
    public Guid? PersonId { get; set;}
    public string FirstName { get; set;}
    public string LastName { get; set;}
}

By marking the GUID nullable (using the ?, or Nullable if you prefer the long way) you let it stay as null when binding against what the browser sent. In your case, just make sure the value of the default option of the dropdown uses an empty string as it's value.

EDIT: The only caveat to this method is you end up having to use something like Person.GetValueOfDefault() everywhere and potentially testing for Guid.Empty. I got tired of doing this and ended up creating my own validation attribute to help simplify validating Guids (and any other types that have default values I want to treat as invalid such as int, DateTime, etc). However I don't have client side validation to go along with this yet, so validation only happens on the server. This can be combined with [Required] (designed to not duplicate functionality of [Required]) if you're ok with using nullable types. This would mean you still have to use GetValueOrDefault(), but at least then you don't have to test for Guid.Empty anymore. The Gist link has some XMLDocs with examples, I left them out here for brevity. I'm currently using it with ASP.NET Core.

EDIT: Updated to fix a bug with Nullable<>, and a bug with treating null as invalid. Added supporting classes to handle client side validation. See Gist for full code.

Gist: RequireNonDefaultAttribute

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class RequireNonDefaultAttribute : ValidationAttribute
{
    public RequireNonDefaultAttribute()
        : base("The {0} field requires a non-default value.")
    {
    }

    public override bool IsValid(object value)
    {
        if (value is null)
            return true; //You can flip this if you want. I wanted leave the responsability of null to RequiredAttribute
        var type = value.GetType();
        return !Equals(value, Activator.CreateInstance(Nullable.GetUnderlyingType(type) ?? type));
    }
}
清旖 2024-12-08 08:30:00

编辑答案

重新阅读您的问题后,听起来您只是想知道是否选择了某个值。如果是这种情况,则只需应用 RequiredAttribute< /code>Guid 属性并使其在模型上可为空,

public class GuidModel
{
    [Required]
    public Guid? Guid { get; set; }

    public IEnumerable<Guid> Guids { get; set; }
}

然后在强类型视图中(使用 @model GuidModel

@Html.ValidationMessageFor(m => m.Guid)
@Html.DropDownListFor(
    m => m.Guid,
    Model.Guids.Select(g => new SelectListItem {Text = g.ToString(), Value = g.ToString()}),
    "-- Select Guid --")

添加客户端验证 JavaScript 脚本参考用于客户端验证。

控制器看起来像这样,

public class GuidsController : Controller
{
    public GuidRepository GuidRepo { get; private set; }

    public GuidsController(GuidRepository guidRepo)
    {
        GuidRepo = guidRepo;
    }

    [HttpGet]
    public ActionResult Edit(int id)
    {
        var guid = GuidRepo.GetForId(id);
        var guids - GuidRepo.All();

        return View(new GuidModel { Guid = guid, Guids = guids });
    }

    [HttpPost]
    public ActionResult Edit(GuidModel model)
    {
        if (!ModelState.IsValid)
        {
            model.Guids = GuidRepo.All();
            return View(model);
        }

        /* update db */

        return RedirectToAction("Edit");
    }
}

这将确保模型绑定的 GuidModel 需要 Guid 属性。

原始答案

我不相信有一个现成的数据注释验证属性能够做到这一点。我写了 关于实现这一目标的一种方法的博客文章;这篇文章使用的是 IoC 容器,但如果您想让某些东西正常工作,您可以采用硬编码的依赖项。

类似于

public class ValidGuidAttribute : ValidationAttribute
{
    private const string DefaultErrorMessage = "'{0}' does not contain a valid guid";

    public ValidGuidAttribute() : base(DefaultErrorMessage)
    {
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var input = Convert.ToString(value, CultureInfo.CurrentCulture);

        // let the Required attribute take care of this validation
        if (string.IsNullOrWhiteSpace(input))
        {
            return null;
        }

        // get all of your guids (assume a repo is being used)
        var guids = new GuidRepository().AllGuids();

        Guid guid;
        if (!Guid.TryParse(input, out guid))
        {
            // not a validstring representation of a guid
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }

        // is the passed guid one we know about?
        return guids.Any(g => g == guid) ?
            new ValidationResult(FormatErrorMessage(validationContext.DisplayName)) : null;
    }
}

然后在您发送到控制器操作的模型上

public class GuidModel
{
    [ValidGuid]
    public Guid guid { get; set; }
}

这将为您提供服务器端验证。您也可以编写客户端验证来执行此操作,也许使用 RemoteAttribute 但在这种情况下我没有看到很多价值,因为唯一会看到此客户端验证的人是正在搞乱DOM 中的值;这对您的普通用户没有任何好处。

Edited Answer

Upon re-reading your question, it sounds like you just want to know if a value is selected. If that's the case then just apply the RequiredAttribute to the Guid property and make it nullable on the model

public class GuidModel
{
    [Required]
    public Guid? Guid { get; set; }

    public IEnumerable<Guid> Guids { get; set; }
}

then in the strongly typed View (with @model GuidModel)

@Html.ValidationMessageFor(m => m.Guid)
@Html.DropDownListFor(
    m => m.Guid,
    Model.Guids.Select(g => new SelectListItem {Text = g.ToString(), Value = g.ToString()}),
    "-- Select Guid --")

Add the client validation JavaScript script references for client-side validation.

The controller looks like

public class GuidsController : Controller
{
    public GuidRepository GuidRepo { get; private set; }

    public GuidsController(GuidRepository guidRepo)
    {
        GuidRepo = guidRepo;
    }

    [HttpGet]
    public ActionResult Edit(int id)
    {
        var guid = GuidRepo.GetForId(id);
        var guids - GuidRepo.All();

        return View(new GuidModel { Guid = guid, Guids = guids });
    }

    [HttpPost]
    public ActionResult Edit(GuidModel model)
    {
        if (!ModelState.IsValid)
        {
            model.Guids = GuidRepo.All();
            return View(model);
        }

        /* update db */

        return RedirectToAction("Edit");
    }
}

This will ensure that the Guid property is required for a model-bound GuidModel.

Original Answer

I don't believe that there is a ready made Data Annotation Validation attribute that is capable of doing this. I wrote a blog post about one way to achieve this; the post is using an IoC container but you could take the hard coded dependency if you're wanting to get something working.

Something like

public class ValidGuidAttribute : ValidationAttribute
{
    private const string DefaultErrorMessage = "'{0}' does not contain a valid guid";

    public ValidGuidAttribute() : base(DefaultErrorMessage)
    {
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var input = Convert.ToString(value, CultureInfo.CurrentCulture);

        // let the Required attribute take care of this validation
        if (string.IsNullOrWhiteSpace(input))
        {
            return null;
        }

        // get all of your guids (assume a repo is being used)
        var guids = new GuidRepository().AllGuids();

        Guid guid;
        if (!Guid.TryParse(input, out guid))
        {
            // not a validstring representation of a guid
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }

        // is the passed guid one we know about?
        return guids.Any(g => g == guid) ?
            new ValidationResult(FormatErrorMessage(validationContext.DisplayName)) : null;
    }
}

and then on the model you send into the controller action

public class GuidModel
{
    [ValidGuid]
    public Guid guid { get; set; }
}

This gives you server side validation. You could write client side validation to do this as well, perhaps using RemoteAttribute but I don't see a lot of value in this case as the only people that are going to see this client side validation are people that are messing with values in the DOM; it would be of no benefit to your normal user.

乙白 2024-12-08 08:30:00

正则表达式确实有效(如果你使用正确的话!)

[Required]
[RegularExpression("^((?!00000000-0000-0000-0000-000000000000).)*$", ErrorMessage = "Cannot use default Guid")]
public Guid Id { get; set; }

Regex actually does work (if you use the right one!)

[Required]
[RegularExpression("^((?!00000000-0000-0000-0000-000000000000).)*
quot;, ErrorMessage = "Cannot use default Guid")]
public Guid Id { get; set; }
ㄖ落Θ余辉 2024-12-08 08:30:00

我知道这是一个老问题了,但如果其他人感兴趣,我可以通过创建 [IsNotEmpty] 注释来解决这个问题(在我的情况下,使 Guid 可为空不是一个选项)。

这使用反射来确定属性上是否有 Empty 的实现,如果有,则进行比较。

public class IsNotEmptyAttribute : ValidationAttribute
{

    public override bool IsValid(object value)
    {

        if (value == null) return false;

        var valueType = value.GetType();
        var emptyField = valueType.GetField("Empty");

        if (emptyField == null) return true;

        var emptyValue = emptyField.GetValue(null);

        return !value.Equals(emptyValue);

    }
}

I know this is an old question now, but if anyone else is interested I managed to get around this by creating an [IsNotEmpty] annotation (making the Guid nullable wasn't an option in my case).

This uses reflection to work out whether there's an implementation of Empty on the property, and if so compares it.

public class IsNotEmptyAttribute : ValidationAttribute
{

    public override bool IsValid(object value)
    {

        if (value == null) return false;

        var valueType = value.GetType();
        var emptyField = valueType.GetField("Empty");

        if (emptyField == null) return true;

        var emptyValue = emptyField.GetValue(null);

        return !value.Equals(emptyValue);

    }
}
§普罗旺斯的薰衣草 2024-12-08 08:30:00

非空引导验证器

阻止 00000000-0000-0000-0000-000000000000

属性:

使用 System.ComponentModel.DataAnnotations;
[AttributeUsage(AttributeTargets.Property)]
内部类 NonEmptyGuidAttribute : ValidationAttribute
{
    受保护覆盖​​ ValidationResult? IsValid(对象?值,ValidationContextvalidationContext)
    {
        if (值为 Guid guid && Guid.Empty == guid)
        {
            return new ValidationResult("Guid 不能为空。");
        }
        返回验证结果.成功;
    }
}

模型:

using System.ComponentModel.DataAnnotations;
public class Material
{
    [Required]
    [NonEmptyGuid]
    public Guid Guid { get; set; }
}

Non Empty Guid Validator

prevents 00000000-0000-0000-0000-000000000000

Attribute:

using System.ComponentModel.DataAnnotations;
[AttributeUsage(AttributeTargets.Property)]
internal class NonEmptyGuidAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        if (value is Guid guid && Guid.Empty == guid)
        {
            return new ValidationResult("Guid cannot be empty.");
        }
        return ValidationResult.Success;
    }
}

Model:

using System.ComponentModel.DataAnnotations;
public class Material
{
    [Required]
    [NonEmptyGuid]
    public Guid Guid { get; set; }
}
花伊自在美 2024-12-08 08:30:00

在 .NET 8 Preview 2 中,您可以在 RequiredAttribute 属性类中将 DisallowAllDefaultValues 设置为 true。

RequiredAttribute 现在允许验证结构不等于其默认值。

[Required(DisallowAllDefaultValues = true)]
public Guid MyGuidValue { get; set; }

有关详细信息:https://devblogs.microsoft .com/dotnet/announcing-dotnet-8-preview-2/#requiredattribute-disallowalldefaultvalues

In .NET 8 Preview 2 you can set the DisallowAllDefaultValues to true in the RequiredAttribute attribute class.

The RequiredAttribute now allows validating that structs do not equal their default values.

[Required(DisallowAllDefaultValues = true)]
public Guid MyGuidValue { get; set; }

For more info: https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-2/#requiredattribute-disallowalldefaultvalues.

相对绾红妆 2024-12-08 08:30:00

如果自定义验证不需要在系统中进行高重用(即不需要自定义验证属性),则还有另一种方法可以将自定义验证添加到 ViewModel / Posted 数据模型,即使用 IValidatableObject

每个错误都可以绑定到一个或多个模型属性,因此这种方法仍然适用于 MVC Razor 中的不引人注目的验证。

以下是检查 Guid 是否默认的方法 (C# 7.1):

public class MyModel : IValidatableObject // Implement IValidatableObject
{
    [Required]
    public string Name {get; set;}
    public Guid SomeGuid {get; set;}
    ... other properties here

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (SomeGuid == default)
        {
            yield return new ValidationResult(
                "SomeGuid must be provided",
                new[] { nameof(SomeGuid) });
        }
    }
 }

有关IValidatableObject 此处的更多信息

If the custom validation doesn't require a high reuse in your system (i.e. without the need for a custom validation attribute), there's another way to add custom validation to a ViewModel / Posted data model, viz by using IValidatableObject.

Each error can be bound to one or more model properties, so this approach still works with e.g. Unobtrusive validation in MVC Razor.

Here's how to check a Guid for default (C# 7.1):

public class MyModel : IValidatableObject // Implement IValidatableObject
{
    [Required]
    public string Name {get; set;}
    public Guid SomeGuid {get; set;}
    ... other properties here

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (SomeGuid == default)
        {
            yield return new ValidationResult(
                "SomeGuid must be provided",
                new[] { nameof(SomeGuid) });
        }
    }
 }

More on IValidatableObject here

独自←快乐 2024-12-08 08:30:00

如果 Guid 包含默认值 - “00000000-0000-0000-0000-000000000000”,您可以验证 Guid。

  if (model.Id == Guid.Empty)
  {
           // TODO: handle the error or do something else
  }

You can validate the Guid if it contains default values - "00000000-0000-0000-0000-000000000000".

  if (model.Id == Guid.Empty)
  {
           // TODO: handle the error or do something else
  }
三五鸿雁 2024-12-08 08:30:00

您可以为此创建一个自定义验证器。

using System;
using System.ComponentModel.DataAnnotations;

namespace {{Your_App_Name}}.Pages
{
    public class NotEmptyGuidAttribute: ValidationAttribute
    {
        protected override ValidationResult IsValid(object guidValue, ValidationContext validationContext)
        {
            var emptyGuid = new Guid();
            var guid = new Guid(guidValue.ToString());
            
            if (guid != emptyGuid){
                return null;
            }
            
            return new ValidationResult(ErrorMessage, new[] {validationContext.MemberName});
        }
    }
}

你可以像这样使用它

[EmptyGuidValidator(ErrorMessage = "Role is required.")]
public Guid MyGuid{ get; set; }

这对我有用。

You can create a custom validator for that.

using System;
using System.ComponentModel.DataAnnotations;

namespace {{Your_App_Name}}.Pages
{
    public class NotEmptyGuidAttribute: ValidationAttribute
    {
        protected override ValidationResult IsValid(object guidValue, ValidationContext validationContext)
        {
            var emptyGuid = new Guid();
            var guid = new Guid(guidValue.ToString());
            
            if (guid != emptyGuid){
                return null;
            }
            
            return new ValidationResult(ErrorMessage, new[] {validationContext.MemberName});
        }
    }
}

You can use it like this

[EmptyGuidValidator(ErrorMessage = "Role is required.")]
public Guid MyGuid{ get; set; }

This worked for me.

故事和酒 2024-12-08 08:30:00

通常 [DeniedValues] 可用于禁止默认值,但不幸的是 Guid 不能是常量,因此它在这里不起作用。

相反,这里有一个自定义属性,它基于废弃的 RequiredAttribute DisallowAllDefaultValues 属性中的代码,拒绝所有默认值。

这比其他答案更灵活,因为它适用于任何类型,而不仅仅是 GUID。

/// <summary>
///     Specifies that default values should not be allowed in a property.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
    AllowMultiple = false)]
public class DenyDefaultValuesAttribute : ValidationAttribute
{
    /// <summary>
    ///     Initializes a new instance of the <see cref="DenyDefaultValuesAttribute"/> class.
    /// </summary>
    public DenyDefaultValuesAttribute()
    {
    }

    public override bool IsValid(object? value)
        => IsValidCore(value, validationContext: null);

    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        return IsValidCore(value, validationContext)
            ? ValidationResult.Success
            : CreateFailedValidationResult(validationContext);
    }

    private protected ValidationResult CreateFailedValidationResult(ValidationContext validationContext)
    {
        string[]? memberNames = validationContext.MemberName is { } memberName
            ? new[] { memberName }
            : null;

        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), memberNames);
    }

    private bool IsValidCore(object? value, ValidationContext? validationContext)
    {
        if (value is null)
        {
            return false;
        }

        Type valueType = validationContext?.ObjectType ?? value.GetType();
        if (GetDefaultValueForNonNullableValueType(valueType) is object defaultValue)
        {
            return !defaultValue.Equals(value);
        }

        return true;
    }

    private object? GetDefaultValueForNonNullableValueType(Type type)
    {
        object? defaultValue = _defaultValueCache;

        if (defaultValue != null && defaultValue.GetType() == type)
        {
            Debug.Assert(type.IsValueType && Nullable.GetUnderlyingType(type) is null);
        }
        else if (type.IsValueType && Nullable.GetUnderlyingType(type) is null)
        {
            defaultValue = RuntimeHelpers.GetUninitializedObject(type);
            _defaultValueCache = defaultValue;
        }
        else
        {
            defaultValue = null;
        }

        return defaultValue;
    }

    private object? _defaultValueCache;
}

Usually [DeniedValues] could be used to disallow the default value, but unfortunately Guids can't be constant, so it won't work here.

Instead, here's a custom attribute that denies all default values, based on code from the scrapped RequiredAttribute DisallowAllDefaultValues property.

This is more flexible than the other answers since it will work for any type, not just GUIDs.

/// <summary>
///     Specifies that default values should not be allowed in a property.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
    AllowMultiple = false)]
public class DenyDefaultValuesAttribute : ValidationAttribute
{
    /// <summary>
    ///     Initializes a new instance of the <see cref="DenyDefaultValuesAttribute"/> class.
    /// </summary>
    public DenyDefaultValuesAttribute()
    {
    }

    public override bool IsValid(object? value)
        => IsValidCore(value, validationContext: null);

    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        return IsValidCore(value, validationContext)
            ? ValidationResult.Success
            : CreateFailedValidationResult(validationContext);
    }

    private protected ValidationResult CreateFailedValidationResult(ValidationContext validationContext)
    {
        string[]? memberNames = validationContext.MemberName is { } memberName
            ? new[] { memberName }
            : null;

        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), memberNames);
    }

    private bool IsValidCore(object? value, ValidationContext? validationContext)
    {
        if (value is null)
        {
            return false;
        }

        Type valueType = validationContext?.ObjectType ?? value.GetType();
        if (GetDefaultValueForNonNullableValueType(valueType) is object defaultValue)
        {
            return !defaultValue.Equals(value);
        }

        return true;
    }

    private object? GetDefaultValueForNonNullableValueType(Type type)
    {
        object? defaultValue = _defaultValueCache;

        if (defaultValue != null && defaultValue.GetType() == type)
        {
            Debug.Assert(type.IsValueType && Nullable.GetUnderlyingType(type) is null);
        }
        else if (type.IsValueType && Nullable.GetUnderlyingType(type) is null)
        {
            defaultValue = RuntimeHelpers.GetUninitializedObject(type);
            _defaultValueCache = defaultValue;
        }
        else
        {
            defaultValue = null;
        }

        return defaultValue;
    }

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