2

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...

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

2 Answers2

2

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.

Community
  • 1
  • 1
Hugo Migneron
  • 4,867
  • 1
  • 32
  • 52
  • Thanks - sure I could do this - but doing this for several classes, and up to 3 groups of properties for each class seems overly tedious. And yes - I was thinking about using reflection in that attribute - knowing it's attached to the `Name` property, it could then go look for the `Name_De` and so forth properties and return the "correct" value. But I haven't found a suitable base class for `Attribute` that would somehow allow me to get informed of when the getter of that property is being invoked.... – marc_s Aug 05 '16 at 17:17
  • Actually, those are two separate files - but both are `public partial class Module` so the C# compiler will merge these together into a single entity in the end – marc_s Aug 05 '16 at 17:18
  • 1
    Yeah it is tedious for sure. Custom Attributes are metadata on top of your property though, so you won't be able to do `Object.Name` and have that call your custom attribute code. I'll try and think of another way to do this without automapper – Hugo Migneron Aug 05 '16 at 17:58
1

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.

Hugo Migneron
  • 4,867
  • 1
  • 32
  • 52
  • Thanks - yes, that's just about what I've achieved so far - it works, and it's simpler than having to write the tedious code for each and every single property. So it seems ASP.NET Webforms doesn't have any way of handling actions with attributes (like the `HandleError` attribute in ASP.NET MVC can do) - correct? Thanks so much for your inputs! – marc_s Aug 05 '16 at 19:43
  • Correct. ActionFilters in MVC are actually kind of misleading because they seem like attributes that execute code (which is what you are trying to achieve here). This is not so. ActionFilters work because the MVC engine triggers events that look for the attributes and execute code depending on their presence (or not). – Hugo Migneron Aug 05 '16 at 19:54