How to change ViewModel display name without using `DisplayNameAttribute`?

后端 未结 1 472
-上瘾入骨i
-上瘾入骨i 2021-01-06 03:50

I want to change the display name of some properties (of ViewModel) directly without using [DisplayName(\"prop name\")]. This should happen either direc

相关标签:
1条回答
  • 2021-01-06 04:25

    @Tseng, sorry I should have said that more clearly, I meant that we should use either naming convention, or SharedResources. but not both, I have many cases where I have a lot of shared resources, and many ViewModel-specific strings (so a mixture). That is not achievable with .Net Core localization solution.

    If your only worries is that you can or can't determine if one or multiple resource files are chosen, that can easily be configured. I had to dig a bit in the source code, bit its seems possible.

    As we can see here the localizer is determined by the factory defined in the configuration

    if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null)
    {
        localizer = _localizationOptions.DataAnnotationLocalizerProvider(containerType, _stringLocalizerFactory);
    }
    

    whereas _localizationOptions is MvcDataAnnotationsLocalizationOptions.

    The default implementation of MvcDataAnnotationsLocalizationOptions is here:

    /// <inheritdoc />
    public void Configure(MvcDataAnnotationsLocalizationOptions options)
    {
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }
    
        options.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) =>
            stringLocalizerFactory.Create(modelType);
    }
    

    So it uses per model resources by default.

    You can change that to a SharedResource file for all data annotations if you like, with the following in your Startup.ConfigureServices (untested, but should work):

    services.AddMvc()
        .AddDataAnnotationsLocalization(options =>
        {
            options.DataAnnotationLocalizerProvider = (type, factory) =>
                factory.Create(typeof(SharedResource));
        });
    

    This will effectively ignore the passed type and always return a shared string localizer.

    Of course, you can add any logic there and decide on type-per-type case which localizer you are going to use.

    Edit

    If that's not enough, you can implement your own custom IDisplayMetadataProvider which handles it the way you want. But using the DisplayAttribute should be enough actually. DisplayAttribute has additional parameters which allow you to define the resource type.

    [Display(Name = "StringToLocalize", ResourceType = typeof(SharedResource))]
    

    With the ResourceType you can choose the class (and hence the resource file name) used to look up for the localization.

    Edit 2: Using a wrapped IStringLocalizer with fallback to per-viewmodel resource

    The more elegant solution involves using the above MvcDataAnnotationsLocalizationOptions options file to return your own IStringLocalizer which looks into one resource file and falls back to the other one.

    public class DataAnnotationStringLocalizer : IStringLocalizer
    {
        private readonly IStringLocalizer primaryLocalizer;
        private readonly IStringLocalizer fallbackLocalizer;
    
        public DataAnnotationStringLocalizer(IStringLocalizer primaryLocalizer, IStringLocalizer fallbackLocalizer)
        {
            this.primaryLocalizer = primaryLocalizer ?? throw new ArgumentNullException(nameof(primaryLocalizer));
            this.fallbackLocalizer = fallbackLocalizer ?? throw new ArgumentNullException(nameof(fallbackLocalizer));
        }
    
        public LocalizedString this[string name]
        {
            get
            {
                LocalizedString localizedString = primaryLocalizer[name];
                if (localizedString.ResourceNotFound)
                {
                    localizedString = fallbackLocalizer[name];
                }
    
                return localizedString;
            }
        }
    
        public LocalizedString this[string name, params object[] arguments]
        {
            get
            {
                LocalizedString localizedString = primaryLocalizer[name, arguments];
                if (localizedString.ResourceNotFound)
                {
                    localizedString = fallbackLocalizer[name, arguments];
                }
    
                return localizedString;
            }
        }
    
        public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
            => primaryLocalizer.GetAllStrings(includeParentCultures).Concat(fallbackLocalizer.GetAllStrings(includeParentCultures));
    
        public IStringLocalizer WithCulture(CultureInfo culture)
            => new DataAnnotationStringLocalizer(primaryLocalizer.WithCulture(culture), fallbackLocalizer.WithCulture(culture));
    }
    

    And with the following options

    services.AddMvc()
        .AddDataAnnotationsLocalization(options =>
        {
            options.DataAnnotationLocalizerProvider = (type, factory) =>
            {
                return new DataAnnotationStringLocalizer(
                    factory?.Create(typeof(SharedResource)),
                    factory?.Create(type)
                );
            };
        });
    

    Now, the string is first resolved from the shared resource and if the string wasn't found there, it will resolve it from the view model type (type parameter passed to the factory method).

    If you don't like the logic and you want that it first looks into the view-model resource files, you just change the order to

    services.AddMvc()
        .AddDataAnnotationsLocalization(options =>
        {
            options.DataAnnotationLocalizerProvider = (type, factory) =>
            {
                return new DataAnnotationStringLocalizer(
                    factory?.Create(type),
                    factory?.Create(typeof(SharedResource))
                );
            }
        });
    

    Now the view model is the primary resolver and shared resource the secondary

    0 讨论(0)
提交回复
热议问题