1

This is a .NET 4.5 project, it has been set up long time before my time here, so not a lot of details as to why it's been done this way.

We have a web application where each language has its own resx file. On every page load, the values are loaded from the resx file, the usual way.

string strLangResourceValue = (string)HttpContext.GetGlobalResourceObject(lang, strLangResourceKey);

This works fine. Might be worth mentioning that this line of code is in a separate project so it can be shared with multiple other projects we have under the solution.

In the views (Razor) we do @Application.GetLangResource("Some_Key"); to retrieve the value we need. The language is taken from the session model stored in HttpContext.

The issue raises when we change the language, every partial view on that page is translated accordingly except for that 'user settings' page we changed the language on. What makes it strange is that we have two views, one read only page to only display the data and one which contains the actual form to modify the values and neither one of them is translated.

The known issues are:

  • I can't now set CultureInfo within the web application, I need to use the session model to get this information and use the above line of code to grab the data.
  • Force refreshing the browser, clearing cookies and cache does not fix the issue.
  • Only restarting the IIS server fixes the issue (the resx file is recompiled)

I know that the resx file are compiled during runtime and are static. They are not changed in the code during application run, it's only the user session that changes. The above list has been attempted to get to the bottom of the issue, but restarting the application every time someone changes their language is not an option (as you might of guessed).

Is there an obvious solution to this that I'm missing? There is no resx error, it's IIS cache (or at least it seems like it is) on just the two specific pages. They are built the same way as all the other ones, just not switching the language. This applies to all users when they change their languages.

I have tried to manually clear the cache using the below lines of code, that did not do the job issue still persisted.

Resources.AppSettings.ResourceManager.ReleaseAllResources();
Resources.English.ResourceManager.ReleaseAllResources();
Resources.Dutch.ResourceManager.ReleaseAllResources();
HttpResponse.RemoveOutputCacheItem("/Views/User/userViewDetails.cshtml");

foreach(System.Collections.DictionaryEntry entry in HttpContext.Cache) {
    HttpContext.Cache.Remove((string) entry.Key);
}
Adrian
  • 8,271
  • 2
  • 26
  • 43
  • See [ASP.NET MVC 5 culture in route and url](https://stackoverflow.com/a/32839796/) for an alternative approach that doesn't involve session or caching. – NightOwl888 Jan 09 '18 at 19:16
  • @NightOwl888 That's for the suggestion, I have tried another solution. After further debugging it appears that values are not taken from resx anymore post first page load. I am currently testing another approach, which seems to work so far if all goes well I'll post my solution here. Thanks again. – Adrian Jan 09 '18 at 19:22

1 Answers1

1

I finally got to the bottom of my own problem, but I still don't understand how that works. So, the problem was that on first view render it would grab the values from resx for the corresponding language but not on any subsequent renders (just as if it would cache those values to cut access times to resx, which is great).

I ended up implementing my own DataAnnotationsModelMetadataProvider and it did the job great.

So for anyone interested at my solution it looks something along the lines of:

public class LocalizedDataAnnotationsModelMetadataProvider : DataAnnotationsModelMetadataProvider
{

    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        var meta = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);  //  Get metadata of the property

        string resXKey = string.Empty;
        object attribute = attributes.OfType<DisplayLabel>().FirstOrDefault();  //  Try to get the DisplayLabel annotation

        //  If DisplayLabel is found then...
        if (attribute != null)
        {
            resXKey = ((DisplayLabel)attribute).Key;    //  Grab the resx key added to the annotation
            meta.DisplayName = Application.GetLangResource(resXKey) + meta.DisplayName; //  Retrieve the language resource for the key and add back any trailing items returned by the annotation
        }

        //  DisplayLabel is not found...
        if (attribute == null)
        {
            attribute = attributes.OfType<DisplayNameLocalizedAttribute>().FirstOrDefault();    //  Try to get the DisplayNameLocalizedAttribute annotation

            //  If annotation exists then...
            if (attribute != null)
            {
                resXKey = ((DisplayNameLocalizedAttribute)attribute).Key;   //  Grab the resx key added to the annotation

                string resXValue = Application.GetLangResource(resXKey);    //  Retrieve the language resource for the key
                string finalValue = resXValue;  //  Create a new string

                if (((DisplayNameLocalizedAttribute)attribute).IsLabel) //  Check if property annotation is set to label
                {
                    finalValue = resXValue + meta.DisplayName;  //  Add back any trailing items returned by the annotation
                }


                meta.DisplayName = finalValue;  //  Set the DisplayName of the property back onto the meta
            }
        }

        return meta;
    }

} 

The DisplayLabel and DisplayNameLocalizedAttribute are custom property annotations, which store the resx keys and any additional data such as (if it's a label so typically we can add ":") at the end if it's for an input.

The Application.GetLangResource is a function which grabs values from resx files, based on given language and if the values is not found it would return a suitable message.

To make the server use the created DataAnnotationModelMetadataProvider you need to set it as the Current one on Application_Start event inside Global.asax.

To do that, you would do:

LocalizedDataAnnotationsModelMetadataProvider loca = new LocalizedDataAnnotationsModelMetadataProvider();

ModelMetadataProviders.Current = loca;

It is important (if you get errors in global.asax) that you're using System.Mvc and not the other import that appears there for ModelMetadataProviders as it won't let you assign the new Provider.

Adrian
  • 8,271
  • 2
  • 26
  • 43