File: System\ComponentModel\DataAnnotations\AsyncValidationAttribute.cs
Web Access
Project: src\runtime\src\libraries\System.ComponentModel.Annotations\src\System.ComponentModel.Annotations.csproj (System.ComponentModel.Annotations)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading;
using System.Threading.Tasks;

namespace System.ComponentModel.DataAnnotations
{
    /// <summary>
    ///     Base class for validation attributes that require asynchronous operations, such as database lookups or API calls.
    /// </summary>
    public abstract class AsyncValidationAttribute : ValidationAttribute
    {
        /// <summary>
        ///     Default constructor for any async validation attribute.
        /// </summary>
        protected AsyncValidationAttribute()
        {
        }

        /// <summary>
        ///     Constructor that accepts a fixed validation error message.
        /// </summary>
        /// <param name="errorMessage">A non-localized error message to use in <see cref="ValidationAttribute.ErrorMessageString" />.</param>
        protected AsyncValidationAttribute(string errorMessage)
            : base(errorMessage)
        {
        }

        /// <summary>
        ///     Allows for providing a resource accessor function that will be used by the <see cref="ValidationAttribute.ErrorMessageString" />
        ///     property to retrieve the error message.
        /// </summary>
        /// <param name="errorMessageAccessor">The <see cref="Func{T}" /> that will return an error message.</param>
        protected AsyncValidationAttribute(Func<string> errorMessageAccessor)
            : base(errorMessageAccessor)
        {
        }

        /// <summary>
        ///     Override of the base class <see cref="ValidationAttribute.IsValid(object?, ValidationContext)" /> method.
        ///     Subclasses must provide a synchronous validation implementation or throw an appropriate exception
        ///     to indicate that synchronous validation is not supported.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">
        ///     A <see cref="ValidationContext" /> instance that provides context about the validation operation,
        ///     such as the object and member being validated. Provides access to services required to perform
        ///     validation using <see cref="IServiceProvider" />.
        /// </param>
        /// <returns>
        ///     <see cref="ValidationResult.Success" /> when validation is valid.
        ///     An instance of <see cref="ValidationResult" /> when validation is invalid.
        /// </returns>
        protected abstract override ValidationResult? IsValid(object? value, ValidationContext validationContext);

        /// <summary>
        ///     Override this method in subclasses to implement asynchronous validation logic.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">
        ///     A <see cref="ValidationContext" /> instance that provides context about the validation operation,
        ///     such as the object and member being validated. Provides access to services required to perform
        ///     validation using <see cref="IServiceProvider" />.
        /// </param>
        /// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
        /// <returns>
        ///     A <see cref="Task{ValidationResult}" /> representing the asynchronous validation operation.
        ///     When validation is valid, the result is <see cref="ValidationResult.Success" />.
        ///     When validation is invalid, the result is an instance of <see cref="ValidationResult" />.
        /// </returns>
        protected abstract Task<ValidationResult?> IsValidAsync(
            object? value,
            ValidationContext validationContext,
            CancellationToken cancellationToken);

        /// <summary>
        ///     Sealed override of <see cref="ValidationAttribute.IsValid(object?)" /> that delegates to the
        ///     <see cref="ValidationContext" /> overload so that <see cref="AsyncValidationAttribute" /> implementations
        ///     only need to provide a single synchronous fallback via
        ///     <see cref="ValidationAttribute.IsValid(object?, ValidationContext)" />.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <returns>
        ///     <see langword="true" /> if the value is valid; otherwise, <see langword="false" />.
        /// </returns>
        public sealed override bool IsValid(object? value)
            => IsValid(value, null!) == ValidationResult.Success;

        /// <summary>
        ///     Tests whether the given <paramref name="value" /> is valid asynchronously with respect to the current
        ///     validation attribute without throwing a <see cref="ValidationException" />.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">
        ///     A <see cref="ValidationContext" /> instance that provides context about the validation operation,
        ///     such as the object and member being validated. Provides access to services required to perform
        ///     validation using <see cref="IServiceProvider" />.
        /// </param>
        /// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
        /// <returns>
        ///     A <see cref="Task{ValidationResult}" /> representing the asynchronous validation operation.
        ///     When validation is valid, the result is <see cref="ValidationResult.Success" />.
        ///     When validation is invalid, the result is an instance of <see cref="ValidationResult" />.
        /// </returns>
        /// <exception cref="InvalidOperationException"> is thrown if the current attribute is malformed.</exception>
        /// <exception cref="ArgumentNullException">When <paramref name="validationContext" /> is null.</exception>
        public async Task<ValidationResult?> GetValidationResultAsync(
            object? value,
            ValidationContext validationContext,
            CancellationToken cancellationToken = default)
        {
            ArgumentNullException.ThrowIfNull(validationContext);

            ValidationResult? result = await IsValidAsync(value, validationContext, cancellationToken).ConfigureAwait(false);

            return EnsureValidationResultErrorMessage(result, validationContext);
        }

    }
}