Guid的验证

24
我有一个强类型视图,其中有DropDownListFor属性。
下拉列表中的每个项目都由GUID表示。
我想要的是一种方法来验证用户是否从下拉列表中选择了项目。目前,我看不到使用数据注释来实现这一点的任何方式。
是否有任何方法可以使用数据注释来实现这一点,以便客户端和服务器端验证将起作用。
我猜我需要编写一个自定义方法来实现这一点,但想知道是否已经存在任何方法。
9个回答

32
事实上,你不能在GUID中使用Required属性(除非使用我下面提到的方法),因为它们继承自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]结合使用(旨在不重复[Required]的功能)。这意味着您仍然必须使用GetValueOrDefault(),但至少您不再需要测试Guid.Empty了。 Gist链接中有一些包含示例的XMLDocs,我在此处留下了它们以节省篇幅。我目前正在使用它与ASP.NET Core一起。

编辑:更新以修复Nullable<>的错误和将null视为无效的错误。添加支持类以处理客户端验证。查看Gist获取完整代码。

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));
    }
}

我尝试在模型中使用您的代码,其中包含以下属性。如果我提供空值,模型将失败。根据摘要中的示例,我认为null是可以的,但不是0。请确认一下。[RequiredNonDefault] public int? Test { get; set; } - Krishna
这是因为你的类型是int?。任何Nullable<>对象的默认值都是null,因此null不是有效值,但0是。但是你正确指出我的示例表明了相反的情况。我会解决这个问题。为了让你所描述的功能起作用(允许null但不允许0),你最好使用类似于[Range(1, int.MaxValue)] int? i {get;set;}这样的代码。 - Nick Albrecht
@Albercht 谢谢您的确认。 - Krishna

14

编辑的答案

重新阅读您的问题后,听起来您只是想知道一个值是否被选中。如果是这样,那么只需在Guid属性上应用RequiredAttribute并在模型上使其可为空。

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");
    }
}

这将确保对于模型绑定的GuidModelGuid属性是必需的。 原始回答 我认为没有现成的数据注释验证属性能够实现这一点。我写了一篇关于如何实现这个目标的博客文章; 这篇文章使用了一个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 中更改值的人;对于普通用户没有任何好处。

1
除非将Guid属性标记为可空,否则实际上这不起作用,请参见我的答案。 - Nick Albrecht

7

我知道这是一个旧问题,但如果有其他人感兴趣,我设法通过创建一个[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);

    }
}

7

如果您使用正确的正则表达式,它确实是有效的!

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

为什么上面的不同作者声称这个解决方案不起作用? - Judy007
当我尝试使用他/她的正则表达式时,它对我无效。我记不清原因了,但我不得不进行一些更改,现在这个有效。 - Eric Longstreet
太棒了。这比创建自定义验证器要优雅得多。 - undefined

5

非空 GUID 验证器

防止出现 00000000-0000-0000-0000-000000000000

属性:

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

模型:

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

2

如果Guid包含默认值-"00000000-0000-0000-0000-000000000000",则可以验证该值。

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

2
您可以为此创建自定义验证器。
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; }

这对我很有帮助。

2
如果自定义验证在系统中不需要高度重用(即不需要使用自定义验证属性),则还有一种方法可以将自定义验证添加到ViewModel / Posted数据模型中,即使用IValidatableObject
每个错误都可以绑定到一个或多个模型属性,因此此方法仍适用于例如MVC Razor中的Unobtrusive验证。
以下是如何检查默认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

0

在.NET 8 Preview 5中已将其移除,请参见https://github.com/dotnet/runtime/pull/86378。 - Leon V

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接