Publié dimanche 8 juillet 2012 19:53 par Arnault Nouvel

[SharePoint] Unification des fichiers de ressources multilingues

sharepoint-server

L’objet de ce post est de proposer une méthode permettant de stocker les resources multilingues uniquement dans le répertoire 14\Resources, afin d’éliminer les problématiques liées aux ressources stockées dans webapp\App_GlobalResources.

Lorsque l’on doit gérer des libellés applicatifs traduisibles dans un projet SharePoint, on les définit dans des fichiers resx que l’on inclue dans les sources de notre projet. Toutefois, selon le contexte et la manière que l’on utilise pour faire référence à ce libellé, on devra déployer le fichier resx dans le répertoire 14\Resources (pour les libellés SharePoint) ou 14\CONFIG\Resources (pour les libellés ASP.NET utilisés dans les aspx et ascx exclusivement, le fichier étant copié par SharePoint dans le dossier App_GlobalResources de l’application web).

Bien que le principe soit facile à comprendre (cet article résume tout de manière très claire) et à mettre en oeuvre, je trouve dommage de devoir gérer 2 fois plus de fichiers resx que nécessaire, et de toujours avoir à se demander dans quel fichier mettre un libellé. Certains développeurs ont trouvé des moyens de déployer le même fichier resx aux 2 endroits via des tâches planifiées ou en bidouillant des fichiers cachés du projet Visual Studio, mais cela ne résout pas le problème fichiers stockés dans App_GlobalResources : ces derniers doivent être traités spécialement via ligne de commande ou timer job afin d’être copiés du répertoire 14\CONFIG\Resources vers le répertoire App_GlobalResources de l’application web qui l’exploite. Cela ralentie notamment le développeur au quotidien lorsqu’il doit ajouter de nouveaux libellés, car il doit soit lancer des lignes de commandes soit déclencher des timer jobs pour permettre la propagation des fichiers.

J’ai mis au point une solution qui permet de n’utiliser que les fichiers resx du répertoire 14\CONFIG. Ceci est rendu possible par le développement d’un ExpressionBuilder custom qui ira chercher les ressources avec l’API SharePoint. Restera à enregistrer la classe correspondante dans le web.config, puis à utiliser l’expression builder (que je nomme ici Resources14) dans nos pages aspx et controles ascx avec <% $Resources14:monfichier:maresource; %>  plutot que le traditionnel <% $Resources:monfichier:maresource; %>

Code de l’ExpressionBuilder

[ExpressionPrefix("Resources14")]

public class Resources14ExpressionBuilder : ExpressionBuilder

{

 

        public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)

        {

            CodeMethodInvokeExpression codeMethodInvokeExpression = new CodeMethodInvokeExpression();

            codeMethodInvokeExpression.Method.TargetObject = new CodeTypeReferenceExpression(typeof(Resources14ExpressionBuilder)); ;

            codeMethodInvokeExpression.Method.MethodName = "GetLocalizedResource";

            codeMethodInvokeExpression.Parameters.Add(new CodePrimitiveExpression(entry.Expression));

            return codeMethodInvokeExpression;

        }

 

        public override bool SupportsEvaluate

        {

            get { return true; }

        }

 

        public override object EvaluateExpression(object target, BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)

        {

            return GetLocalizedResource(entry.Expression);

        }

 

        public static String GetLocalizedResource(String key)

        {

            return SPUtility.GetLocalizedString("$Resources:" + key, null, SPContext.Current.Web.Language);

        }

 

 

}

 

La méthode GetCodeExpression est appelée lorsque ASP.NET parse l’expression builder. Une chose importante à savoir est que cette méthode renvoie une expression (au sens CodeDom) qui sera récupérée et mise en cache pour chaque entrée (ce qui est après le caractère ‘:’) différente. L’expression mise en cache sera par contre évaluée à chaque fois que l’expressionbuilder sera parsé sur une page.. Il est donc important que GetCodeExpression soit “language agnostic”, et retourne une référence à une méthode qui elle, tiendra compte de la langue. Ici, GetCodeExpression renvoie une sorte de pointeur vers la méthode GetLocalizedResource.

 

Modification du web.config

 

Afin de pouvoir consommer l’expression builder dans nos développements, il est nécessaire qu’il soit enregistré dans le web.Config.

Pour ce faire, on doit ajouter un noeud dans la section expressionBuilders comme ci-dessous :

 

image

Pour bien faire, il faudra faire cet enregistrement par code en utilisant la classe SPWebConfigModification afin d’être certain que la modification soit effectuée de la même manière sur tous les frontaux de la ferme.

Ci dessous un exemple d’implémentation du feature receiver d’une feature de scope web app :

public class WebApp_RegisterExpressionBuilderEventReceiver : SPFeatureReceiver
{
 
        private const String OWNER = "ANO.Resources14_WebApp_RegisterExpressionBuilderEventReceiver";
 
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webapp = properties.Feature.Parent as SPWebApplication;
 
            //Cleanup
            RemoveWebConfigEntries(webapp, OWNER);
 
            //Adds the expressionBuilder
            SPWebConfigModification mod = new SPWebConfigModification();
            mod.Path = "configuration/system.web/compilation/expressionBuilders";
            mod.Name = "add[@ expressionPrefix='Resources14']";
            mod.Sequence = 1;
            mod.Owner = OWNER;
            mod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            mod.Value = String.Format(@"<add expressionPrefix=""Resources14"" type=""{0}"" />", typeof(Resources14ExpressionBuilder).AssemblyQualifiedName);
 
            //registers the web.config modification to the web app
            webapp.WebConfigModifications.Add(mod);
            webapp.Update();
 
            //updates the web.config on each front end
            SPWebService.ContentService.ApplyWebConfigModifications();
        }
 
        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webapp = properties.Feature.Parent as SPWebApplication;
 
            //Removes the expression builder registration from the web.config
            if (RemoveWebConfigEntries(webapp, OWNER))
                SPWebService.ContentService.ApplyWebConfigModifications();
        }
 
        private static Boolean RemoveWebConfigEntries(SPWebApplication webapp, String owner)
        {
            Boolean updated = false;
 
            //Modifications to remove
            List<SPWebConfigModification> modsToRemove = new List<SPWebConfigModification>();
 
            //Identifies modifications to remove
            foreach (SPWebConfigModification mod in webapp.WebConfigModifications)
                if (mod.Owner == owner)
                    modsToRemove.Add(mod);
 
            //Removes modifications one by one
            foreach (SPWebConfigModification mod in modsToRemove)
            {
                webapp.WebConfigModifications.Remove(mod);
                updated = true;
            }
 
            //Updates if necesssary
            if (updated)
                webapp.Update();
 
            return updated;
        }

}

Une fois cette feature déployée et activée sur une web app, on peut consommer notre expression builder.

Utilisation de l’expression builder

On peut maintenant utiliser notre expression builder dans les pages aspx et les controles ascx de notre projet.

Exemple :

image

Résultat :

image

Conclusion

L’utilisation de cette méthode nécessite un développement spécifique qu’il faudra embarquer dans les sources du projet, mais il me parait intéressant de le faire pour les raisons suivantes :

  • Un seul jeu de resx à maintenir, et à faire traduire
  • Plus besoin de se demander dans quel resx doit aller un libellé
  • Plus de problématique de déploiement spécifique aux resx qui doivent aller dans le répertoire App_GlobalResources de la web app
  • Gain de temps notable durant la phase de développement, vis à vis de la problématique de déploiement 
  • Si vous souhaitez récupérer les sources du projet, elles devraient téléchargeables être en pièce jointe de ce post : ANO.Resources14.zip


    Arnault Nouvel
    MVP SharePoint Server

    Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

    Classé sous ,
    Attachment(s): ANO.Resources14.zip

    # re: [SharePoint] Unification des fichiers de ressources multilingues @ lundi 9 juillet 2012 10:35

    Ce dev était dans ma todo list.

    Merci pour ce gain de temps Arnaud :)

    Gribouillon

    # re: [SharePoint] Unification des fichiers de ressources multilingues @ lundi 9 juillet 2012 22:54

    de rien seb :)

    Arnault Nouvel


    Les 10 derniers blogs postés

    - Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

    - Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

    - Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

    - Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

    - Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

    - Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

    - Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

    - Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

    - SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01

    - SharePoint Online: Script PowerShell pour supprimer une colonne dans tous les sites d’une collection par Blog Technique de Romelard Fabrice le 11-27-2018, 18:01