| File: System\ComponentModel\DataAnnotations\AsyncValidationAttribute.cs | Web Access |
| Project: 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. /// Derived implementations must be thread-safe, as instances of this attribute may be invoked concurrently from multiple threads. /// </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> /// <remarks> /// <para> /// Synchronous validation consumers invoke this method. Provide a synchronous implementation when it /// can evaluate the applicable rule, and return a validation error when the rule rejects the value. /// If a required rule applies but cannot be evaluated synchronously, throw /// <see cref="InvalidOperationException" /> with a message directing callers to an asynchronous /// validation entry point. An unsupported invocation does not establish that the value is invalid. /// </para> /// <para> /// Return <see cref="ValidationResult.Success" /> without evaluating the rule only when the rule does /// not apply. Do not return success merely because a required asynchronous check applies but cannot run /// synchronously. Success does not indicate pending validation or arrange a later asynchronous invocation. /// </para> /// <para> /// Do not implement this method by blocking on asynchronous work through /// <c>Task<TResult>.Result</c>, <c>Task.Wait()</c>, or /// <c>GetAwaiter().GetResult()</c>. Wrapping the asynchronous operation in /// <c>Task.Run</c> does not make a blocking wait appropriate. /// </para> /// </remarks> /// <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" />. This value can be <see langword="null" /> when the /// contextless <see cref="ValidationAttribute.IsValid(object?)" /> overload invokes this method. /// </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> /// <remarks> /// <para> /// This method must perform all applicable checks, including checks that can run synchronously. /// The synchronous <see cref="IsValid(object?, ValidationContext)" /> implementation is not /// automatically invoked before this method. Share common checks through a helper when needed. /// </para> /// <para> /// Implementations must observe the supplied <paramref name="cancellationToken" /> and stop work /// promptly when cancellation is requested. The validation infrastructure may cancel this token after /// a validation failure to stop sibling validators and awaits all started validation tasks before /// returning. An implementation that ignores cancellation can delay failure and short-circuiting. /// </para> /// </remarks> 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> /// <remarks> /// <para> /// The underlying <see cref="IsValidAsync(object, ValidationContext, CancellationToken)" /> implementation /// must observe the supplied <paramref name="cancellationToken" /> and stop work promptly when cancellation /// is requested. The validation infrastructure awaits all started validation tasks before returning, so an /// implementation that ignores cancellation can delay failure and short-circuiting. /// </para> /// <para> /// Callers that need to bound validation time should pass a token configured to cancel after a timeout, /// such as one from a <see cref="CancellationTokenSource" /> configured with /// <see cref="CancellationTokenSource.CancelAfter(TimeSpan)" />. /// </para> /// </remarks> /// <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); } } }