John's profileJohn West Blogs about Si...PhotosBlogLists Tools Help

Blog


    11/23/2008

    Manipulating Sitecore Links

    Update: most of this is covered by http://sdn.sitecore.net/Reference/Sitecore%206/Dynamic%20Links.aspx, which references http://trac.sitecore.net/LinkProvider.

    The release notes for Sitecore 6 081002 introduce a new web.config setting called Rendering.SiteResolving. If you use Redgate Reflector to analyze the getter for Sitecore.Configuration.Settings.Rendering.SiteResolving, you will see that it is called in two places: when using the RenderField pipeline, and when configuring options for an XSL rendering helper. It doesn't look like the Sitecore.Links.LinkManager.GetItemUrl() method respects this setting. You can work around this with code such as the following:

    Sitecore.Data.Items.Item item = Sitecore.Context.Item;
    Sitecore.Links.UrlOptions urlOptions = (Sitecore.Links.UrlOptions) Sitecore.Links.UrlOptions.DefaultOptions.Clone();
    urlOptions.SiteResolving = Sitecore.Configuration.Settings.Rendering.SiteResolving;
    string url = Sitecore.Links.LinkManager.GetItemUrl(item,urlOptions);
    

    But that's a new method to remember to call. It would be preferable to override the link provider. Unfortunately, due to my lack of knowledge and time, or possibly due to the implementation, I couldn't figure out how to override the link provider perfectly. My link provider either enforces Rendering.SiteResolving all of the time, or never. I am not sure if this could cause any problems. For example, CMS users may prefer to authenticate against a single domain. If needed, the logic could set the Sitecore.Links.UrlOptions.SiteResolving property only if the Sitecore.Context.PageMode.IsPageEditor property is False.

    This feature alone would not require much work, so I decided to add some more features something else. There has been some discussion on the Sitecore Developer Network (SDN) forums about further optimizing Sitecore URLs for search engines, and it would be nice if Sitecore would use the alias for an item if one exists. I used the following properties, though you could use this for various URL processing requirements.

    • ApplySiteResolving - Whether or not to always apply the Rendering.SiteResolving setting.
    • ApplyAliases - Whether or not to check for and apply aliases when possible.
    • AppendSlash - Appending a slash instead of an extension might improve SEO in some cases.

    The logic for this is still relatively straightforward - override the GetItemUrl() and apply any differences.

    As with anything I post, this is largely untested, especially under various configurations. Configure the /configuraiton/sitecore/linkManager element of web.config as follows:

    <linkManager defaultProvider="sitecore">
      <providers>
        <clear />
        <add name="sitecore" type="Namespace.Links.LinkProvider, assembly" addAspxExtension="true" alwaysIncludeServerUrl="true" encodeNames="true" languageEmbedding="always" languageLocation="filePath" useDisplayName="false">
          <applySiteResolving>true</applySiteResolving>
          <expandAliases>true</expandAliases>
          <appendSlash>false</appendSlash>
        </add>
      </providers>
    </linkManager>
    

    The real logic is in the supporting Namespace.Web.Url class used to parse URLs. Sitecore provides facilities for building URLs and links, but I wanted somewhat more explicit control of URL components. I used the following properties:

    • Prefix - I don't see any reason to separate protocol and domain (http://domain.tld), so I made this one property, which can be empty.
    • Language - The language from the first word in the path. The getter adds a leading slash ("/") if needed.
    • Path - The path to the item or alias, relative to the language or rooth, and without an extension or trailing slash.
    • QueryStringParameters - Query string parameters collection.
    • QueryString - Query string parameters as a string, including the leading question mark ("?").

    The class includes a constructor and a method intended to work something like a factory. The ToString() method returns the URL. The ParseUrlString() method resets the object and parses a string to set properties.

    The Namespace.StringExtensions class extends the System.String class with the NthIndexOf() method. The Namespace.ItemExtensions class extends the Sitecore.Data.Items.Item class with the GetFriendliestUrl() method, which calls the appropriate method to determine the URL of an item. Then, to access the URL of an item:

    Sitecore.Data.Items.Item item = Sitecore.Context.Item;
    string url = item.GetFriendliestUrl();
    

    You still need to override the link provider to ensure that Sitecore always applies your logic.

    Remember that unless you use IIS7 with an application pool in integrated mode, URLs should include the .aspx extension, so that ASP.NET provides Sitecore with an opportunity to process the request. Theoretically, you can work around this by mapping an empty extension to the ,NET ISAPI filter, but I don't think this is practical. Another workaround to this limitation of ASP.NET is to use an an ISAPI filter as described in this SDN scrapbook entry I wrote some time ago.

    The code is available at http://resources.thedotnetcms.com/sitecorejohn/linkextensions.zip.

    Update: The Content API Cookbook provides information about Sitecore dynamic link management.

    11/20/2008

    Reflecting on Sitecore

    I do not have access to the source code for Sitecore, so I often use Redgate's .NET Reflector to investigate the code. I think it's still fair to credit Lutz Roeder for this excellent tool. Redgate (which makes some other great products as well) can probably increase the value of Reflector more efficiently than an individual, such as by hosting online forums, managing newsletters, soliciting developers to build add-ins, and so forth. A recent Redgate .NET Reflector newsletter provided a link to an article by Jason Crease titled "First Steps with .NET Reflector", and another by Andrew Clarke titled "Using .NET Reflector Add-ins". I didn't even know about Reflector add-ins, so I hope to find (or possibly even develop) one that addresses some of my only complaints with this tool: that I can search by name for a class, struct, or enum, but I cannot search for a method by name, and I cannot search for a string within the disassembled code.

    I don't believe that using Reflector with Sitecore assemblies violates any Sitecore licensing terms, but if it does, I am sure that Sitecore will never enforce those clauses of the license. In order to prevent reverse-engineering the product, Sitecore obfuscates a small portion of the internal code that it doesn't expect most Sitecore developers to read.

    Most Sitecore developers primarily use APIs in the Sitecore.Kernel.dll assembly. Once in a while you might need to open Sitecore.Client.dll. Once you access a class, Reflector will prompt you to let it open additional assemblies as needed.

    The three Reflector commands I find most helpful are:

    • Search - Press F3 and search for a class (or struct, or enum) by name, then double-click a match to open it. I think it's a good habit to check the constructor whenever you investigate a class with which you are not familiar.
    • Disassemble - Once you've opened a class, right-click it, and then click Disassemble. This will show you the signatures for all of the methods and properties in the class, and you can click these to see the approximate source code. Note that this code is disassembled from the IL - it is not the code Sitecore maintains, it does not contain comments, and some parts may be horribly obfuscated, most often when you see a switch or goto statement.
    • Analyze - Right-click a class or expand a class and right-click a method or property, then click Analyze. This will show you what else uses that class, method, or property. For example, if you want to determine what sets Sitecore.Context.Item, right-click that property, then click Analyze, and then expand the set_Item() method (which represents the setter for the property). This will show you all of the code that might set this property.

    It's easy to determine how to investigate certain aspects of Sitecore. For instance, the properties of Sitecore.Context generally lead you to the most relevant classes, and you can investigate the classes referenced in web.config to see how Sitecore implements all sorts of features. When you're ready, you can inherit from or replace those classes with customizations.

    More advanced developers might be interested in how Sitecore implements certain features in the user interface. This usually comes down to one of two things: investigating a command in the Content Editor ribbon, or investigating an entire application such as the User Manager.

    To investigate ribbon commands, it helps to know a little more about how Sitecore works. Log in to the Sitecore Desktop as an administrator, click the database icon in the lower right corner, and then click core. You are now working with the Core database, which controls the Sitecore user interfaces, instead of the default Master database, which contains all versions of all content. Launch the Content Editor and navigate to the item you wish to investigate. Remember to switch back to the Master database when you're done investigating the Core database.

    For example, to investigate the Publish button in the Publish group on the Publish tab, in the Content Editor, navigate to /Sitecore/Content/Applications/Content Editor/Ribbons/Chunks/Publish/Publish (chunks being the original name for groups). In the Data section of this item, the Click field contains the token item:publishnow. This corresponds to an entry in the /App_Config/Commands.config file, which specifies the class that implements the logic that occurs when you click the Publish command, including the assembly that contains that class. The Menu field of this item references other items in the Core database that correspond to the commands on the menu that appears if you click the drop-down menu underneath the Publish command.

    Sitecore uses a really elegant (and hence, sometimes complex) approach for developing certain user interface components. Instead of hard-coding everything in .aspx files, Sitecore uses XML files under the /sitecore/shell/applications directory for a number of user interface components. The Sitecore SheerUI layer converts these XML files to ASP.NET controls, which render the user interface. Sitecore may not always name these files what you expect, so you might have to find a reference in the Core database or elsewhere to the item you need, or just look around and validate your guess before investigating too closely. There appear to be some XAML/Sheer tutorials under the /sitecore/shell/applications/xaml directory (my understanding is that Sitecore referred to SheerUI as XAML before Microsoft formalized XAML). These XML files reference classes that effectively provide code-beside for the UI components represented by the XML, and these classes tend to be in the Sitecore.Client.dll assembly.

    For example, the /sitecore/shell/applications/licenses/licensedetails/licensedetails.xml file implements the license details UI. The <CodeBeside> element in this file specifies the code-behind that implements the logic behind the license details UI component.

    One of the greatest things about Sitecore is that you can override almost any feature of the product with your own logic. You can change the classes referenced in web.config and /App_Config/Commands.config, you can add your own commands and other features to the ribbon and /App_Config/Commands.config, and you can override SheerUI components (copy the XML file from the source directory to the /sitecore/shell/override folder and change the <CodeBeside> attribute to reference your class and assembly). Note that Sitecore recently introduced a new technology called XAMLSharp, which has the effect described here.

    If you have read this entire post, you may also be interested in the MSDN Magazine .NET Tools article "Ten Must-Have Tools Every Developer Should Download Now", which I found in the Wikipedia page for Reflector. It's embarrassing to say I'm not as familiar with NUnit, NDoc, or NAnt as I could be.

    Update: The CodeSearch Reflector add-in seems to allow searching for strings in code. It's a little slow, which reminds me to write that I think it's a good idea to close assemblies that you're not using. My shortcut is to select one assembly, and then press CTRL-F4 until I have closed all of the assemblies. I couldn't find anything that would allow me to search for a method by name.

    Update Again: XSL developers can investigate signatures for XSL extension methods in the sc namespace by reviewing the Sitecore.Xml.Xsl.XslHelper class in the Sitecore.Kernel.dll assembly. Or review the Sitecore API documentation. Sitecore's XSL Presentation Component Reference provides an overview of common XSL extension methods, and lists the classes exposed by other namespaces in the section "Additional XSL Extension Method Classes".

    Update Once More: A coworker told me they use the FileDisassembler Reflector add-in to create source files from code. This coworker prefers to compile the code and use the debugger to see how it works instead of just reading it, but this could also be handy to override things that could otherwise be difficult to override (I think some cases that use this or contain private and protected methods). But be forewarned: using this approach to override components could prevent you from receiving bugfixes and could also present upgrade challenges.

    Yet Another Update (this time with Windows Live (Blog) Writer): Deblector sounds very useful for stepping through .NET framework assemblies, but for some reason I can’t seem to attach to w3wp.exe, and haven’t received a response to an email sent to the owner.

    11/19/2008

    Overriding Sitecore's Logic to Determine the Context Language

    The Sitecore layout engine retrieves content from the Sitecore repository in the context language (Sitecore.Context.Language). The default logic to determine the context language is to use the first of these variables that specifies a value:

    1. The sc_lang query string parameter.
    2. The language prefix in the path in the requested URL.
    3. The language cookie associated with the context site.
    4. The default language associated with the context logical site.
    5. The DefaultLanguage setting specified in web.config.

    In the first two cases, Sitecore generates a session cookie so that subsequent requests do not have to include the URL query string or language prefix in the URL. If the user returns to the site without specifying the URL query string parameter or the language prefix in the URL path, their language selection would be lost.

    If the organization has not published a translation of the context item in the context language, Sitecore acts as if all fields in that item are empty.

    Some solutions need to augment the default logic for determining the context language. For instance, the developer may want to:

    • Consider the browser's language preferences.
    • Fall back to an alternate language if the organization has not published a translation for the requested language. For example, if the browser requests Austrian German, but content does not exist in that language, but does exist in German German, the developer might want to use German German instead of the default language associated with the context site. Fall back from one language to another until content exists for a language, or use the site's default language.
    • If no content exists for the item in any of the languages investigated, but content does exist in some other language, use that language.
    • Use a persistant cookie.

    Additionally, ASP.NET uses the System.Threading.Thread.CurrentThread.CurrentUICulture and System.Threading.Thread.CurrentThread.CurrentCulture properties for localization, such as formatting dates. Sitecore does not set these properties based on the context language, so they default to the operating system configuration. It might be helpful if the language resolution logic set these properties.

    The following solution is relatively untested but could provide inspiration for a production-quality solution to meet these requirements. My excuses for not testing are that a developer should not test their own code and I am not even really a developer, but the issue is really that there are a lot of conditions to test, especially with different configurations of the dynamic link manager, and nobody is paying me to do this.

    Sitecore.Context.Language is a smart property, which means it follows the lazy load pattern: if code accesses this property when nothing has set it, the getter for the property contains logic to determine the context language.

    Sitecore uses the Sitecore.Pipelines.HttpRequest.LanguageResolver processor in the httpRequestBegin pipeline to determine the context language. Because Sitecore.Context.Language is a smart property, this processor probably isn't necessary, as some subsequent logic in the processing of the request is almost guaranteed to access Sitecore.Context.Language. In any case, we want to override this logic. Normally when adding logic to a pipeline, we replace an existing processor with our processor, or add our processor before or after a default processor. But in this case our processor depends on Sitecore.Context.Item, and therefore must appear after the Sitecore.Pipelines.HttpRequest.ItemResolver processor which sets the Sitecore.Context.Item property. Without investigating, we don't know whether processors between Sitecore.Pipelines.HttpRequest.LanguageResolver and Sitecore.Pipelines.HttpRequest.ItemResolver use Sitecore.Context.Language, so we'll just leave that default Sitecore.Pipelines.HttpRequest.LanguageResolver alone and add our processor after Sitecore.Pipelines.HttpRequest.ItemResolver.

    Compile the processor into your visual studio project.

    Add the custom processor after the default Sitecore.Pipelines.HttpRequest.ItemResolver processor in web.config:

    <processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />
    <processor type="Namespace.Pipelines.HttpRequestBegin.LanguageResolver, assembly">
      <persistLanguage>true</persistLanguage> 
      <setCulture>true</setCulture>
    </processor>
    

    If you don't want to create a persistant cookie to persist the user's language preference between browser sessions, set the value of the <persistLanguage> element to false, or simply remove this property setter.

    To support fallback languages:

    1. Create a new data template called something like Custom Language.
    2. Add a section to the Custom Language template.
    3. Add a field named Fallback Language to the Custom Language template.
    4. Set the type of the Fallback Language field to Droplist.
    5. Set the Source property of the Fallback Language field to /sitecore/system/languages.
    6. Add the Custom Language template to the base templates for the System/Language data template.
    7. For each language, select a fallback language, but beware of configuring infinite recursion (for instance, don't set the fallback language for German German to Austrian German if the fallback language for Austrian German is German German).

    Note that asNeeded is the default value for the languageEmbedding attribute of the /configuration/sitecore/linkManager/providers/add element in web.config that controls the logic for generating friendly URLs. The logic applied for this value may not be exactly what you expect, which can result in multiple URLs for a single content item in a single language. I recommend setting this to always for solutions involving a single language, or never otherwise.

    My current code for this processor follows:

    using System;
    
    namespace Namespace.Pipelines.HttpRequestBegin
    {
      public class LanguageResolver
      {
        private int _fallbackDepthLimit = 5;
    
        public int FallbackDepthLimit
        {
          set
          {
            _fallbackDepthLimit = value;
          }
    
          get
          {
            return _fallbackDepthLimit;
          }
        }
    
        public bool SetCulture
        {
          get;
          set;
        }
        
        public bool PersistLanguage
        {
          get;
          set;
        }
    
        public void Process(Sitecore.Pipelines.HttpRequest.HttpRequestArgs args)
        {
          if (Sitecore.Context.Item == null)
          {
            string message = String.Format("{0} : context item null : {1}", this.GetType().ToString(),
                                           Sitecore.Web.WebUtil.GetRawUrl());
            Sitecore.Diagnostics.Log.Error( message, this);
          }
          else if ( Sitecore.Context.Site == null )
          {
            string message = String.Format("{0} : context site null : {1}", this.GetType().ToString(),
                                           Sitecore.Web.WebUtil.GetRawUrl());
            Sitecore.Diagnostics.Log.Error( message, this );
          }
          else if ( LanguageSetFromUrlPath()
                   || LanguageSetFromQueryString()
                   || LanguageSetFromCookie()
                   || LanguageSetFromBrowserPreferences()
                   || LanguageSetFromContextSite()
                   || LanguageSetFromDefaultSetting()
                   || LanguageSetToFirstExistingLanguage())
          {
            if (SetCulture)
            {
              System.Threading.Thread.CurrentThread.CurrentUICulture =
                new System.Globalization.CultureInfo(Sitecore.Context.Language.Name);
              System.Threading.Thread.CurrentThread.CurrentCulture =
                System.Globalization.CultureInfo.CreateSpecificCulture(Sitecore.Context.Language.Name);
            }
          }
          else
          {
            string message = String.Format("{0} : Unable to determine valid context language : {1}",
                                           this.GetType().ToString(), Sitecore.Web.WebUtil.GetRawUrl());
            Sitecore.Diagnostics.Log.Error(message, this );
          }
        }
    
        private bool LanguageSetFromUrlPath()
        {
          return Sitecore.Context.Data.FilePathLanguage != null && SetLanguage(Sitecore.Context.Data.FilePathLanguage.Name, false, true, 0);
        }
    
        private bool LanguageSetFromQueryString()
        {
          return SetLanguage(System.Web.HttpContext.Current.Request.QueryString["sc_lang"], true, true, 0);
        }
    
        private bool LanguageSetFromCookie()
        {
          return SetLanguage(Sitecore.Web.WebUtil.GetCookieValue(Sitecore.Context.Site.GetCookieKey("lang")), false, true, 0);
        }
    
        private bool LanguageSetFromBrowserPreferences()
        {
          string langs = System.Web.HttpContext.Current.Request.ServerVariables["HTTP_ACCEPT_LANGUAGE"];
    
          if (!String.IsNullOrEmpty(langs))
          {
            foreach (string lang in Sitecore.StringUtil.Split(langs, ',', true))
            {
              string langName = lang;
    
              if (lang.IndexOf(';') > -1)
              {
                langName = lang.Substring(0, lang.IndexOf(';'));
              }
    
              if (SetLanguage(langName, false,false, 0))
              {
                return true;
              }
            }
          }
    
          return false;
        }
    
        private bool LanguageSetFromContextSite()
        {
          return SetLanguage(Sitecore.Context.Site.Language, false, true, 0);
        }
    
        private bool LanguageSetFromDefaultSetting()
        {
          return SetLanguage(Sitecore.Configuration.Settings.DefaultLanguage, false,true, 0);
        }
    
        private bool LanguageSetToFirstExistingLanguage()
        {
          foreach (Sitecore.Globalization.Language language in Sitecore.Context.Item.Database.Languages)
          {
            if (SetLanguage(language.Name, false, false, 0))
            {
              return true;
            }
          }
    
          return false;
        }
    
        private bool SetLanguage(string languageName, bool spanRequests, bool fallback, int fallbackDepth )
        {
          if (!String.IsNullOrEmpty(languageName))
          {
            foreach (Sitecore.Globalization.Language compare in Sitecore.Context.Item.Database.Languages)
            {
              if (languageName.Equals(compare.Name, StringComparison.InvariantCultureIgnoreCase))
              {
                if (HasVersionInLanguage(Sitecore.Context.Item, compare))
                {
                  SetContextLanguage(compare, spanRequests);
                  return true;
                }
                else if (fallback)
                {
                  Sitecore.Data.Items.Item languageItem =
                    Sitecore.Context.Item.Database.GetItem("/sitecore/system/languages" + compare.Name);
    
                  if (languageItem != null)
                  {
                    string fallbackLanguageName = languageItem["fallback language"];
    
                    if (!String.IsNullOrEmpty(fallbackLanguageName))
                    {
                      if (fallbackLanguageName == languageName)
                      {
                        string message = String.Format("{0} : invalid fallback language {1} in {2}",
                                                       this.GetType().ToString(), fallbackLanguageName,
                                                       languageItem.Paths.FullPath);
                        Sitecore.Diagnostics.Log.Error( message, this );
                      }
                      else
                      {
                        if ( fallbackDepth < FallbackDepthLimit )
                        {
                          return( SetLanguage(fallbackLanguageName, spanRequests, fallback, fallbackDepth++));
                        }
                        else
                        {
                          string message = String.Format("{0} : Fallback depth limit {1} exceeded processing {2}",
                                                         this.GetType().ToString(), FallbackDepthLimit, fallbackLanguageName);
                          Sitecore.Diagnostics.Log.Warn(message, this);
                        }
                      }
                    }
                  }
                }
              }
            }
          }
    
          return false;
        }
    
        private bool HasVersionInLanguage(Sitecore.Data.Items.Item item,Sitecore.Globalization.Language language)
        {
          Sitecore.Data.Items.Item langItem = Sitecore.Context.Item.Database.GetItem(Sitecore.Context.Item.ID, language);
          return langItem.Versions.Count > 0;
        }
    
        private void SetContextLanguage(Sitecore.Globalization.Language language, bool spanRequests )
        {
          Sitecore.Context.SetLanguage(language, spanRequests);
    
          if ( spanRequests && PersistLanguage )
          {
            string cookieName = Sitecore.Context.Site.GetCookieKey("lang");
            Sitecore.Web.WebUtil.SetCookieValue( cookieName, language.Name,DateTime.MaxValue);
          }
        }
      }
    }
    

    This code could definitely use some refactoring. The following properties, which default to false unless set in the processor signature in web.config, serve the purposes described:

    • FallbackDepthLimit - In case someone configures infinite recursion in fallback languages.
    • SetCulture - Whether to set System.Threading.Thread.CurrentThread.CurrentUICulture and System.Threading.Thread.CurrentThread.CurrentCulture after determining the context language.
    • PersistLanguage - Whether to create a persistent cookie, to persist the language selection between browser requests.

    The following methods serve the purposes described:

    • Process() - The pipeline processor body.
    • LanguageSetFromFUrlPath() - Set the context language and return True if the method can determine the language from the URL path.
    • LanguageSetFromQueryString() - Set the context language and return True if the method can determine the language from the sc_lang URL query string parameter. This is the only logic in this processor that sets a cookie, so that subsequent URLs do not have to include the URL query string parameter (all of the other variables used to set the context language should appear in all HTTP requests, so there should be no need for a cookie).
    • LanguageSetFromCookie() - Set the context language and return True if the method can determine the language from the cookie.
    • LanguageSetFromBrowserPreferences() - Set the context language and return True if the method can determine the language from browser preferences.
    • LanguageSetFromContextSite() - Set the context language and return True if the method can determine the language from the context site.
    • LanguageSetFromDefaultSetting() - Set the context language and return True if the method can determine the language from the DefaultLanguage setting.
    • LanguageSetToFirstExistingLanguage() - Set the context language and return True if the method can determine a language for which content exists for the item.
    • SetLanguage() - Sets the language and returns True if the context item contains data for the specified language, or if the method can determine the language from fallback languages.
    • HasVersionInLanguage() - Returns true if the specified item contains version data for the specified language.
    • SetContextLanguage() - Sets the context language. Used by SetLanguage().

    One thing that concerns me is how ASP.NET uses the language associated with the current thread. If we assume that the entire system processes the entire request using this thread, or passes this setting to any child threads, then I think this should be reliable. But this is obviously far beyond my knowledge of ASP.NET threading. For me, it seemed to work using the Sitecore.Web.UI.WebControls.FieldRenderer Web control, the <sc:date> XSL extension control, and a custom XSL extension method, but my test system doesn't have the kind of load that might raise threading issues.

    Updated: New code.

    11/14/2008

    MSDN Magazine Utility Spotlight Article: 12 Steps To Faster Web Pages With Visual Round Trip Analyzer

    The November 2008 issue of MSDN Magazine has a Utility Spotlight article by Jim Pierson titled "12 Steps To Faster Web Pages With Visual Round Trip Analyzer" (http://msdn.microsoft.com/en-us/magazine/dd188562.aspx). Much of this content is relatively technical, including the explanation of Visual Round Trip Analyzer. But there are also some tips that should be relatively easy to implement for any Sitecore solution. I won't try to summarize the article, but point out and expand on what I expect would be the easiest suggestions to try.

    • Use a Web client as close to the Web server as possible to minimize round-trip time. When the desktop can't be close to the server, consider remote desktop to a desktop near the server.
    • Consider configuring the browser to increase the number of simultaneous connections to Web servers. For Internet explorer, in regedit, navigate to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings, create two new DWORDs named MaxConnectionsPer1_0Server and MaxConnectionsPerServer, and set the value of each to 10. If you use IIS on Windows XP, you may want to set these values to 5 because that version of IIS starts throwing errors if it receives more than 5 simultaneous requests. Be sure to select decimal instead of hex, and don't set the value above 10 if you use IIS on Vista as I've heard Microsoft limits that edition to processing 10 HTTP requests concurrently. Internet Explorer 8 may change the default values for these settings.
    • Minimize the number of small files in each page.
    • Use the JavaScript document.write() function to generate <script> elements instead of embedding <script> elements in HTML. Internet Explorer 8 may work around the issue that makes dynamic <script> elements more efficient than <script> elements in markup.
    • Ensure that keep-alives are enabled (they are enabled by default in IIS).
    • Set expiration dates in HTTP headers, but avoid entity tags (Etags).
    • Avoid redirects. Try to get the old URL to serve the content provided by the new URL. I have reservations about this tip because it could cause a search engine to index the same content twice at two different URLs.
    • Use HTTP compression, preferably performed by a load balancer. Try different compression formats to see which is most efficient for your workload, but don't use formats common browsers don't accept.
    • Use the most efficient format (png, jpg, gif, etc.) for each image on your site. Optimize your images, and generate thumbnails on the server instead of just using the full-size image with lower height and width attributes values.
    • Minimize whitespace and comments in HTML, CSS, JavaScript, and other text.
    • Optimize your CSS to avoid redundancy.
    • Use the instructions in the article for Visual Round Trip Analyzer (http://www.microsoft.com/downloads/details.aspx?FamilyID=119f3477-dced-41e3-a0e7-d8b5cae893a3&displaylang=en).
    11/10/2008

    Southern Methodist University Mailing List for Higher Education and Public Sector Sitecore Administrators

    Sitecore USA customer Southern Methodist University has created a mailing list for administrators using or considering Sitecore for their Web solutions. To join the list, see http://sdn.sitecore.net/forum/ShowPost.aspx?PostID=12546#12546.

    The list owner specifically requests no posting of advertisements on the list.