Adrian
Adrian

Reputation: 8597

Resx caching issue on views

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 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);
}

Upvotes: 1

Views: 1205

Answers (1)

Adrian
Adrian

Reputation: 8597

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.

Upvotes: 1

Related Questions