Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customized DisplayFormatAttribute only setting once

Tags:

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; }     } 
like image 994
Justin CI Avatar asked May 19 '15 12:05

Justin CI


1 Answers

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.

like image 179
Kenneth Ito Avatar answered Sep 24 '22 18:09

Kenneth Ito