SharePointCommunity
Die deutschsprachige Community für SharePoint, Microsoft 365, Teams, Yammer und mit Azure
How to: Ändern des Variation Root Landing Verhaltens

Blogs

Oliver Wirkus

Syndication

Certifications

Web 2.0

http://smits-net.de/img/linkedin_button.gif

XING

http://i80.photobucket.com/albums/j191/mikesamerica/twitter_button_zpsee74f711.png

 

 Add to Technorati Favorites

 

 Delicious Bookmark this on Delicious

 

  Locations of visitors to this page

 

SharePoint bietet in Form von Variations Unterstützung bei der Erstellung von mehrsprachigen Portalen an. Bevor wir uns ansehen, wie diese Unterstützung bei SharePoint genau aussieht und wo das Problem liegt, dass mich zum Schreiben dieses Artikels veranlasst hat, schauen wir uns zuerst an, wie man manuell ein mehrsprachiges Portal erzeugen würde.

Zuerst würde man eine Web-Struktur anlegen und festlegen, dass in dieser Web-Struktur alle Inhalte z.B. in deutsch hinterlegt werden. Dies gilt für alle Beschriftungen (z.B. Navigation) und die Inhalte (also z.B. Artikel und News). Nun würde man dieses deutsche Web als Ausgang für eine Kopie verwenden. Diese Kopie wird per Sprachpaket auf z.B. Englisch konfiguriert und soll später die News und Artikel der deutsche Seite in einer englischsprachigen Version enthalten. Im Prinzip hat man nun zwei parallele Webs – eines in deutsch und eines in englisch. Die Inhalte (also die News und die Artikel) werden in deutsch im deutschen Web geschrieben und publiziert. Danach wird die Seite vom deutschen Web ins englische Web kopiert und die Inhalte übersetzt. Auf diese manuelle Weise hält man beide Webs konsistent. Wenn man diese beiden Webs jetzt noch unter ein gemeinsames Web kopiert und dort dem Benutzer die Auswahl zwischen deutsch und englisch anbietet, hat man eine einfache (aber arbeitsintensive) Version eines mehrsprachigen Webauftritts.

Zugegeben – das war jetzt ein sehr einfaches Beispiel mit viel manuellem Nachbearbeitungs- und Pflegeaufwand, aber im Prinzip funktioniert das Verfahren bei den SharePoint Variations ähnlich.

Um ein mehrsprachiges Portal mit SharePoint 2007 zu erstellen, geht man zu Beginn ähnlich vor, wie in dem einfachen Beispiel beschrieben. Es wird zuerst eine Webstruktur (mit Child-Webs) erstellt. Danach legt man fest, dass SharePoint Variations verwenden und wie diese aufgebaut werden sollen. Dies macht man mit diesem Dialog in SharePoint:

image

 

Nachdem wir nun festgelegt haben, dass wir Variations verwenden wollen, müssen wir festlegen, welche Sprache unsere Kopien des ursprünglichen Webs verwenden sollen. Dazu werden sogenannte Variation Labels –eines pro verwendeter Sprache- erzeugt. Nicht vergessen: auch für die Ausgangssprache muss ein Label erzeugt werden und dies muss als Source Variation gekennzeichnet werden. Diese Labels, die hier erzeugt werden, sind später die fremdsprachlichen Webs, die vom ursprünglichen Web kopiert wurden. Zum Anlegen von Variation Labels bietet SharePoint diesen Dialog an:

image

Nachdem für jede Sprache ein Variation Label angelegt wurde (wichtig: eine sogenannte Source Variation muss definiert sein), kann die neue Struktur –die sogenannte Variation Hierarchie- erzeugt werden. Dies kann man im gleichen Fenster erledigen – oben in diesem Fenster sollte dazu dieser Button angezeigt werden:

image

 

Wenn alles problemlos durchgelaufen ist (je nach Größe des ursprünglichen Webs und der Anzahl an Variation Labels kann das eine Weile dauern), hat SharePoint eine neue Struktur erstellt. Natürlich muss nicht gleich zu Beginn die vollständige Struktur vorliegen. Auch nach Erzeugen der Variation Hierarchie können noch Webs und Pages hinzugefügt werden. Je nach Einstellung werden dann automatisch die entsprechenden Variations ergänzt (siehe erster Screenshot).

Soviel in aller Kürze zum Erstellen von Variations – eigentlich wollten ich mich in diesem Artikel nicht um das Erstellen von Variations kümmern, sondern auf ein Problem bei deren Benutzung aufmerksam machen. Schauen wir uns dazu an, wie diese neu angelegten Variations denn nun funktionieren. Gehen wir einmal davon aus, wir hätten eine Source Variation in deutsch und ein Variation Label in englisch. Wenn nun ein Benutzer mit einem Browser, der auf deutsch eingestellt ist, auf unser Web surft, wird er automatisch von SharePoint auf die deutsche Quell-Variation umgeleitet. Stellt dieser Benutzer nun statt deutsch auf englisch um, leitet ihn SharePoint auf das englische Web um. Soweit alles in Ordnung – funktioniert exakt, wie erwartet!

Übrigens: im Internet Explorer stellt man die Sprachen folgendermaßen um:

image

In FireFox 3.5 werden die Sprachen hier eingestellt:

 image

 

Was passiert nun aber, wenn wir z.B. als Sprache “Französisch” einstellen? Ein französisches Variation Label haben wir nicht erzeugt und aus diesem Grund leitet SharePoint in diesem Fall auf die Quell-Variation um. Dies ist zwar das Standard-Verhalten und irgendwie auch nachvollziehbar, aber im vorliegenden Fall wäre es vielleicht doch besser gewesen, einen französischsprachigen Besucher auf die englischen Seiten umzuleiten. Besser wäre es gewesen, für die Source Variation als Sprache “englisch” zu wählen. So wäre sichergestellt, dass deutschsprachige Besucher auf die deutschen Seiten, alle anderen Besucher aber immer auf die englischen Seiten umgeleitet werden.

Wem dies schon passiert ist (und ich gebe zu, dass es mir bei meinem ersten Variation-Projekt auch passiert ist), der hat ein größeres Problem: die Variation Hierarchie ist erstellt und lässt sich leider nachträglich nicht mehr ändern!

image

 

Wenn man aber ein wenig recherchiert und sich den Variation-Mechanismus von SharePoint etwas genauer ansieht, dann findet man eine Möglichkeit, wie man Einfluss auf das sogenannte Variation Root Landing –also den Automatismus, der für das sprachabhängige Umleiten verantwortlich ist- nehmen kann. Am Beispiel von Publishingwebs möchte ich hier zeigen, wie man das machen kann.

Da wir Änderungen an den Dateien VariationRootPageLayout.aspx und VariationsRootLanding.ascx vornehmen müssen, sollten wir diese Seite zuerst einmal sichern. Die VariationRootPageLayout.aspx findet sich im Verzeichnis \<Program Files>\Common Files\Microsoft Shared Debug\Web Server Extensions\12\Template\FEATURES\PublishingResources und die VariationsRootLanding.ascx findet sich im Verzeichnis \<Program Files>\Common Files\Microsoft Shared Debug\Web Server Extensions\12\Template\ControlTemplates.

Nun erstellen wir von der VariationsRootLanding.ascx eine Kopie und benennen diese Kopie z.B. in MyVariationsRootLanding.ascx um.

Diese umbenannte Kopie können wir mit einem Texteditor bearbeiten und das Verhalten bzgl. Weiterleitung ändern. Wie so eine Änderung beispielhaft aussehen kann, zeige ich an diesem Code-Beispiel:

   1: <%@ Control Language="C#"   %>
   2: <%@Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
   3: <%@Assembly Name="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
   4: <%@Register TagPrefix="CMS" Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.Publishing.WebControls"%>
   5: <%@ Import Namespace="System.Collections" %>
   6: <%@ Import Namespace="System.Collections.Specialized" %>
   7: <%@ Import Namespace="System.Collections.Generic" %>
   8: <%@ Import Namespace="System.Collections.ObjectModel" %>
   9: <%@ Import Namespace="System.Globalization" %>
  10: <%@ Import Namespace="Microsoft.SharePoint" %>
  11: <%@ Import Namespace="Microsoft.SharePoint.Utilities" %>
  12: <%@ Import Namespace="Microsoft.SharePoint.WebControls" %>
  13: <%@ Import Namespace="Microsoft.SharePoint.Publishing" %>
  14: <script runat="server">
  15:         private const string QualityValuePrefix = ";q=";
  16:         private enum PropertiesOnLabelToUse
  17:         {
  18:             Language,
  19:             Locale
  20:         }
  21:         private enum MatchingPreference
  22:         {
  23:             ImpreciseOrderFirst,
  24:             PreciseMatch
  25:         }
  26:         private PropertiesOnLabelToUse PropertyOnLabelToUse
  27:         {
  28:             get { return this.propertyOnLabelToUse; }
  29:             set { this.propertyOnLabelToUse = value; }
  30:         }
  31:         private PropertiesOnLabelToUse propertyOnLabelToUse = PropertiesOnLabelToUse.Locale;
  32:         private MatchingPreference PreferenceOrder
  33:         {
  34:             get { return this.preferenceOrder; }
  35:             set { this.preferenceOrder = value; }
  36:         }
  37:         private MatchingPreference preferenceOrder = MatchingPreference.ImpreciseOrderFirst;
  38:         protected override void OnLoad(EventArgs e)
  39:         {
  40:             base.OnLoad(e);
  41:             string targetUrl = this.GetRedirectTargetUrl();
  42:             if (!string.IsNullOrEmpty(targetUrl))
  43:             {
  44:                 SPUtility.Redirect(targetUrl, SPRedirectFlags.Default, Context);
  45:             }
  46:         }
  47:         private string GetRedirectTargetUrl()
  48:         {
  49:             const string c_strDefaultLocal = "1033";  // CHANGED  (1033:english / 1031:deutsch)
  50:             
  51:             ReadOnlyCollection<VariationLabel> spawnedLabels = Variations.Current.UserAccessibleLabels;
  52:             if (spawnedLabels.Count > 0)
  53:             {
  54:                 string sourceLabelUrl = string.Empty;
  55:                 string strDefaultlUrl = string.Empty;  // CHANGED
  56:                 
  57:                 Dictionary<string, string> cultureCodeToUrlMapping = new Dictionary<string, string>();
  58:                 Dictionary<string, string> cultureCodeStrippedToUrlMapping = new Dictionary<string, string>();
  59:                 foreach (VariationLabel label in spawnedLabels)
  60:                 {
  61:                     if (label.IsSource)
  62:                     {
  63:                         sourceLabelUrl = label.TopWebUrl;
  64:                     }
  65:                     else
  66:                     {
  67:                         if (label.Locale == c_strDefaultLocal) // CHANGED
  68:                         {
  69:                             strDefaultlUrl = label.TopWebUrl;  // CHANGED
  70:                         }
  71:                     }
  72:                     CultureInfo labelCultureInfo = this.GetLabelCultureInfo(label);
  73:                     string labelCultureInfoName = labelCultureInfo.Name.ToUpperInvariant();
  74:                     if (!cultureCodeToUrlMapping.ContainsKey(labelCultureInfoName) || label.IsSource)
  75:                     {
  76:                         cultureCodeToUrlMapping.Remove(labelCultureInfoName);
  77:                         cultureCodeToUrlMapping.Add(labelCultureInfoName, label.TopWebUrl);
  78:                         string strippedCode = labelCultureInfoName.Split('-')[0];
  79:                         if (!cultureCodeStrippedToUrlMapping.ContainsKey(strippedCode) || label.IsSource)
  80:                         {
  81:                             cultureCodeStrippedToUrlMapping.Remove(strippedCode);
  82:                             cultureCodeStrippedToUrlMapping.Add(strippedCode, label.TopWebUrl);
  83:                         }
  84:                     }
  85:                 }
  86:                 string matchedUrl;
  87:                 if (MatchingPreference.ImpreciseOrderFirst == this.preferenceOrder)
  88:                 {
  89:                     matchedUrl = this.GetRedirectTargetUrlImpreciseOrderFirst(
  90:                         cultureCodeToUrlMapping, cultureCodeStrippedToUrlMapping);
  91:                 }
  92:                 else
  93:                 {
  94:                     matchedUrl = this.GetRedirectTargetUrlPreciseMatch(
  95:                         cultureCodeToUrlMapping, cultureCodeStrippedToUrlMapping);
  96:                 }
  97:                 //return (string.IsNullOrEmpty(matchedUrl) ? sourceLabelUrl : matchedUrl);
  98:                 return (string.IsNullOrEmpty(matchedUrl) ? strDefaultlUrl : matchedUrl);   // CHANGED
  99:             }
 100:             return null;
 101:         }
 102:         private CultureInfo GetLabelCultureInfo(VariationLabel label)
 103:         {
 104:             if (PropertiesOnLabelToUse.Locale == this.propertyOnLabelToUse)
 105:             {
 106:                 return new CultureInfo(Convert.ToInt32(label.Locale, CultureInfo.InvariantCulture));
 107:             }
 108:             else
 109:             {
 110:                 return new CultureInfo(label.Language);
 111:             }
 112:         }
 113:         private string GetRedirectTargetUrlImpreciseOrderFirst(Dictionary<string, string> cultureCodeToUrlMapping, Dictionary<string, string> cultureCodeStrippedToUrlMapping)
 114:         {
 115:             string[] browserPrefLanguages = this.GetUserLanguages();
 116:             if (null == browserPrefLanguages)
 117:                 return null;
 118:             string browserPrefLang;
 119:             string browserPrefLangStripped;
 120:             for (int i = 0; i < browserPrefLanguages.Length; i++)
 121:             {
 122:                 browserPrefLang = browserPrefLanguages[i].ToUpperInvariant();
 123:                 if (cultureCodeToUrlMapping.ContainsKey(browserPrefLang))
 124:                 {
 125:                     return cultureCodeToUrlMapping[browserPrefLang];
 126:                 }
 127:                 browserPrefLangStripped = browserPrefLang.Split('-')[0];
 128:                 if ((browserPrefLang != browserPrefLangStripped) &&
 129:                     (cultureCodeToUrlMapping.ContainsKey(browserPrefLangStripped)))
 130:                 {
 131:                     return cultureCodeToUrlMapping[browserPrefLangStripped];
 132:                 }
 133:                 if (cultureCodeStrippedToUrlMapping.ContainsKey(browserPrefLangStripped))
 134:                 {
 135:                     return cultureCodeStrippedToUrlMapping[browserPrefLangStripped];
 136:                 }
 137:             }
 138:             return null;
 139:         }
 140:         private string GetRedirectTargetUrlPreciseMatch(Dictionary<string, string> cultureCodeToUrlMapping, Dictionary<string, string> cultureCodeStrippedToUrlMapping)
 141:         {
 142:             string[] browserPrefLanguages = this.GetUserLanguages();
 143:             if (null == browserPrefLanguages)
 144:                 return null;
 145:             for (int i = 0; i < browserPrefLanguages.Length; i++)
 146:             {
 147:                 string browserPrefLanguageName = browserPrefLanguages[i].ToUpperInvariant();
 148:                 if (cultureCodeToUrlMapping.ContainsKey(browserPrefLanguageName))
 149:                 {
 150:                     return cultureCodeToUrlMapping[browserPrefLanguageName];
 151:                 }
 152:             }
 153:             for (int i = 0; i < browserPrefLanguages.Length; i++)
 154:             {
 155:                 string browserPrefLanguageName = browserPrefLanguages[i].ToUpperInvariant();
 156:                 if (cultureCodeStrippedToUrlMapping.ContainsKey(browserPrefLanguageName))
 157:                 {
 158:                     return cultureCodeStrippedToUrlMapping[browserPrefLanguageName];
 159:                 }
 160:             }
 161:             string browserPrefLangStripped;
 162:             for (int i = 0; i < browserPrefLanguages.Length; i++)
 163:             {
 164:                 browserPrefLangStripped = browserPrefLanguages[i].Split('-')[0].ToUpperInvariant();
 165:                 if (cultureCodeToUrlMapping.ContainsKey(browserPrefLangStripped))
 166:                 {
 167:                     return cultureCodeToUrlMapping[browserPrefLangStripped];
 168:                 }
 169:             }
 170:             for (int i = 0; i < browserPrefLanguages.Length; i++)
 171:             {
 172:                 browserPrefLangStripped = browserPrefLanguages[i].Split('-')[0].ToUpperInvariant();
 173:                 if (cultureCodeStrippedToUrlMapping.ContainsKey(browserPrefLangStripped))
 174:                 {
 175:                     return cultureCodeStrippedToUrlMapping[browserPrefLangStripped];
 176:                 }
 177:             }
 178:             return null;
 179:         }
 180:         private string[] GetUserLanguages()
 181:         {
 182:             string[] browserPrefLanguages = Page.Request.UserLanguages;
 183:             if (null != browserPrefLanguages)
 184:             {
 185:                 int qualityIndexPos = -1;
 186:                 for (int i = 0; i < browserPrefLanguages.Length; i++)
 187:                 {
 188:                     qualityIndexPos = browserPrefLanguages[i].IndexOf(QualityValuePrefix, StringComparison.Ordinal);
 189:                     if (qualityIndexPos > 0)
 190:                     {
 191:                         browserPrefLanguages[i] = browserPrefLanguages[i].Substring(0, qualityIndexPos);
 192:                     }
 193:                 }
 194:             }
 195:             return browserPrefLanguages;
 196:         }
 197:         private string ConcatUrls(string firstPart, string secondPart)
 198:         {
 199:             if (firstPart.EndsWith("/"))
 200:             {
 201:                 if (secondPart.StartsWith("/"))
 202:                 {
 203:                     firstPart = firstPart.TrimEnd('/');
 204:                 }
 205:                 return firstPart + secondPart;
 206:             }
 207:             else
 208:             {
 209:                 if (secondPart.StartsWith("/"))
 210:                     return firstPart + secondPart;
 211:                 else
 212:                     return firstPart + "/" + secondPart;
 213:             }
 214:         }
 215: </script>
 216: <!--
 217:     The following control renders runtime fallback logic when the logic above cannot determine proper Variation sub Site to redirect to.
 218:     If the control is removed, it will affect the runtime fallback behavior of the Variation Root redirect.
 219: -->
 220: <cms:VariationsRootLandingRunTime id="VariationsRootLandingRunTime" runat="server"/>
 221: <!--
 222:     The following control renders UI for SharePointDesigner. If the control is removed,
 223:     it will affect SharePointDesigner design time experience.
 224: -->
 225: <cms:VariationsRootLandingDesignTime id="VariationsRootLandingDesignTime" runat="server"/>

 

Nun müssen wir nur noch unser neues User-Control (MyVariationsRootLanding.ascx) in die Datei VariationRootPageLayout.aspx eintragen.

So sieht es im Original aus:

   1: <%@ Register TagPrefix="Publishing" 
   2: TagName="VariationsRootLanding"
   3: src="~/_controltemplates/VariationsRootLanding.ascx"%>
   4: <Publishing:VariationsRootLanding runat="server" id="VariationsRootLanding1" />

 

Und die geänderte Version würde so aussehen:

   1: <%@ Register TagPrefix="Publishing" 
   2: TagName="VariationsRootLanding"
   3: src="~/_controltemplates/MyVariationsRootLanding.ascx"%>
   4: <Publishing:VariationsRootLanding runat="server" id="VariationsRootLanding1" />

 

Zwar ist dies keine Lösung, die man direkt in einem Projekt verwenden kann, aber sie zeigt eine Möglichkeit, wie man nach Erstellen der Variations Hierarchie Einfluss auf das Variation Root Landing –also den Mechanismus, der einen Besucher anhand der in seinem Browser eingestellten Sprache- nehmen kann. Diese Lösung könnte man nun noch in ein Feature verpacken, um dieses geänderte Verhalten manuell ein- bzw. wieder auszuschalten.

Auf eines möchte ich aber noch hinweisen: da wir hier direkte Änderungen an Dateien aus der SharePoint-Installation vorgenommen haben, ist diese Lösung selbstverständlich nicht update-sicher. Durch Einspielen eines ServicePacks oder eines Hotfixes können diese Änderungen möglicherweise überschrieben werden.

 

Add to Technorati Favorites

 


Bereitgestellt 28 Aug 2009 14:19 von Oliver Wirkus
Gespeichert unter: , ,