asp.net-corerazor-pagesdata-annotations

The DataType and Range data annotations do not localize the error message


I don't understand why in an asp.net 7.0 razor pages application the localization of data annotations error messages works for all attributes except for the "DataType" attribute where the error message displayed is always in English.

Same problem with "Range" attribute when the field is empty you get the same "DataType" error message.

Additionally, localization also does not work using the ErrorMessageResourceName and ErrorMessageResourceType properties.

There are two resource files ValidationMessages.resx and ValidationMessages.it.resx, both with all the error messages.

A part of the data model:

    [Display(Name = "Year go")]
    [Required(ErrorMessage = "Required")]
    [Range(2000, 2100, ErrorMessage = "Range")]
    //[Range(2000, 2100, ErrorMessageResourceName = nameof(ValidationMessages.Range), ErrorMessageResourceType = typeof(ValidationMessages))]
    public int YearGo { get; set; }

    //[DataType(DataType.Date, ErrorMessage = "DataType")]
    [DataType(DataType.Date, ErrorMessageResourceName = nameof(ValidationMessages.DataType), ErrorMessageResourceType = typeof(ValidationMessages))]
    public DateTime ReleaseDate { get; set; }

    [Range(1, 10, ErrorMessage = "Range")]
    //[DataType(DataType.Currency, ErrorMessage = "DataType")]
    [DataType(DataType.Currency, ErrorMessageResourceName = nameof(ValidationMessages.DataType), ErrorMessageResourceType = typeof(ValidationMessages))]
    [Column(TypeName = "decimal(18, 2)")]
    public decimal? Price { get; set; } 

Program:

public static void Main(string[] args)
{
    string culture = "it";
    Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);

    var builder = WebApplication.CreateBuilder(args);

    // Add services to the container.
    builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");

    var supportedCultures = new[] { new CultureInfo("it"), new CultureInfo("en-US") };
    builder.Services.Configure<RequestLocalizationOptions>(options =>
    {
        options.DefaultRequestCulture = new RequestCulture(culture);
        options.SupportedCultures = supportedCultures;
        options.SupportedUICultures = supportedCultures;
    });

    builder.Services.AddDbContext<OWsefaWebContext>(options =>
        options.UseSqlServer(builder.Configuration.GetConnectionString("OWsefaWebContext") ?? throw new InvalidOperationException("Connection string 'OWsefaWebContext' not found.")));

    // Add services to the container.
    builder.Services.AddRazorPages()
        .AddDataAnnotationsLocalization(opts =>
        {
            opts.DataAnnotationLocalizerProvider = (type, factory) =>
            {
                var assemblyName = new AssemblyName(typeof(SharedResource).GetTypeInfo().Assembly.FullName!);
                return factory.Create(nameof(SharedResource), assemblyName.Name!);
            };
        })
        .AddViewLocalization();


    var app = builder.Build();

    using (var scope = app.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
        DataInitializer.Initialize(services);
    }

    // Configure the HTTP request pipeline.
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();
    app.MapRazorPages();

    app.UseRequestLocalization();

    app.Run();
}

Screen-shot: enter image description here


Solution

  • I checked your code, and I realized that the problem is that the error message returned by the system doesn't come from the DataTypeAttribute, but from the binding system of .NET.

    The messages for the binding system derives from the DefaultModelBindingMessageProvider , and you could set the various methods to provide a custom error message.

    You could set the messages in the AddMvcOptions method, this is the change that I do in your code to make it work with provided resources.

    builder.Services.AddRazorPages()
        .AddDataAnnotationsLocalization(opts =>
        {
            opts.DataAnnotationLocalizerProvider = (type, factory) =>
            {
                var assemblyName = new AssemblyName(typeof(SharedResource).GetTypeInfo().Assembly.FullName!);
                return factory.Create(nameof(SharedResource), assemblyName.Name!);
            };
            
        })
        .AddViewLocalization()
        .AddMvcOptions(options => {
            options.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(
                (arg) => {
                    return string.Format(CultureInfo.CurrentCulture, Resources.SharedResource.Message_DataType, arg);
            });
        });
    

    For some reasons you need to specify the current culture to string.Format to use italian resource.