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












3















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).



UPDATE:



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.










share|improve this question

























  • why not have label properties on the view model and use that inside the view, then you can assign the labels from the controller action

    – Joe Audette
    Dec 30 '17 at 14:10











  • @JoeAudette, That is what I wanted to do. I was just looking for more elegant way (it there is), where the property and its name are "coupled".

    – Mohammed Noureldin
    Dec 30 '17 at 14:12













  • What exactly is your issue with DisplayNameAttribute or more specifically Display? Localization? It should work since ASP.NET Core 1.1

    – Tseng
    Dec 30 '17 at 14:31











  • Also in case you don't want to use precompiled resource files, you can use database as the source, see damienbod's example

    – Tseng
    Dec 30 '17 at 14:32








  • 1





    You can still do that, if you either override the default implementation of IDisplayMetadataProvider, in case you are not happy with the default implementation. Here a minimalistic example for DisplayNameAttribute from last year but within it you can choose which class/resource file to use basically

    – Tseng
    Dec 30 '17 at 16:03


















3















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).



UPDATE:



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.










share|improve this question

























  • why not have label properties on the view model and use that inside the view, then you can assign the labels from the controller action

    – Joe Audette
    Dec 30 '17 at 14:10











  • @JoeAudette, That is what I wanted to do. I was just looking for more elegant way (it there is), where the property and its name are "coupled".

    – Mohammed Noureldin
    Dec 30 '17 at 14:12













  • What exactly is your issue with DisplayNameAttribute or more specifically Display? Localization? It should work since ASP.NET Core 1.1

    – Tseng
    Dec 30 '17 at 14:31











  • Also in case you don't want to use precompiled resource files, you can use database as the source, see damienbod's example

    – Tseng
    Dec 30 '17 at 14:32








  • 1





    You can still do that, if you either override the default implementation of IDisplayMetadataProvider, in case you are not happy with the default implementation. Here a minimalistic example for DisplayNameAttribute from last year but within it you can choose which class/resource file to use basically

    – Tseng
    Dec 30 '17 at 16:03
















3












3








3


2






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).



UPDATE:



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.










share|improve this question
















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).



UPDATE:



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.







c# .net asp.net-mvc asp.net-core .net-core






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 30 '17 at 18:21







Mohammed Noureldin

















asked Dec 30 '17 at 14:05









Mohammed NoureldinMohammed Noureldin

3,30783247




3,30783247













  • why not have label properties on the view model and use that inside the view, then you can assign the labels from the controller action

    – Joe Audette
    Dec 30 '17 at 14:10











  • @JoeAudette, That is what I wanted to do. I was just looking for more elegant way (it there is), where the property and its name are "coupled".

    – Mohammed Noureldin
    Dec 30 '17 at 14:12













  • What exactly is your issue with DisplayNameAttribute or more specifically Display? Localization? It should work since ASP.NET Core 1.1

    – Tseng
    Dec 30 '17 at 14:31











  • Also in case you don't want to use precompiled resource files, you can use database as the source, see damienbod's example

    – Tseng
    Dec 30 '17 at 14:32








  • 1





    You can still do that, if you either override the default implementation of IDisplayMetadataProvider, in case you are not happy with the default implementation. Here a minimalistic example for DisplayNameAttribute from last year but within it you can choose which class/resource file to use basically

    – Tseng
    Dec 30 '17 at 16:03





















  • why not have label properties on the view model and use that inside the view, then you can assign the labels from the controller action

    – Joe Audette
    Dec 30 '17 at 14:10











  • @JoeAudette, That is what I wanted to do. I was just looking for more elegant way (it there is), where the property and its name are "coupled".

    – Mohammed Noureldin
    Dec 30 '17 at 14:12













  • What exactly is your issue with DisplayNameAttribute or more specifically Display? Localization? It should work since ASP.NET Core 1.1

    – Tseng
    Dec 30 '17 at 14:31











  • Also in case you don't want to use precompiled resource files, you can use database as the source, see damienbod's example

    – Tseng
    Dec 30 '17 at 14:32








  • 1





    You can still do that, if you either override the default implementation of IDisplayMetadataProvider, in case you are not happy with the default implementation. Here a minimalistic example for DisplayNameAttribute from last year but within it you can choose which class/resource file to use basically

    – Tseng
    Dec 30 '17 at 16:03



















why not have label properties on the view model and use that inside the view, then you can assign the labels from the controller action

– Joe Audette
Dec 30 '17 at 14:10





why not have label properties on the view model and use that inside the view, then you can assign the labels from the controller action

– Joe Audette
Dec 30 '17 at 14:10













@JoeAudette, That is what I wanted to do. I was just looking for more elegant way (it there is), where the property and its name are "coupled".

– Mohammed Noureldin
Dec 30 '17 at 14:12







@JoeAudette, That is what I wanted to do. I was just looking for more elegant way (it there is), where the property and its name are "coupled".

– Mohammed Noureldin
Dec 30 '17 at 14:12















What exactly is your issue with DisplayNameAttribute or more specifically Display? Localization? It should work since ASP.NET Core 1.1

– Tseng
Dec 30 '17 at 14:31





What exactly is your issue with DisplayNameAttribute or more specifically Display? Localization? It should work since ASP.NET Core 1.1

– Tseng
Dec 30 '17 at 14:31













Also in case you don't want to use precompiled resource files, you can use database as the source, see damienbod's example

– Tseng
Dec 30 '17 at 14:32







Also in case you don't want to use precompiled resource files, you can use database as the source, see damienbod's example

– Tseng
Dec 30 '17 at 14:32






1




1





You can still do that, if you either override the default implementation of IDisplayMetadataProvider, in case you are not happy with the default implementation. Here a minimalistic example for DisplayNameAttribute from last year but within it you can choose which class/resource file to use basically

– Tseng
Dec 30 '17 at 16:03







You can still do that, if you either override the default implementation of IDisplayMetadataProvider, in case you are not happy with the default implementation. Here a minimalistic example for DisplayNameAttribute from last year but within it you can choose which class/resource file to use basically

– Tseng
Dec 30 '17 at 16:03














1 Answer
1






active

oldest

votes


















4















@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






share|improve this answer





















  • 1





    Yea, I was just working on additional example where you create a custom localizer which accepts two localizers, one for the shared resource and one for the model, then you check if the string exists in the shared resource, if not look up in the viewmodel resource

    – Tseng
    Dec 30 '17 at 16:50






  • 1





    See this example github.com/aspnet/Mvc/issues/2706#issuecomment-248049748. It's obsolete now cause the DisplayNameAttribute Code has now been moved into DataAnnotationsMetadataProvider, but it shows how to add and register your own IXxxMetadataProvider

    – Tseng
    Dec 30 '17 at 16:52






  • 1





    @MohammedNoureldin: They are a bit different. DisplayNameAttribute doesn't have the properties that DisplayNamehave, it has just a parameterless constructor and Name. Wheras DisplayAttribute has Name, Description, ResourceType and more. But both are now implemented in DataAnnotationsMetadataProvider (which wasn't the case when ASP.NET Core 1.0 was released)

    – Tseng
    Dec 30 '17 at 17:00






  • 1





    Updated my answer. Untested though other than "it compiles" ;) Next time just provide sufficient information in the initial question, makes it easier to bring in a solution when the exact reasons for not wanting to use the ASP.NET Core localization. The localization system is VERY flexible and customizable

    – Tseng
    Dec 30 '17 at 17:07








  • 1





    @MohammedNoureldin: Neither one uses unmanaged resources, so there's nothing to dispose and worry about. Neither ResourceManagerStringLocalizer nor StringLocalizer implements IDisposable. M.E.DI calls dispose where IDisposable is is implemented and resolved via M:E.DI, all others will get disposed when they are not referenced anymore and the GC feels like it

    – Tseng
    Dec 30 '17 at 23:54











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f48034655%2fhow-to-change-viewmodel-display-name-without-using-displaynameattribute%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









4















@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






share|improve this answer





















  • 1





    Yea, I was just working on additional example where you create a custom localizer which accepts two localizers, one for the shared resource and one for the model, then you check if the string exists in the shared resource, if not look up in the viewmodel resource

    – Tseng
    Dec 30 '17 at 16:50






  • 1





    See this example github.com/aspnet/Mvc/issues/2706#issuecomment-248049748. It's obsolete now cause the DisplayNameAttribute Code has now been moved into DataAnnotationsMetadataProvider, but it shows how to add and register your own IXxxMetadataProvider

    – Tseng
    Dec 30 '17 at 16:52






  • 1





    @MohammedNoureldin: They are a bit different. DisplayNameAttribute doesn't have the properties that DisplayNamehave, it has just a parameterless constructor and Name. Wheras DisplayAttribute has Name, Description, ResourceType and more. But both are now implemented in DataAnnotationsMetadataProvider (which wasn't the case when ASP.NET Core 1.0 was released)

    – Tseng
    Dec 30 '17 at 17:00






  • 1





    Updated my answer. Untested though other than "it compiles" ;) Next time just provide sufficient information in the initial question, makes it easier to bring in a solution when the exact reasons for not wanting to use the ASP.NET Core localization. The localization system is VERY flexible and customizable

    – Tseng
    Dec 30 '17 at 17:07








  • 1





    @MohammedNoureldin: Neither one uses unmanaged resources, so there's nothing to dispose and worry about. Neither ResourceManagerStringLocalizer nor StringLocalizer implements IDisposable. M.E.DI calls dispose where IDisposable is is implemented and resolved via M:E.DI, all others will get disposed when they are not referenced anymore and the GC feels like it

    – Tseng
    Dec 30 '17 at 23:54
















4















@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






share|improve this answer





















  • 1





    Yea, I was just working on additional example where you create a custom localizer which accepts two localizers, one for the shared resource and one for the model, then you check if the string exists in the shared resource, if not look up in the viewmodel resource

    – Tseng
    Dec 30 '17 at 16:50






  • 1





    See this example github.com/aspnet/Mvc/issues/2706#issuecomment-248049748. It's obsolete now cause the DisplayNameAttribute Code has now been moved into DataAnnotationsMetadataProvider, but it shows how to add and register your own IXxxMetadataProvider

    – Tseng
    Dec 30 '17 at 16:52






  • 1





    @MohammedNoureldin: They are a bit different. DisplayNameAttribute doesn't have the properties that DisplayNamehave, it has just a parameterless constructor and Name. Wheras DisplayAttribute has Name, Description, ResourceType and more. But both are now implemented in DataAnnotationsMetadataProvider (which wasn't the case when ASP.NET Core 1.0 was released)

    – Tseng
    Dec 30 '17 at 17:00






  • 1





    Updated my answer. Untested though other than "it compiles" ;) Next time just provide sufficient information in the initial question, makes it easier to bring in a solution when the exact reasons for not wanting to use the ASP.NET Core localization. The localization system is VERY flexible and customizable

    – Tseng
    Dec 30 '17 at 17:07








  • 1





    @MohammedNoureldin: Neither one uses unmanaged resources, so there's nothing to dispose and worry about. Neither ResourceManagerStringLocalizer nor StringLocalizer implements IDisposable. M.E.DI calls dispose where IDisposable is is implemented and resolved via M:E.DI, all others will get disposed when they are not referenced anymore and the GC feels like it

    – Tseng
    Dec 30 '17 at 23:54














4












4








4








@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






share|improve this answer
















@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







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 20 '18 at 16:01

























answered Dec 30 '17 at 16:39









TsengTseng

34.5k595124




34.5k595124








  • 1





    Yea, I was just working on additional example where you create a custom localizer which accepts two localizers, one for the shared resource and one for the model, then you check if the string exists in the shared resource, if not look up in the viewmodel resource

    – Tseng
    Dec 30 '17 at 16:50






  • 1





    See this example github.com/aspnet/Mvc/issues/2706#issuecomment-248049748. It's obsolete now cause the DisplayNameAttribute Code has now been moved into DataAnnotationsMetadataProvider, but it shows how to add and register your own IXxxMetadataProvider

    – Tseng
    Dec 30 '17 at 16:52






  • 1





    @MohammedNoureldin: They are a bit different. DisplayNameAttribute doesn't have the properties that DisplayNamehave, it has just a parameterless constructor and Name. Wheras DisplayAttribute has Name, Description, ResourceType and more. But both are now implemented in DataAnnotationsMetadataProvider (which wasn't the case when ASP.NET Core 1.0 was released)

    – Tseng
    Dec 30 '17 at 17:00






  • 1





    Updated my answer. Untested though other than "it compiles" ;) Next time just provide sufficient information in the initial question, makes it easier to bring in a solution when the exact reasons for not wanting to use the ASP.NET Core localization. The localization system is VERY flexible and customizable

    – Tseng
    Dec 30 '17 at 17:07








  • 1





    @MohammedNoureldin: Neither one uses unmanaged resources, so there's nothing to dispose and worry about. Neither ResourceManagerStringLocalizer nor StringLocalizer implements IDisposable. M.E.DI calls dispose where IDisposable is is implemented and resolved via M:E.DI, all others will get disposed when they are not referenced anymore and the GC feels like it

    – Tseng
    Dec 30 '17 at 23:54














  • 1





    Yea, I was just working on additional example where you create a custom localizer which accepts two localizers, one for the shared resource and one for the model, then you check if the string exists in the shared resource, if not look up in the viewmodel resource

    – Tseng
    Dec 30 '17 at 16:50






  • 1





    See this example github.com/aspnet/Mvc/issues/2706#issuecomment-248049748. It's obsolete now cause the DisplayNameAttribute Code has now been moved into DataAnnotationsMetadataProvider, but it shows how to add and register your own IXxxMetadataProvider

    – Tseng
    Dec 30 '17 at 16:52






  • 1





    @MohammedNoureldin: They are a bit different. DisplayNameAttribute doesn't have the properties that DisplayNamehave, it has just a parameterless constructor and Name. Wheras DisplayAttribute has Name, Description, ResourceType and more. But both are now implemented in DataAnnotationsMetadataProvider (which wasn't the case when ASP.NET Core 1.0 was released)

    – Tseng
    Dec 30 '17 at 17:00






  • 1





    Updated my answer. Untested though other than "it compiles" ;) Next time just provide sufficient information in the initial question, makes it easier to bring in a solution when the exact reasons for not wanting to use the ASP.NET Core localization. The localization system is VERY flexible and customizable

    – Tseng
    Dec 30 '17 at 17:07








  • 1





    @MohammedNoureldin: Neither one uses unmanaged resources, so there's nothing to dispose and worry about. Neither ResourceManagerStringLocalizer nor StringLocalizer implements IDisposable. M.E.DI calls dispose where IDisposable is is implemented and resolved via M:E.DI, all others will get disposed when they are not referenced anymore and the GC feels like it

    – Tseng
    Dec 30 '17 at 23:54








1




1





Yea, I was just working on additional example where you create a custom localizer which accepts two localizers, one for the shared resource and one for the model, then you check if the string exists in the shared resource, if not look up in the viewmodel resource

– Tseng
Dec 30 '17 at 16:50





Yea, I was just working on additional example where you create a custom localizer which accepts two localizers, one for the shared resource and one for the model, then you check if the string exists in the shared resource, if not look up in the viewmodel resource

– Tseng
Dec 30 '17 at 16:50




1




1





See this example github.com/aspnet/Mvc/issues/2706#issuecomment-248049748. It's obsolete now cause the DisplayNameAttribute Code has now been moved into DataAnnotationsMetadataProvider, but it shows how to add and register your own IXxxMetadataProvider

– Tseng
Dec 30 '17 at 16:52





See this example github.com/aspnet/Mvc/issues/2706#issuecomment-248049748. It's obsolete now cause the DisplayNameAttribute Code has now been moved into DataAnnotationsMetadataProvider, but it shows how to add and register your own IXxxMetadataProvider

– Tseng
Dec 30 '17 at 16:52




1




1





@MohammedNoureldin: They are a bit different. DisplayNameAttribute doesn't have the properties that DisplayNamehave, it has just a parameterless constructor and Name. Wheras DisplayAttribute has Name, Description, ResourceType and more. But both are now implemented in DataAnnotationsMetadataProvider (which wasn't the case when ASP.NET Core 1.0 was released)

– Tseng
Dec 30 '17 at 17:00





@MohammedNoureldin: They are a bit different. DisplayNameAttribute doesn't have the properties that DisplayNamehave, it has just a parameterless constructor and Name. Wheras DisplayAttribute has Name, Description, ResourceType and more. But both are now implemented in DataAnnotationsMetadataProvider (which wasn't the case when ASP.NET Core 1.0 was released)

– Tseng
Dec 30 '17 at 17:00




1




1





Updated my answer. Untested though other than "it compiles" ;) Next time just provide sufficient information in the initial question, makes it easier to bring in a solution when the exact reasons for not wanting to use the ASP.NET Core localization. The localization system is VERY flexible and customizable

– Tseng
Dec 30 '17 at 17:07







Updated my answer. Untested though other than "it compiles" ;) Next time just provide sufficient information in the initial question, makes it easier to bring in a solution when the exact reasons for not wanting to use the ASP.NET Core localization. The localization system is VERY flexible and customizable

– Tseng
Dec 30 '17 at 17:07






1




1





@MohammedNoureldin: Neither one uses unmanaged resources, so there's nothing to dispose and worry about. Neither ResourceManagerStringLocalizer nor StringLocalizer implements IDisposable. M.E.DI calls dispose where IDisposable is is implemented and resolved via M:E.DI, all others will get disposed when they are not referenced anymore and the GC feels like it

– Tseng
Dec 30 '17 at 23:54





@MohammedNoureldin: Neither one uses unmanaged resources, so there's nothing to dispose and worry about. Neither ResourceManagerStringLocalizer nor StringLocalizer implements IDisposable. M.E.DI calls dispose where IDisposable is is implemented and resolved via M:E.DI, all others will get disposed when they are not referenced anymore and the GC feels like it

– Tseng
Dec 30 '17 at 23:54




















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f48034655%2fhow-to-change-viewmodel-display-name-without-using-displaynameattribute%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

How to pass form data using jquery Ajax to insert data in database?

National Museum of Racing and Hall of Fame

Accessing nested JavaScript objects with string key