| File: ValidationLocalizationOptions.cs | Web Access |
| Project: src\aspnetcore\src\Validation\Localization\src\Microsoft.Extensions.Validation.Localization.csproj (Microsoft.Extensions.Validation.Localization) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.Localization; namespace Microsoft.Extensions.Validation.Localization; /// <summary> /// Configuration options for the <c>Microsoft.Extensions.Validation.Localization</c> default /// <see cref="IStringLocalizer"/>-based validation localizer. Configure via /// <c>services.AddValidationLocalization(options => ...)</c>. /// </summary> public class ValidationLocalizationOptions { /// <summary> /// Gets or sets the delegate that controls which <see cref="IStringLocalizer"/> is used /// for a given declaring type. The declaring type is the type that contains the property /// being validated, or <see langword="null"/> when the validation pipeline has no declaring /// type to attribute the message to (e.g., top-level Minimal API parameters). /// </summary> /// <remarks> /// <para> /// When <see langword="null"/> (the default), <see cref="IStringLocalizerFactory.Create(Type)"/> /// is called with the declaring type, which follows the standard resource file naming convention /// (e.g., <c>Resources/Models.Customer.fr.resx</c> for type <c>Models.Customer</c>). /// For declaring-type-less invocations, the no-provider fallback uses <c>typeof(object)</c>. /// In that case the resolved resource source is rarely useful, which is why configuring /// this delegate (or using <c>AddValidationLocalization<TResource>()</c>) /// is recommended for Minimal API parameter validation. /// </para> /// <example> /// <code> /// options.LocalizerProvider = (type, factory) => /// factory.Create(typeof(SharedValidationMessages)); /// </code> /// </example> /// <para> /// For the shared-resource pattern above, prefer the dedicated /// <c>AddValidationLocalization<TResource>()</c> overload, which configures /// <see cref="LocalizerProvider"/> for you (with a single-instance optimization that /// skips re-resolving the same <see cref="IStringLocalizer"/> on every validation call). /// </para> /// <para> /// The delegate must return a non-null <see cref="IStringLocalizer"/>. Returning <see langword="null"/> /// causes an <see cref="InvalidOperationException"/> to be thrown the next time validation tries /// to localize a message for that declaring type. /// </para> /// <para> /// <b>Caching:</b> the validation pipeline does not cache the <see cref="IStringLocalizer"/> /// returned by this delegate. The delegate is invoked once per call to /// <see cref="IValidationLocalizer.ResolveDisplayName"/> and /// <see cref="IValidationLocalizer.ResolveErrorMessage"/>, so the underlying /// <see cref="IStringLocalizerFactory"/> is responsible for caching localizer instances if /// instance creation is expensive. However, the default factory registered by /// <c>AddLocalization()</c> (<c>ResourceManagerStringLocalizerFactory</c>) caches its /// results internally. If your delegate itself does /// meaningful work beyond calling the factory, capture the result in a closure to amortize /// that cost across calls. /// </para> /// </remarks> public Func<Type?, IStringLocalizerFactory, IStringLocalizer>? LocalizerProvider { get; set; } /// <summary> /// Gets or sets the delegate that determines the localization lookup key for a /// validation attribute's error message. When configured, the delegate is invoked for /// every attribute and takes precedence over <see cref="ValidationAttribute.ErrorMessage"/>. /// Returning <see langword="null"/> or an empty string defers to using /// <see cref="ValidationAttribute.ErrorMessage"/> directly as the lookup key. /// </summary> /// <remarks> /// <para> /// When <see langword="null"/> (the default), only attributes with /// <see cref="ValidationAttribute.ErrorMessage"/> set are localized (using the /// <see cref="ValidationAttribute.ErrorMessage"/> value as the key); attributes without /// an explicit <see cref="ValidationAttribute.ErrorMessage"/> are not localized at all. /// </para> /// <para> /// When configured, the delegate can: /// </para> /// <list type="bullet"> /// <item>Provide a fallback key for attributes without an explicit /// <see cref="ValidationAttribute.ErrorMessage"/> (convention-based key selection).</item> /// <item>Transform an existing <see cref="ValidationAttribute.ErrorMessage"/> into a /// different lookup key by reading /// <see cref="ErrorMessageLocalizationContext.Attribute"/>'s /// <see cref="ValidationAttribute.ErrorMessage"/> from the context.</item> /// <item>Return <see langword="null"/> or an empty string to defer to the default /// behavior of using <see cref="ValidationAttribute.ErrorMessage"/> as the key.</item> /// </list> /// <example> /// <code> /// options.ErrorMessageKeyProvider = context => /// $"{context.Attribute.GetType().Name}_ValidationError"; /// // This makes the localizer look up "RequiredAttribute_ValidationError" /// // instead of "The {0} field is required." /// </code> /// </example> /// </remarks> public Func<ErrorMessageLocalizationContext, string?>? ErrorMessageKeyProvider { get; set; } /// <summary> /// Gets the registry of formatters for attribute-specific error message template formatting. /// Built-in formatters for standard attributes are registered automatically. /// </summary> /// <remarks> /// The registry is intended to be configured during application startup (typically inside the /// <c>AddValidationLocalization</c> options callback) by calling /// <see cref="ValidationAttributeFormatterRegistry.AddFormatter{TAttribute}(Func{TAttribute, IValidationAttributeFormatter})"/>. /// Mutating the registry after the validation pipeline has begun processing requests is not /// thread-safe and is not supported. /// </remarks> public ValidationAttributeFormatterRegistry AttributeFormatters { get; } = new(); }