23

I am setting NullDisplayText in the DisplayFormat from resource through the following code

public class LocalizedDisplayFormatAttribute : DisplayFormatAttribute
{

    private readonly PropertyInfo _propertyInfo;


    public LocalizedDisplayFormatAttribute(string resourceKey, Type resourceType)
        : base()
    {
        this._propertyInfo = resourceType.GetProperty(resourceKey, BindingFlags.Static | BindingFlags.Public);
        if (this._propertyInfo == null)
        {
            return;
        }

        base.NullDisplayText = (string)this._propertyInfo.GetValue(this._propertyInfo.DeclaringType, null);
    }


    public new string NullDisplayText
    {
        get
        {
            return base.NullDisplayText;
        }

        set
        {
            base.NullDisplayText = value;
        }
    }
}

My default culture used is "en-US",Once I change the culture to es-AR and load the pages its working fine, but when I change the culture back to en-US fields are not getting converted back.

I change the culture throught the following way

protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        try
        {
            HttpCookie cookie = HttpContext.Current.Request.Cookies.Get("CurrentCulture");
            string culutureCode = cookie != null && !string.IsNullOrEmpty(cookie.Value) ? cookie.Value : "en";
            CultureInfo ci = new CultureInfo(culutureCode);
            System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
            System.Threading.Thread.CurrentThread.CurrentCulture =
            CultureInfo.CreateSpecificCulture(ci.Name);
        }
        catch
        {
        }
    }

I use DisplayFormat attribute in ViewModel as

  public class AlarmCodeDetailsViewModel
    {
        /// <summary>
        /// Gets or sets the alarm code ID
        /// </summary>
        public int AlarmCodeID { get; set; }

        /// <summary>
        /// Gets or sets the alarm code
        /// </summary>
        [LocalizedDisplayName("Label_AlarmCode")]
        [LocalizedDisplayFormatAttribute("Warning_NullDisplayText", typeof(Properties.Resources), HtmlEncode = false)]
        public string Code { get; set; }

        /// <summary>
        /// Gets or sets the Description
        /// </summary>
        [LocalizedDisplayName("Label_Description")]
        [LocalizedDisplayFormatAttribute("Warning_NullDisplayText", typeof(Properties.Resources), HtmlEncode = false)]
        public string Description { get; set; }

        /// <summary>
        /// Gets or sets the Notes
        /// </summary>
        [LocalizedDisplayName("Label_Notes")]
        [LocalizedDisplayFormatAttribute("Warning_NullDisplayText", typeof(Properties.Resources), HtmlEncode = false)]
        public string Notes { get; set; }
    }
Cœur
  • 37,241
  • 25
  • 195
  • 267
Justin CI
  • 2,693
  • 1
  • 16
  • 34
  • how are you changing the culture? please share your approach / code for changing . And please share your code declaration of View Model with use of LocalizedDisplayFormatAttribute. – Dave Alperovich May 27 '15 at 14:22
  • @Dave Alperovich: I have edited my question – Justin CI May 28 '15 at 05:24
  • Please help me out. I'm confused by some wording in the question. You say that **But when I change the culture it does not affecting the NullDisplayText, it's setting only once** I read that to mean that your app reads the culture setting once and sets the `NullDisplayText` once. That is probably NOT what you mean, since the culture will only be set once, so there seems to be no problem. – Dave Alperovich May 28 '15 at 05:30
  • I'm guessing you mean the NullDisplayText should be set differently than it is. If so, please explain with greater detail. What is the culture you get and what is expected value of NullDisplayText vs value you get. – Dave Alperovich May 28 '15 at 05:31
  • @DaveAlperovich : my default culture used is "en-US",Once i change the culture to es-AR and load the pages its working fine,but when i change the culture back to en-US fields are not getting converted back. – Justin CI May 28 '15 at 05:40
  • Ok, this is good. I understand you a little bit better. Please explain how and when are you changing the culture. Are you changing a setting in your browser so the incoming headers are changed? – Dave Alperovich May 28 '15 at 05:47
  • @DaveAlperovich : In my application there is a option to change language,when user select and change the language,the culture code is saved in a cookie . In Global.asax I have added Application_AcquireRequestState (Please refer above code in question) in which culture is changed. – Justin CI May 28 '15 at 05:50
  • I see. This looks correct so far. I wonder, have you gone used the debugger or any other way to check the value of `ci.Name` ? – Dave Alperovich May 28 '15 at 06:25
  • value changes correctly.When I load page for the first time DisplayFormatAttribute works fine. When I reload the page after culture change Values is displayed in previous culture format. LocalizedDisplayFormatAttribute is only called once. – Justin CI May 28 '15 at 06:54
  • Everything you have done looks correct. I suspect your Culture is being cached. Try adding overriding `GetVaryByCustomString` in Global.asax... Or turning off output cache http://visitmix.com/writings/using-varybycustom-with-outputcache-in-asp-net-mvc-to-support-caching-for-logged-in-users – Dave Alperovich May 28 '15 at 07:08
  • This didn't work for me.Is there any Idea to change Null to a Custom message from resource – Justin CI May 28 '15 at 08:00
  • You can use a custom route. And action filter. – Dave Alperovich May 28 '15 at 08:03
  • may be you can get some idea from this post? http://stackoverflow.com/questions/1560796/set-culture-in-an-asp-net-mvc-app he is using this localization method and added a route parameter that sets the culture and language – V-SHY May 30 '15 at 02:36
  • I'm looking at the MSDN and they use it like this: [DisplayFormat(ConvertEmptyStringToNull = true, NullDisplayText = "[Null]")] Have you tried the attribute similar to this? – Kala J Jun 02 '15 at 21:41
  • normally it's working.When I change the culture and load that screen again. Message is shown from previous resource culture. – Justin CI Jun 03 '15 at 05:12
  • If you take that `try` `catch` out of `AcquireRequestState`, when you change back cultures do you get an exception? – Mathew Thompson Jun 05 '15 at 11:15
  • No...culture change is working fine. – Justin CI Jun 05 '15 at 11:17
  • @JustinCI Unless I'm missing something, why can't you just use the `Display` attibute? `[Display(Name = "Item", ResourceType = typeof(MyResources))]` – Mathew Thompson Jun 05 '15 at 11:21
  • @ mattytommo If I use Display Attribute , IAm not clear how to format NULL values – Justin CI Jun 05 '15 at 11:31

1 Answers1

7

Here's some insight into what's wrong.

Mvc is using a form of TypeDescriptor (AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type)) to read attributes off your models. The TypeDescriptors are caching information about properties and attributes. So your LocalizedDisplayFormatAttribute attribute is only getting instantiated once, in terms of the TypeDescriptor api's, which means that the resource information is only read once (at construction). See the bottom of the answer for references.

Solutions that dont work

  1. The blink reaction is to just pull the latest resource information from your LocalizedDisplayFormatAttribute NullDisplayText every time it is accessed via the getter. Unfortunately DisplayFormatAttribute NullDisplayTextis not virtual, and you are shadowing the property with a new keyword. This won't work from a polymorphic dispatch perspective (Mvc is calling the getter as a DisplayFormatAttribute instead of a LocalizedDisplayFormatAttribute, so your shadowed property is never being called)

  2. I tried TypeDescriptor.Refresh() overloads https://msdn.microsoft.com/en-us/library/z1ztz056(v=vs.110).aspx and had no luck

The remaining options that I'm aware of are not as convenient or not amazing in one way or another. Probably not recommended.

  1. Some way to successfully refresh the AssociatedMetadataTypeTypeDescriptionProvider TypeDescriptors. I'm not too familiar with these, so there could totally be one. I'm just not seeing one currently.
  2. Rework or create a ModelMetadataProvider of your own. Everything is open source, so its possible, though I'm not sure I would recommend it except as a last resort.
  3. You could possibly work with the TypeDescriptor api's to force a re-instantiation of your attribute whenever it is being pulled. See https://stackoverflow.com/a/12143653/897291.
  4. Model the needed properties directly in MVC (as model properties, instead of attributes). Could either be entirely new properties, or you could have some sort of logic within your original properties, that when null return something else. Awkward to deal with though.

Nothing great, I know. Maybe this will give someone else enough insight to come up with something better?

To verify this yourself, see https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/DataAnnotationsModelMetadataProvider.cs CreateMetaData method which calls SetFromDataTypeAndDisplayAttributes method setting result.NullDisplayText = displayFormatAttribute.NullDisplayText;

DataAnnotationsModelMetadataProvider extends AssociatedMetadataProvider which is repsonsible for passing in the attributes. See https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/AssociatedMetadataProvider.cs GetMetadataForProperty method as an example.

Community
  • 1
  • 1
Kenneth Ito
  • 5,201
  • 2
  • 25
  • 44