Gino Heyman
Gids voor meertalige websites
T i p s e n t e c h n i e k e n vo o r h e t bo u w e n va n m e e r ta l i g e w e b s i t e s m e t ASP. NE T 2 . 0
Het is een steeds terugkerende requirement: de gebruikersinterface moet in twee of meer talen beschikbaar zijn. Op zich geen onredelijke vraag, maar menige softwareontwikkelaar kijkt niet meteen uit naar de bijkomende taken die hiermee gepaard gaan. Deze gids wil een overzicht geven van de globalization-mogelijkheden van ASP.NET 2.0, gecombineerd met enkele nuttige tips om meertalige sites efficiënt te implementeren.
E
en meertalige website bouwen of een bestaande website meertalig maken, is geen triviale taak. Op verschillende niveaus moeten allerlei aanpassingen worden doorgevoerd. Statische tekst kan niet langer rechtstreeks in de (x)html of code worden opgenomen, maar moet worden verplaatst naar resourcebestanden in verschillende talen. Bepaalde gegevens, zoals een datum en prijs, moeten eventueel anders worden geformatteerd. Gebruikers moeten de mogelijkheid krijgen hun taal te kiezen en deze keuze dient ook onthouden te worden voor volgende sessies. Zoekmachines moeten elke versie (taal) van de site eenvoudig en efficiënt kunnen crawlen. Tot slot moeten ook de eigenlijke applicatiegegevens worden vertaald. Er komt dus heel wat kijken bij het ontwerpen van meertalige webapplicaties. Gelukkig biedt de huidige versie van ASP.NET tools en libraries die het leed iets kunnen verzachten. ASP.NET kan echter niet alles dekken, sommige aspecten kunnen daarom nog steeds vrij arbeidsintensief zijn.
Resources Een eerste stap naar meertaligheid is het opnemen van statische tekst (of, in veel gevallen, format strings) in resource-files. Hier biedt ASP.NET twee mogelijkheden: global en local resources. Global resources worden vertegenwoordigd door resx-bestanden in de App_GlobalResources map in de root van de webapplicatie. Deze resources zijn bruikbaar over heel de website, zoals hun naam al doet vermoeden. Er kan vanuit elk master-, aspx- of ascx-bestand – in de context van een server-control –worden verwezen naar een global resource met behulp van een resource-tag met de vorm ‘<%$ Resources:File, Key %>’. Onderstaand voorbeeld illustreert hoe de tekst van een button kan worden vervangen door een referentie naar een resource met de naam ‘Title’ afkomstig uit de bestanden Strings[.xx[-XX]].resx in de map App_GlobalResources.
” />
Visual Studio 2005 genereert met behulp van de StronglyTypedResourceBuilder voor elke set van global resource-files, zodra deze wordt opgeslagen, een type-safe object met static properties. Dit maakt het mogelijk om, buiten de context van een server control, ook elementen zoals ‘<%= Resources.File.Key %>‘ in master-, aspx-, ascx-files of ‘Resources.File.Key‘ in code te gebruiken. Dergelijke codegeneratie impliceert uiteraard enige omzichtigheid met de naamgeving van deze bestanden en de gebruikte resourcenamen of keys. Hoewel de StronglyTypedResourceBuilder ongeldige symboolkarakters zoals spaties of quotes vervangt door een underscore (_) is het toch aan te raden ze te vermijden.
Naast global resources is het ook mogelijk local resources te gebruiken. In tegenstelling tot de globale variant zijn deze resources aan één UserControl, Page of MasterPage gebonden. Deze link gebeurt op basis van de locatie en naam van het resourcebestand. De pagina invoice.aspx in map ~/admin/ zal bijvoorbeeld kunnen verwijzen naar local resources uit de bestanden ~/admin/ App_LocalResources/invoice.aspx[.xx[-XX]].resx. Een Page of UserControl kan op twee manieren naar local resources verwijzen: impliciet of expliciet. Impliciet gebruik steunt op een conventie voor de naamgeving van de resourcenamen. Neem bijvoorbeeld de volgende LinkButton-control:
ASP.NET zal de properties Text en ToolTip van bovenstaande LinkButton initialiseren met de resources met naam ‘btn.Text’ en ‘btn.ToolTip’. Deze naamgevingconventie (‘.’) werkt bij impliciet gebruik voor alle localized properties (zie verderop in dit artikel) van een servercontrol. Het leuke aan deze techniek is dat de (x)html-code niet overladen raakt door elke property afzonderlijk op te geven. Local resources kunnen ook expliciet worden gebruikt. De syntax lijkt sterk op degene die wordt gebruikt bij global resources, de klassenaam is hier echter overbodig. Het volgende element geeft exact hetzelfde resultaat als voorgaand voorbeeld: ” ToolTip=”<%$ Resources:btn.ToolTip %>” runat=”server” />
Indien gewenst, kan men een default (zonder culture name) local resourcefile door Visual Studio laten genereren. Hiertoe dient men de MasterPage, Page of UserControl in designview te openen en ‘Generate Local Resource’ uit het ‘Tools’-menu te selecteren. Visual Studio maakt vervolgens het default resourcebestand (met juiste naam en in de juiste map) aan en voorziet alvast de nodige waarden voor alle localizable properties van de server-controls die in de control of pagina voorkomen. Ook zullen de pagina of control worden aangepast om de net aangemaakte resources (impliciet) te gebruiken. Voor later toegevoegde elementen in de pagina of control kan deze operatie gewoon herhaald worden. Let wel, resourcebestanden die al voor andere culturen werden aangemaakt of gekopieerd moeten manueel up-to-date gehouden worden. Voor het genereren van local resources zoekt de Visual Studio IDE voor elk element op de Page of UserControl naar properties met .net magazine for developers #20 | maart 2008
49
het LocalizableAttribute. Enkel properties met [Localizable(true)] worden in de local resourcefile opgenomen. Dit attribuut staat ontwikkelaars van custom controls ook toe om zelf properties localizable te maken, wat de bruikbaarheid van de controls ten goede komt. Terwijl dit genereren van local resources erg handig en tijdsbesparend is, kan het ook een ongewenst effect met zich meebrengen. Indien local resources gegenereerd worden voor een Page die geen Culture- en UICulture-attributen bevat (wat meestal het geval is), worden deze toegevoegd met de waarde ‘auto‘. Hierdoor worden ongewenst eventuele andere instellingen in je Web. Config teniet gedaan (zie verderop in dit artikel). Ten slotte bestaat er nog de mogelijkheid om met behulp van de methoden GetGlobalResourceObject en GetLocalResourceObject resources op te vragen. Deze methoden zijn gedefinieerd op zowel HttpContext en TemplateControl (intern gebruiken ze beide de ResourceExpressionBuilder) en hebben verschillende overloads waarmee je in verschillende omstandigheden aan de nodige resources kunt komen.
De Culture en UICulture bepalen Localization (het gebruik van resources) en globalization (het formatteren van gegevens) worden bepaald door respectievelijk de CurrentUICulture en CurrentCulture van de current thread. ASP. NET kan de Current(UI)Culture van een thread aanpassen aan de hand van configuratie op verschillende niveaus. Op paginaniveau (door de attributen Culture en UICulture in de @Page-tag):
ting in acht neemt, het gaat immers om formattering) zal daarentegen in het Duits worden weergegeven. Dit probleem doet zich uiteraard ook voor bij het formatteren van allerlei andere gegevens. Een punt of een komma kan opeens een heel andere betekenis krijgen. De waarde ‘auto’ voor de Culture vervangen door een specifieke culture name biedt helaas niet altijd de oplossing, het zou alsnog tot een inconsistente user-interface kunnen leiden. Een bruikbare oplossing voor dit probleem wordt in volgende paragrafen besproken.
De gebruiker de taal laten wijzigen Het bepalen van de taal zoals hierboven is uiteengezet, is in veel gevallen niet voldoende. Men kan – de aangehaalde problemen even buiten beschouwing gelaten – het instellen van de cultures wel automatisch laten verlopen, maar de culture name van de browser komt niet noodzakelijk overeen met de eigenlijke taal van de bezoeker. De gebruiker zal dus zelf een keuze moeten kunnen maken. Het designteam van ASP.NET heeft hiervoor een nieuwe functie geïntroduceerd. De Page-klasse heeft sinds versie 2.0 een virtual InitializeCulturemethode. Deze kan door de ontwikkelaar worden overridden om zelf te bepalen hoe de cultures van de current thread worden gewijzigd, in tegenstelling tot de voorheen besproken configuratiemogelijkheden. Het framework zelf implementeert deze methode niet (ze returnt gewoon), maar bij elke request van een pagina wordt deze methode public class LocalizationModule : IHttpModule {
<%@ Page Language=”C#” AutoEventWireup=”true” CodeFile=”Default.aspx.cs” Inherits=”_Default” Culture=”auto” UICulture=”auto” %>
public void Init(HttpApplication app) { app.BeginRequest += new EventHandler(OnBeginRequest); }
Of in de globalization-tag van je web.config file: private void OnBeginRequest(object sender, EventArgs e)
{ HttpApplication app = sender as HttpApplication;
De waarden in de web.config kunnen per subfolder overruled worden en de setting per pagina heeft altijd het laatste woord. Als één van deze configuratiesettings wordt gebruikt, zal ASP.NET voor elke request de Culture en UICulture (van de current worker thread) zoals gewenst instellen. Meer is niet nodig. De mogelijke waarden voor de attributen Culture of UICulture zijn: een culture name (bijvoorbeeld ‘nl-BE’), ‘auto’ (automatisch) of ‘auto:xx-XX’ (automatisch met een fallback culture). Bij het gebruik van een culture name zal een pagina of de hele site (naargelang de waarde in de pagina of in de web.config staat) gebruikmaken van de resources voor die culture. ASP.NET zal namelijk bij het begin van een request de CurrentCulture en/of CurrentUICulture van de CurrentThread aanpassen, zodat deze overeenstemt met de waarde die werd geconfigureerd in de web.config. De configuratie op paginaniveau wordt pas toegepast tijdens de ProcessRequest van de pagina. Het gebruik van ‘auto’ heeft hetzelfde effect, alleen wordt hier rekening gehouden met de http-header ‘Accept-Language’ om de culture-waarden aan te passen. Het is best mogelijk dat deze http-header ontbreekt, ‘auto’ kan daarom gecombineerd worden met een fallback culture name. De waarde ‘auto:nl-BE’ zal bijvoorbeeld zorgen dat ASP.NET de culture op ‘nl-BE’ instelt bij gebrek aan de http-header. Het gebruik van ‘auto’ lijkt op het eerste gezicht een ideale oplossing, maar voor het configureren van de Culture kan het beter vermeden worden. Neem de volgende situatie: zowel culture als uiCulture hebben de waarde auto in de web.config-file (al dan niet met een fallback culture name). De resource-files strings.nl.resx, strings.fr.resx en strings.resx (de default resourcefile met de Engelse teksten) zijn beschikbaar. Een browser vraagt een pagina met een Calendar-control op en geeft in zijn Accept-Language-header de waarde ‘de-DE’ mee. In dit geval zal de pagina met Engelse resources worden gerenderd. Aangezien de file strings.de-DE.resx niet bestaat, zal de resourcemanager terugvallen op de default file. De Calendar-control (die de Culture-set-
50
.net magazine for developers #20 | maart 2008
if (app != null && app.Context != null
&& app.Context.Profile != null)
{ string culture = app.Context.Request.QueryString[“c”]; if (!string.IsNullOrEmpty(culture)) { // De begruiker wenst de taal te wijzigen app.Context.Profile[“CultureName”] = culture; } if (app.Context.Profile[“CultureName”] == null) { if (app.Context.Request.UserLanguages.Length > 0) { string al = app.Context.Request.UserLanguages[0]; if (GetSupportedLanguages().Contains(al)) app.Context.Profile[“CultureName”] = al; else app.Context.Profile[“CultureName”] = GetDefaultCulture Name(); } else app.Context.Profile[“CultureName”] = GetDefaultCultureName(); } string c = app.Context.Profile[“CultureName”].ToString(); Thread.CurrentThread.CurrentUICulture = new CultureInfo(c); Thread.CurrentThread.CurrentCulture = new CultureInfo(c); } } public void Dispose() { } } Codevoorbeeld 1.
wel aangeroepen. Dit gebeurt tijdens de uitvoer van de methode ProcessRequest (via FrameworkInitialize en __BuildControlTree), net voor het creëren van de controls van de pagina in kwestie. De (misschien wat overhaaste?) keuze voor InitializeCulture heeft echter verschillende nadelen. De methode InitializeCulture wordt vrij laat tijdens het afhandelen van een request uitgevoerd. Hierdoor kan het zijn dat – tot de uitvoer van InitializeCulture – de cultures niet zijn ingesteld zoals gewenst. Dit geldt trouwens ook voor het specificeren van de culture in de @Page-header van een aspx-file. Code die eerder in de afhandeling van een request wordt uigevoerd (bijvoorbeeld de constructor van de pagina) en gebruikmaakt van resources, kan bijgevolg mogelijk foute waarden opvragen. De methode InitializeCulture is gedefinieerd in de Page-klasse. Voor andere localized resources, zoals gegenereerde afbeeldingen, PDF- of XML-files, moet een andere eigen techniek worden gebruikt. De InitializeCulturemethode moet voor elke nieuwe webpagina opnieuw worden overridden, wat allesbehalve onderhoudsvriendelijk is. Een logische redenering is uiteraard om een base-class in het leven te roepen en alle pages hiervan te laten overerven voor maximaal hergebruik van code, maar dat houdt weer in dat elke code-behind-file manueel aangepast moet worden. Dit is evenmin onderhoudsvriendelijk en kan leiden tot vervelende bugs die pas laat boven water komen. Voor het aanduiden van een base-class kan ook in de web.config-file verwezen worden naar het basistype waar alle webpagina’s impliciet van overerven public class LocalizedEnum // where T : Enum -> not supported :-( { private static ResourceManager resourceManager = GetConfiguredResourceManager(); public static List> GetValues() { List> ret = new List>(); Type type = typeof(T); string fullTypeName = type.FullName; string[] names = Enum.GetNames(type); T[] values = (T[])Enum.GetValues(type); for (int i = 0; i < names.Length; i++) { string resKey = string.Format(“{0}.{1}”, fullTypeName, names[i]); ret.Add(new LocalizedEnum( resourceManager.GetString(resKey) ?? names[i], values[i])); } return ret; } public static LocalizedEnum FromValue(TEnum value) { Type t = value.GetType(); string name = Enum.GetName(t, value); return new LocalizedEnum(resourceManager.GetString
(<pages pageBaseType=”PageBase”>). Dit werkt overigens enkel voor pagina’s zonder code-behind. Aangezien dit veelal niet het geval is, is ook deze optie niet erg bruikbaar. Het kan ook anders. Bij het gebruik van een HttpModule is het mogelijk bovenstaande nadelen van de InitializeCulture-aanpak te omzeilen. Bovendien wordt het omschakelen van de culture at run-time compleet ontkoppeld van de Page (of andere handlers), wat herbruikbaarheid in andere projecten mogelijk maakt. Codevoorbeeld 1 geeft een vereenvoudigde voorbeeldimplementatie van een LocalizationModule weer. De module houdt rekening met een query string-parameter en het profiel van de gebruiker om te bepalen welke (UI)Culture van toepassing is. Merk op dat de query string enkel nodig is voor het wijzigen van de huidige taal. Als er een andere taal is gekozen, is de parameter niet meer nodig.
Enumeraties Een belangrijke bron van gegevens voor keuzemogelijkheden zoals drop down lists, radio buttons en checkboxes zijn enumeraties (meestal afkomstig uit het domeinmodel van de applicatie). Op een gegeven moment moeten dergelijke enumeratiewaarden vertaald worden. De kunst is om dit op een zo generiek mogelijk manier te doen. Een veelgebruikte aanpak is het implementeren van een eigen EnumConverter die aan een enum wordt gelinkt met behulp van het attribuut TypeConverterAttribute. Een custom EnumConverter kan bij conversie naar een string een ResourceManager aanspreken om de waarde te vertalen. Deze techniek is in het verleden al vaker besproken en lijkt op het eerste gezicht in orde. Toch brengt zij enkele problemen met zich mee. Een EnumConverter werkt niet goed samen met ASP.NET-databinding en is onbruikbaar voor bestaande enumeraties (van het .NET Framework bijvoorbeeld), aangezien het TypeConverterAttribute noodzakelijk is. Het grootste probleem is echter dat deze techniek een ‘foute’ afhankelijkheid creëert: de domain-assembly die de enumeratie declareert, moet refereren naar een assembly die deel uitmaakt van de presentatielaag. Het (onvolledige) voorbeeld uit codevoorbeeld 2 illustreert een derde techniek, die steunt op generics en een eenvoudige conventie om het localization-probleem, zonder bovengenoemde nadelen, aan te pakken. De te gebruiken resourcemanager kan worden geconfigureerd in de web.config-file. Deze kan verwijzen naar een resx-file, of naar een assembly met embedded resources. Deze resources moeten als resourcenaam de volledige naam van een enumeratiewaarde gebruiken. Voor de bestaande Framework-enumeratie System.DayOfWeek wordt dit: ‘System.DayOfWeek.Sunday’, ‘System.DayOfWeek.Monday’, enzovoort. Dankzij deze conventie is het eventueel mogelijk om de resource-file met keys voor alle enumeraties in een assembly te genereren. Zie codevoorbeeld 3 voor het gebruik van de LocalizedEnum.
Domeinobjecten De waarschijnlijk moeilijkste opdracht is het vertalen van businessentiteiten. Deze taak staat in principe los van de webapplicatie, maar je kunt toch van de volgende aannames uitgegaan: • Bij het tonen van meer instanties, bijvoorbeeld in een lijst, zal één taal volstaan. • Pas bij het redigeren (edit) van een instantie – bijvoorbeeld in het administratieve gedeelte van de site – zijn alle beschikbare talen nodig.
(t.FullName + “.” + name), value); } private LocalizedEnum(string name, T e) {} public T Value { get; }
Deze aannames maken het mogelijk het ophalen van entiteiten enigszins te optimaliseren. Het ophalen van (een lijst van) entiteiten kan zich beperken tot het ophalen van de gegevens voor de huidige taal (CurrentUICulture). Pas als expliciet naar een property in een andere taal wordt gevraagd, kunnen de resterende gegevens
public string Name { get; } lst.DataSource = LocalizedEnum<System.DayOfWeek>.GetValues(); public override string ToString() { return Name; }
lst.DataTextField = “Name”; // Niet nodig wegens ToString lstDayOfWeek.DataValueField = “Value”; // Enkel nodig bij ASP.NET
}
lstDayOfWeek.DataBind();
Codevoorbeeld 2.
Codevoorbeeld 3.
.net magazine for developers #20 | maart 2008
51
Profiles
Afbeelding 1. Extensietabellen
worden opgehaald (lazy loading). Aangezien de meeste applicaties gebruikmaken van een relationele databank voor het persisteren van data, moet er toch even worden stilgestaan bij een relationeel datamodel voor meertaligheid. Er bestaan verschillende mogelijkheden om meer talen te ondersteunen, maar de bruikbaarste is waarschijnlijk het gebruik van extensietabellen; zie afbeelding 1 voor een voorbeeld. Hierbij wordt elk localized veld naar een aparte tabel verplaatst die met een foreign key gelinkt is aan de eigenlijke tabel. Dit biedt volgende voordelen ten opzichte van andere modellen: • Het model respecteert de gebruikelijke normalisatieregels bij relationele databanken. Opmerking: eventueel kan een kleine denormalisatie gunstiger zijn dan het theoretische voorbeeld uit afbeelding 1. In plaats van een foreign key naar de Culturestabel, kan gewoon de CultureName zelf worden opgenomen in de ‘*_Loc’-tabel. Hiermee vermijd je het ontstaan van bijzonder veel relaties naar deze Cultures-tabel en het maakt SELECTstatements eenvoudiger (een JOIN minder). • Elke waarde kan efficiënt geïndexeerd worden. Andere oplossingen zoals het XML-datatype of een centrale resourcetabel maken dit erg moeilijk, minder efficiënt of zelfs onmogelijk. • Het principe kan ook eenvoudig worden toegepast op bestaande databasemodellen, de bestaande tabellen ondergaan nauwelijks veranderingen. • Voor elk localizable veld kan het geschiktste type worden gekozen, in tegenstelling tot een schema met een algemene ‘resourcetabel’ die door verschillende tabellen wordt gebruikt. • De meeste databases hebben een maximum voor de toegelaten lengte per record. Een model met bijvoorbeeld één veld per taal (Name_nl, Name_fr,
) kan snel tegen deze limiet aanlopen. • Het model is flexibel: het kan eenvoudig worden uitgebreid met andere talen en/of velden. • Het ophalen van (reeksen van) objecten kan via een eenvoudig SELECT-statement met een INNER JOIN met de extensietabel (voor de current CultureId, of CultureName, al naar gelang de keuze voor denormalisatie of niet). De domeinklassen kunnen gebruikmaken van bijvoorbeeld een generic dictionary om de verschillende versies van de localized data te bewaren. Voor de klasse Product zou dit kunnen zijn ‘Dictionary<string, LocalizedProduct>’. Bovendien kunnen dan methodes zoals ‘GetName(string culture)’ en ‘SetName(string culture, string value)’ (die de dictionary raadplegen voor het ophalen, bewaren en laden (lazy loading) van data) gedefinieerd worden. Om het gebruik van domeinobjecten te vereenvoudigen, is het zeker interessant om localized properties te voorzien die rekening houden met de CurrentUICulture:
Zoals al eerder is aangehaald, is het bewaren van een gebruikersprofiel de ideale manier om de taalkeuze van een gebruiker te persisteren over verschillende sessies. De eenvoudigste manier om dit te implementeren is gebruik te maken van een ProfileProvider. De Framework Class Library beschikt over een default-implementatie (SqlProfileProvider) die profielgegevens bewaart in een SQL Serverdatabank naar keuze. Codevoorbeeld 4 toont een onderdeel van een web.config die ervoor zorgt dat: • profielgegevens in een SQL Server-databank worden bewaard • ook anonieme gebruikers een profiel kunnen hebben • elke gebruiker een CultureName in zijn profiel heeft Het framework genereert ook hier weer code. Met de configuratie uit codevoorbeeld 4 zal het mogelijk zijn de CultureName op te vragen met Profile.CultureName (in een Page, MasterPage of UserControl).
Zoekmachines De misschien meest voor de hand liggende methode waardoor zoekmachines eenvoudig een site in alle mogelijke talen kunnen crawlen, is de master-page voorzien van een link per taal. Zo zal bij elke webpagina een link worden aangetroffen die een zoekmachine naar dezelfde pagina in een andere taal leidt. Het gebruik van een url om de taal te bepalen, is niet alleen interessant voor zoekmachines en het bepalen van de taal door de gebruiker. Het is op deze manier ook mogelijk om via een link in een e-mail of op een andere site nieuwe gebruikers meteen naar de juiste taal te leiden. Hoe die url er moet uitzien, is een andere kwestie. Een url met een query string-parameter is waarschijnlijk de meest praktische (op voorwaarde dat de parameter slechts eenmalig moet worden meegegeven). Indien query-strings niet gewenst zijn, kan een custom url-rewriter worden gebruikt om bijvoorbeeld url’s in de vorm van http://www.site.be/nl-BE/about/ te ondersteunen. Dit brengt echter bepaalde bezwaren met zich mee: • Voor url-rewriting in ASP.NET heeft men als ontwikkelaar heel wat keuze: een HttpModule, HttpHandler, Request.PathInfo en talloze libraries kunnen worden overwogen. Elk van deze oplossingen steunt op het feit dat elke request door ASP.NET wordt behandeld. Dit betekent dat in IIS de MIME-mapping moet worden aangepast; IIS 7’s runAllManagedModulesForAllRequest s=”true” en ISAPI-filters buiten beschouwing gelaten. Dit is niet altijd mogelijk (hosting) en gaat bovendien gepaard met een zekere performance-hit. • Postbacks kunnen zorgen voor inconsistente url’s, tenzij ook hier opnieuw een bijkomende ingreep gebeurt.
<profile automaticSaveEnabled=”true” defaultProvider=”SqlProvider” enabled=”true”> <providers>
public string Name
connectionStringName=”database_name” applicationName=”app”/>
{
get { return GetName(CultureInfo.CurrentCultureName); } <properties>
Het is zeker aan te raden een Visual Studio-code snippet te gebruiken die het coderen van dergelijke properties (inclusief methoden en dictionary) handiger te maken. Naast een degelijk datamodel is het ook belangrijk rekening te houden met zaken zoals Collations en het al dan niet gebruikmaken van Unicode-datatypes (zie referenties voor meer informatie).
52
.net magazine for developers #20 | maart 2008
Codevoorbeeld 4.
• B ij url-rewriting kan op de master-page niet zomaar een vaste link per taal worden geplaatst. De link moet telkens opnieuw worden samengesteld in functie van de huidige url. Met een query string-parameter kan gewoon een relatieve url zoals ‘?c=xx[-XX]’ worden gebruikt.
een databank. De volgende configuratie kan ASP.NET de instructie geven om de eigen implementatie te gebruiken:
Veel tekst
Het is duidelijk dat het ASP.NET-team heel wat aandacht heeft besteed aan de localization van websites. Met behulp van zaken als resourcemanagement, codegeneratie, speciale controls en profiles kunnen ontwikkelaars met minimale inspanningen robuuste meertalige websites ontwikkelen. Dankzij de modulaire architectuur van ASP.NET is het ook relatief eenvoudig om minder triviale problemen op een elegante manier aan te pakken.
De meeste websites bevatten wel enkele pagina’s waarin grote hoeveelheden statische tekst zijn opgenomen. Neem bijvoorbeeld een ‘over ons’- of ‘algemene voorwaarden’-pagina. Statische tekst van deze omvang is niet meer zo eenvoudig op te nemen in resourcebestanden. Voornamelijk omdat de resource-editor niet geschikt is voor omvangrijke teksten. Resourcebestanden kunnen echter ook verwijzen naar externe bestanden. Zo kunnen bijvoorbeeld (x)html-bestanden worden gelinkt aan een resource-file. Deze resources kunnen worden beschouwd als gewone stringresources. Bij de creatie en het kopiëren van resourcebestanden voor andere specifieke cultures (bijvoorbeeld HtmlContent.nl-BE. resx) moet er wel voor worden gezorgd dat de gelinkte bestanden (evenals de referenties ernaar) ook worden gekopieerd (bijvoorbeeld. About.nl-BE.htm). De inhoud van deze resources kan in een pagina worden ingevoegd zoals is besproken bij het overzicht van local en global resources. ASP.NET voorziet echter ook een Localize-control (in principe is dit niets anders dan een Literalcontrol) die ideaal is voor deze taak.
Minimale inspanning
Gino Heyman is werkzaam als softwarearchitect bij Capgemini België (http://www. be.capgemini.com/) en sterk geboeid door alles wat met softwaredesign te maken heeft. Naast zijn dagelijkse taken houdt hij zich bezig met coachen en het bestuderen en evalueren van nieuwe Microsoft-technologieën. Zo nu en dan slaagt hij er in zijn ervaringen via zijn website (http://typesafe.be/) met anderen te delen. Referenties Localization: http://msdn2.microsoft.com/en-us/library/5839we2z.aspx Globalization: http://msdn2.microsoft.com/en-us/library/c08a467e.aspx Resources in Web Server Controls: http://msdn2.microsoft.com/en-us/library/ ms228093.aspx Globalization-tag in web.config: http://msdn2.microsoft.com/en-us/library/ ydkak5b9(VS.71).aspx
ResourceManager
EnumConverter: http://msdn2.microsoft.com/en-us/library/system.componentmodel.
Resources updaten kan door de bestaande resx-files te overschrijven met nieuwe. Soms moeten resources echter kunnen worden gemanaged via, laat ons zeggen, een administratieve interface. In dergelijke gevallen kan het interessant zijn om een eigen ResourceProvider en ResxResourceProviderFactory te ontwikkelen die de resources niet uit resx-bestanden haalt, maar uit bijvoorbeeld
enumconverter.aspx TypeConverterAttribute: http://msdn2.microsoft.com/en-us/library/system.compo nentmodel.typeconverterattribute.aspx ASP.NET application lifecycle (met overzicht over request): http://msdn2.microsoft. com/en-us/library/ms178473.aspx SQL Server Collations: http://msdn2.microsoft.com/en-us/library/aa214408(SQL.80).aspx
Altijd al de Taj Mahal willen zien?
www.CertificeringViaIndia.nl .NET developers Projectmanagers Consultants XCESS expertise center b.v.
|
Storkstraat 19
Werken bij XCESS is ook werken aan je eigen ontwikkeling. In India of in Nederland. Onze projecten vragen om vakkundige collega’s met veel kennis van Microsoft technologie. Kies voor kwaliteit en een professionele carrière. Kies voor XCESS. www.xcess.nl
| 3833 LB Leusden
|
T (033) 433 51 51
|
I www.CertificeringViaIndia.nl