I want to change the display name of some properties (of ViewModel) directly without using [DisplayName("prop name")]
. This should happen either directly inside the controller before returning the View, or inside the ViewModel class itself.
I do NOT want to change anything in the View, and I do not want to use any data annotations. How can I achieve that?
Is there any fluent syntax maybe to get that?
I am using: ASP.Net Core 2.0
The problem with data annotations is that I want to get my display name in run time (while data annotations are pre-compiled).
The main reason for asking this question was to find a way to wrap the IStringLocalizer
and particularly its behavior when localizing data annotations. The accepted answer explains the basics of that well.
@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.
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.
IStringLocalizer
with fallback to per-viewmodel resourceThe 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With