Automagically handling multi-lingual properties in a .NET class - c#

I am trying to handle multiple languages in an ASP.NET Webforms (.NET 4.5, C#) application of mine.
Basically, some of my entities in my SQL Server 2012 database have properties like Name or Description which exist in three languages - German, French, Italian.
Those are stored as columns Name_De (German), Name_Fr (French), and Name_It (Italian).
When I create my .NET objects from the database, of course, I also get those three properties in my entity class. But for displaying on screen, in a grid for instance, it would be nice if I could somehow "magically" always show the "right" language. This should be based on the Thread.Current.CurrentUICulture.TwoLetterISOLanguageName (which returns de, fr or it, depending on the browser's language preferences).
So I was hoping to somehow be able to create e.g. a .NET attribute that would allow me to do something like this:
Base "Module" entity - generated from existing SQL Server database:
public partial class Module
{
public string ModuleCode { get; set; }
public string Name_De { get; set; }
public string Name_Fr { get; set; }
public string Name_It { get; set; }
... // other properties
}
Partial extension in a separate file
public partial class Module
{
[Multilingual]
public string Name { get; set; }
}
The base idea is: I can access the Module.Name property, and depending on the current setting of CurrentUICulture, either the value of Name_De, Name_Fr or Name_It would be fetched, when I access the getter of the Name property.
Can something like this be done in C# ? I have looked at a lot of custom attribute implementations, but nothing ever seemed to be doing something like this...

Assuming you are using two separate entities (one generated by your SQL entities and one "business entity" which only contains a Name property), are you open to using something like AutoMapper ?
If you are, then you could tweak your resolve function to map the entity depending on the current thread culture.
switch(Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToUpperInvariant())
{
case "DE":
return dto.Name_De;
case "FR":
return dto.Name_Fr;
// ...
default :
return String.Empty;
}
which would work for your scenario.
If this is a solution that could work for you, I think this question is very close to what you're looking for : Automapper Mapping for Localization Resolver in a Multi-Language Website
If you do go down the custom attribute route, you will have to deal with Reflection stuff and string parsing I'm afraid. AFAIK, there is no built in way to do this with the localization functions provided by .NET. AutoMapper will hide that from you.
The problem with custom attributes in this case is that you are still trying to access the Name property. You are trying to "shadow" the default behaviour of the property by making it access other properties. If I understand correctly you want the Multilingual custom attribute to turn your property into :
public String Name
{
get
{ switch(Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToUpperInvariant())
{
case "DE":
return dto.Name_De;
case "FR":
return dto.Name_Fr;
// ...
default :
return String.Empty;
}
}
}
If that's correct, then you won't be able to do that easily with attributes, simply because the attribute will never be aware of the existence of the Name_De property.

Other option that still isn't quite what you're looking for :
void Main()
{
Module mod = new Module();
mod.Name_De = "name";
mod.Name_Fr = "nom";
// This is the unfortunate nasty bit. I address the property using its name
// in a String which is just bad. I don't think there is a way
// you will be able to address the ".Name" property directly and have
// it return the localized value through your custom attribute though
String localizedValue = mod.GetLocalizedProperty("Name");
}
[AttributeUsage(AttributeTargets.Property)]
public sealed class MultilingualAttribute : Attribute
{
public MultilingualAttribute()
{
}
}
public static class ModuleExtensions
{
public static String GetLocalizedProperty(this Module module, String propName)
{
var type = typeof(Module);
var propInfo = type.GetProperty(propName);
// Make sure the prop really is "Multilingual"
if(Attribute.IsDefined(propInfo, typeof(MultilingualAttribute)))
{
String localizedPropName = propInfo.Name;
switch(Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToUpperInvariant())
{
case "DE":
localizedPropName += "_De";
return type.GetProperty(localizedPropName).GetValue(module, null).ToString();
case "FR":
localizedPropName += "_Fr";
return type.GetProperty(localizedPropName).GetValue(module, null).ToString();
}
}
return String.Empty;
}
}
public class Module
{
public String Name_De {get; set;}
public String Name_Fr {get; set;}
[Multilingual]
public String Name {get; set;}
public Module()
{
}
}
I don't know of a more powerful way to use custom attributes for what you're looking for unfortunately. Quite frankly, I don't think this is a good solution, only posted because I was trying to see what I could do with custom attributes. There is no real point in using this code over a more "normal" property which would do the same thing in a clearer way (without attributes). As you say in your original question, your goal is to intercept the call to the Name property and this doesn't achieve it.

Related

Can DisplayNameAttribute extension with XML source update in runtime?

This might be more of a question related to how .NET Framework works, than looking for an actual solution. Reason is I would like to know if this is something I should pursue in fixing, or try something else entirely. I did some searching, but couldn't find the right answer in my opinion.
I am working on an ASP.NET MVC5 application that utilizes a translation provider with an XML file as it source. In some scenarios I use a DisplayNameAttribute extension to decorate model properties to provide translations. It is made by referencing the solution here: https://stackoverflow.com/a/9723620/1501132
This is my implementation:
[AttributeUsage(AttributeTargets.Property)]
public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
public LocalizedDisplayNameAttribute(string key, string page = null) : base(FormatMessage(key, page))
{
}
private static string FormatMessage(string key, string page = null)
{
if (!string.IsNullOrWhiteSpace(key) && string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key);
}
else if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key, page);
}
else
{
return string.Empty;
}
}
}
The "TextGetter" is a separate library that handles fetching strings from the XML data source.
The attribute extension is used like so:
[LocalizedDisplayName("Timestamp", "/report")]
public DateTimeOffset Timestamp { get; set; }
The website also has a feature where a super user can edit the translation XML file, in case some translations are wrong or missing. Usually an edit in the XML file is visible immediately, except for properties with this particular attribute. I know that normally when using DisplayName attribute with a hardcoded value can not be changed because it is compiled, though I was under the assumption that since this uses an XML file as reference, I believed that if the XML was changed it would be reflected immediately in this case as well. But that seems not to happen.
Being able to change translations on the fly is an important feature; should I seek some other solution? I can set the property names with translations in the views, which is working, but that will entail a LOT of refactoring, and keeping it as annotations is just more neat.
I don't really know where to take it from here.
Found a solution in the meantime, and just putting it out there if anyone stumbles across it. So this is what you should do, if you want to make an attribute that derives from DisplayName used for localization, and on top of that have a localization source that can change and update during runtime:
[AttributeUsage(AttributeTargets.Property)]
public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private readonly string _key;
private readonly string _page;
public LocalizedDisplayNameAttribute(string key, string page = null) : base(key)
{
this._key = key;
this._page = page;
}
public override string DisplayName => this.FormatMessage(this._key, this._page);
private string FormatMessage(string key, string page = null)
{
if (!string.IsNullOrWhiteSpace(key) && string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key);
}
else if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(page))
{
return TextGetter.GetText(key, page);
}
else
{
return string.Empty;
}
}
}
The contents of "FormatMessage" can be whatever you want really, just insert there whatever code you need to fetch your translated string.

Alternative name of a field for UI purposes

I want to give a field (or property) an alternative name that can be shown in the user interface by using reflection. I have found the attribute DescriptionAttribute, but is it really for this purpose or am I better off using something else?
Is this attribute somehow restricted to Windows Forms and its property view, or is it UI framework independent? (currently I am on Windows Forms for the project, but it might change in the future so I don't want to be stuck with it)
public class MyCustomZoo
{
[Description("Cute Mouse")]
public MyCustomAnimal CuteMouse;
[Description("Frightning Lion")]
public MyCustomAnimal FrightningLion;
}
I have found my preferred solution in one of the answers here.
using System.ComponentModel.DataAnnotations;
// ...
public class MyCustomZoo
{
[Display(Name = "Cute Mouse")]
public object CuteMouse;
[Display(Name = "Frightning Lion")]
public int FrightningLion;
}
public static string FieldDisplayName(FieldInfo field)
{
DisplayAttribute da = (DisplayAttribute)(field.GetCustomAttributes(typeof(DisplayAttribute), false)[0]);
return da.Name;
}
// ...
// c# identifier names, results in {"CuteMouse","FrightningLion"}
List<string> fieldNames = typeof(MyCustomZoo).GetFields().Select(field => field.Name).ToList();
// "human readable" names, results in {"Cute Mouse","Frightning Lion"}
List<string> fieldDisplayNames = typeof(MyCustomZoo).GetFields().Select(field => FieldDisplayName(field)).ToList();
Don't forget to add a reference to assembly System.ComponentModel.DataAnnotations.
Note: System.ComponentModel.DisplayNameAttribute could be used as well (thanks to Kieran Devlin) if properties is what you want to tag. With pure fields it doesn't work however.

Dynamic Validation in Web API

I am looking to validate a particular request depending on values in a database. It's a complex scenario, but I will try to simplify it in an example.
Say I have the following model:
public class CustomerModel
{
public int AgencyId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
When a POST request comes in, I need to make a call to get certain requirements for the AgencyId being passed.
var requirements = _repository.GetRequirementsForAgency(model.AgencyId);
The information I would get back from the database would tell me which properties are required, which may be different for each agency. For instance, one agency might require Name and Age where as another one might only require Name. The requirements object would look something like this:
public class Requirement
{
public string PropertyName { get; set; }
public bool IsRequired { get; set; }
}
So, my question is what would be the best way to validate this model before it gets submitted to the database? Ideally, I would like to give the Agency the ability to change these requirements, therefore, I would like to avoid hard coding validation if possible.
My first thought was to call a list of requirements and then do a foreach over each requirement searching by PropertyName and then checking to see if there was a value or not, but I wasn't sure if this was the best way.
I then looked into Data Annotations, but did not find a way to add attributes at run time.
You can use Fluent Validation library and implement custom validator
public class CustomerModelValidator : AbstractValidator<CustomerModel>
{
private readonly IRepository _repository;
public RegisterModelValidator(IRepository repository)
{
this._repository= repository;
RuleFor(x => x.AgencyId).GreaterThan(0).WithMessage("Invalid AgencyId");
RuleFor(x => x.Age).GreaterThan(0).WithMessage("Invalid Age");
Custom(c =>
{
var requirements = _repository.GetRequirementsForAgency(model.AgencyId);
\\validate each property according to requirements object.
\\if (Validation fails for some property)
return new ValidationFailure("property", "message");
\\else
return null;
});
}
}
If you use dependency injection in your project (which i strongly advice), you will have to inject relevant IRepository into an attribute. Otherwise you can just create/use a specific repository in your attribute.
A really nice thing is when you properly register your validator you will be able to validate you model with default if (ModelState.IsValid) check

Is it wrong to dynamically add "data-val" and "data-val-required" in the View?

I have a ViewModel that I can decorate with the [Required] attribute (see below). I've come to the point where I need to let the client control which fields are required or not. They can configure this trough XML and all this info is stored in the Model when it's first created. Now I have fields that are not decorated with [Required] but still need to get validated (as per "user settings") before submitting (for example the Phone field).
public class MyBusinessObjectViewModel
{
[Required]
public string Email { get; set; } //compulsory
public string Phone { get; set; } //not (yet) compulsory, but might become
}
If the user will not enter the Phone number, the data will still get posted. Wanting not to mess with custom validators, I just add the "data-val" and "data-val-required" attributes to the Html, like this:
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("data-val", "true");
dict.Add("data-val-required", "This field is required.");
#Html.TextBoxFor(x => x, dict);
This forces the client side validation for all the properties that are dynamically set as required. Is this good practice? What kind of side effects can I expect?
You should look into extending the meta model framework with your own metadata provider to do the actual binding between your site's configuration and the model metadata. You can actually set the required property flag to true on the property model metadata during the metadata creation process. I can't remember for sure whether this causes the built in editor templates to generate the attribute, but I think it does. Worst case scenario you can actually create and attach a new RequiredAttribute to the property, which is a tad bit kluggy, but works pretty well in certain scenarios.
You could also do this with IMetadataAware attributes, especially if Required is the only metadata aspect your users can customize, but the implementation really depends on what you're trying to do.
One major advantage of using a custom ModelMetadataProvider in your specific case is that you can use dependency injection (via ModelMetadataProviders) to get your customer settings persistence mechanism into scope, whereas with the data attribute you only get to write an isolated method that runs immediately after the metadata model is created.
Here is a sample implementation of a custom model metadata provider, you'd just have to change the client settings to whatever you wanted to use.
UPDATED but not tested at all
public class ClientSettingsProvider
{
public ClientSettingsProvider(/* db info */) { /* init */ }
public bool IsPropertyRequired(string propertyIdentifier)
{
// check the property identifier here and return status
}
}
public ClientRequiredAttribute : Attribute
{
string _identifier;
public string Identifier { get { return _identifer; } }
public ClientRequiredAttribute(string identifier)
{ _identifier = identifier; }
}
public class RequiredModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
ClientSettings _clientSettings;
public RequiredModelMetadataProvider(ClientSettings clientSettings)
{
_clientSettings = clientSettings;
}
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
// alternatively here is where you could 'inject' a RequiredAttribute into the attributes list
var clientRequiredAttribute = attributes.OfType<ClientRequiredAttribute>().SingleOrDefault();
if(clientRequiredAttribute != null && _clientSettings.IsPropertyRequired(clientRequiredAttribute.Identifier))
{
// By injecting the Required attribute here it will seem to
// the base provider we are extending as if the property was
// marked with [Required]. Your data validation attributes should
// be added, provide you are using the default editor templates in
// you view.
attributes = attributes.Union(new [] { new RequiredAttribute() });
}
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
// REMOVED, this is another way but I'm not 100% sure it will add your attributes
// Use whatever attributes you need here as parameters...
//if (_clientSettings.IsPropertyRequired(containerType, propertyName))
//{
// metadata.IsRequired = true;
//}
return metadata;
}
}
USAGE
public class MyModel
{
[ClientRequired("CompanyName")]
public string Company { get; set; }
}
public class MyOtherModel
{
[ClientRequired("CompanyName")]
public string Name { get; set; }
public string Address { get; set; }
}
Both of these models would validate the string "CompanyName" against your client settings provider.
Not wanting to mess with custom validators, so you messed in the View obfuscating the logic of your validation by removing it from the place where it is expected to be found.
Really, don't be afraid of creating a custom attribute validator. What you are doing right now is getting a technical debt.

The "Model" in .Net MVC

How do data frameworks such as Linq 2 SQL, ADO.Net Data Entities and DataSets relate to the "Model" as defined by .Net MVC.
The reason I ask is I'm trying to learn the ins and outs of the .Net framework without relying on many of the tools that make it easy and hide the workings from you.
The "model" I'm building in my exploratory app is simply PostgreSQL commands to update the database. I'm purposefully not using a data "framework".
I'm finding that much of the functionality that comes as part of the .Net MVC framework isn't working for me. Stuff like UpdateModel() and anything related to ModelState doesn't seem to acknowledge what's going on.
Is much of that functionality tied to using Linq 2 SQL or ADO.Net Data Entities? If so, that's fine, I just don't quite understand the relationship yet.
Unlike Views and Controllers, there's not really any restrictions on what the Model is in an ASP.NET MVC app. It just enables you to model the data in your app and clearly and safely express your intent without having to resort to using dictionaries such as ViewData to pass data around.
In terms of the model-related functionality, I believe (not 100% sure) that it is based on having public properties on your model objects. If you call UpdateModel or its relatives, it will set public properties on the model object based on the form data etc. All it does it set properties on that in-memory object if it finds appropriate ones that match form inputs etc. You still need to include the logic to actually persist that to a database or whatever else it is you want to do.
Hope this points you in the right direction at least.
Basically your "Model" object needs to have a parameterless constructor and public get/set properties for it to function easily with the DefaultModelBinder.
Tip: I believe that if you fail to define a parameterless constructor, an empty one is inferred for you (just so you don't freak out: "ahh! i don't hav a parameterless constructor").
So this would generally work fine:
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public string Number { get; set; }
public string Email { get; set; }
}
Figured it out. For the sake of completeness...
Ok, so there is a "minimum standard" for models in .Net.
This is NOT accepted as a model:
namespace MVCApplication.Models
{
public class Person
{
public int ID;
public string Name;
public string Title;
public string Description;
public string Phone;
public string Address;
public string Country;
public Person()
{
}
}
}
This IS accepted as a model:
namespace MVCApplication.Models
{
public class Person
{
private int _ID;
private string _Name;
...
public Person() {}
public int ID { get{ return _ID } set{ this._ID = value } }
public int Name { get{ return _Name } set{ this._Name = value } }
...
}
}
I can't say I completely understand why, but at least now I know.

Categories