Validation of Guid
Asked Answered
A

10

24

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.

Aquanaut answered 25/8, 2011 at 8:55 Comment(0)
B
32

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));
    }
}
Blepharitis answered 3/12, 2012 at 18:39 Comment(3)
i tried to use your code with property like below in a model and the model fails if i supply null value? i thought null is okay but not 0 in this case according to example in the summary. Please confirm [RequiredNonDefault] public int? Test { get; set; }Appressed
That's because your your type is int?. And the default value of any Nullable<>` object, is null, so null is not valid but 0 is. However you're correct in that my example implies otherwise. I'll fix that. To get what you're describing to work (allowing null, but not 0), you're better off using something like this [Range(1, int.MaxValue)] int? i {get;set;}Blepharitis
@Albercht Thank you for confirmation.Appressed
T
14

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.

Triacid answered 25/8, 2011 at 9:5 Comment(1)
That actually won't work unless you mark the Guid property as nullable, see my answer.Blepharitis
O
9

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

[Required]
[RegularExpression("^((?!00000000-0000-0000-0000-000000000000).)*$", ErrorMessage = "Cannot use default Guid")]
public Guid Id { get; set; }
Olathe answered 8/9, 2020 at 3:8 Comment(3)
Why is a different author above stating this solution does not work?Conscription
His/Her regex didn't work for me when i tried it. Can't recall why, but i had to make some changes and this one works.Olathe
Outstanding. This is so much more elegant than creating a custom validator.Gyration
K
7

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

    }
}
Ketchan answered 16/11, 2016 at 12:9 Comment(0)
A
6

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; }
}
Aha answered 21/2, 2020 at 21:24 Comment(0)
A
3

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.

Aviator answered 26/5, 2023 at 9:23 Comment(2)
Removed it in .NET 8 Preview 5, see github.com/dotnet/runtime/pull/86378.Tratner
Very sad this was removed. The reasoning is also annoying: "It fails validation on numeric properties that have been explicitly set to zero". That's by design!!!Mcpeak
C
2

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

Christiansand answered 26/5, 2019 at 6:45 Comment(0)
C
2

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
  }
Convery answered 6/11, 2020 at 13:3 Comment(0)
S
2

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.

Seep answered 11/11, 2021 at 6:0 Comment(0)
M
1

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;
}
Mcpeak answered 1/5, 2024 at 3:40 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.