in

SharePoint Blogs

The Best Place for SharePoint-related Blogs

Tech MOSS Team

January 2008 - Posts

  • Configuring SSL in SharePoint 2007 development environment

    A SharePoint 2007 development can get quite complex depending on the business case and requirements of your customer. Last year I have worked on a few SharePoint 2007 solutions. During the development I have noticed that it is extremely helpful if you know how the customer's infrastructure will look like. It will help you even more if you will configure your development environment to resemble the customer's infrastructure as much as possible.

    One of the things you should definitely consider is working with anonymous access and SSL support from the very beginning if applicable because they have major impact on the custom code you might need to create. Examples of the things you should be considering are Regular Expression for url parsing and privileges elevation if required to access some of the SharePoint properties. Finding out that your solution doesn't work in the real environment might be painful - especially if it's after it all has been deployed.

    Setting up anonymous access in SharePoint 2007 is really straight forward and can be done by turning on two checkboxes. It is a bit more difficult to set up a working SSL certificate on your development machine though. It's all get difficult if you don't have the access to a server issuing certificates and all you want is a dummy certificate for development purposes only.

    Let's begin with creating a new SharePoint 2007 Web Application which will use SSL:

    SharePointSSL_WebApp

    The most important here is setting up the port to 443 and enabling SSL support. Configuring these settings correctly should automatically create the correct load balanced url beginning with https and ending with :443.

    Now we have the Web Application, we are ready to create and link the SSL certificate. I have assumed you don't have access to a certificate server and you need to create an SSL certificate by yourself. To do so, you will first of all need the IIS 6.0 Resource Kit Tools. It contains a tool called SelfSSL which will create and link the dummy SSL certificate. After the installation you are almost ready to run the tool. The last detail you need to have is the ID of your Web Application which is required by SelfSSL. You can obtain it quite easily by running the IIS Manager > Properties of your Web Application and then opening the Logging Properties dialog.

    SharePointSSL_LoggingProperties

    The Web Application ID is the long number following W3SVC and in our case is 75208739:

    SharePointSSL_SiteID

    Now we have all the details we must run SelfSSL by calling from the command prompt:

    SelfSSL.exe /S:75208739 /T /Q

    The SSL certificate will get automatically created and linked to our Web Application which will allow us to work with SharePoint through SSL.

    Summary

    Making your SharePoint 2007 development environment resemble the customer's production environment turns very useful during custom development. It allows you to debug your solution earlier and much more accurately. Furthermore you are able to test your deployment procedure much earlier in your development process what will spare you some unpleasant surprises afterwards.
    Configuring anonymous access and SSL support if applicable isn't very difficult and covers the most common development issues. It is therefore worth making an integral part of your SharePoint 2007 development environment initiation.

  • Which Doctype to use with SharePoint 2007?

    Designing and developing accessible web sites on top of SharePoint 2007 gets more and more attention in the community. But the more developers try to reach the required accessibility or standards compliancy level, the more challenges they face and the more questions pop up. One of such questions is which doctype should be used for standards compliant and accessible web sites.

    As for SharePoint 2007 the answer is simple: XHTML - it is the only possibility. SharePoint 2007 is built upon ASP.NET 2.0: it makes use of the ASP.NET 2.0 runtime and enriches it with extra functionality. ASP.NET 2.0 has been designed to render XHTML output. Although the compliancy level can be set in web.config not all controls adjust their output to this configuration. Furthermore the runtime itself uses hidden fields for State Management: all these fields contains the ID attribute which begins with __ (double underscore). Unfortunately such names are allowed only in XHTML so there is no chance for fallback to HTML 4.01 unless you want to rewrite the ASP.NET 2.0 runtime.

    The choice for XHTML as the only possible doctype for ASP.NET 2.0 and SharePoint 2007 applications is quite odd knowing it's the only doctype so far officially not supported by Microsoft browsers. According to the specification all XHTML pages should be served with the application/xhtml+xml mime-type. This allows the parser (no matter whether it is a User Agent or another information system) to process the file as if it was an XML document. None of the Internet Explorer versions can deal with the application/xhtml+xml mime-type. Microsoft Internet Explorer 6.0 doesn't render the page at all and Windows Internet Explorer 7.0 renders it as an XML document. Although there are some tricks available to bypass this default behavior and you could vary the doctype depending on the User Agent requesting the page using content negotiation and browser headers, it would lead to serving two different content versions - both standards incompliant. Because of the ASP.NET 2.0 framework XHTML remains the only doctype allowing you to achieve standards compliancy.

    Luckily XHTML pages can be served with the text/html mime-type known from the HTML 4.01 pages. All currently available Internet browsers parse XHTML pages correctly regardless of the mime-type. Even the W3C Validation service returns no errors during the validation of an XHTML page served with the text/html mime-type - not even a warning.

    There are some major drawbacks for using the text/html mime-type with XHTML pages though. These are important particularly when you want to process XHTML pages by external information systems. In such situations the complexity of such documents arises as there are some workarounds required. One of the elements affected by the incorrect mime-type are the JavaScript scripts and CSS styles within the XHTML document which required additional markup for their comments. Using in-page script and style elements isn't a best practice yet it's definitely quite a plausible scenario.

    Another major issue is the output produced by the authoring tools available in SharePoint 2007. The standard Rich Text Editor (RTE) produces output which is far from XHTML compliant. Even it's well known substitute from Telerik can't do anything about it as the generated markup (which is XHTML compliant) is being altered on saving by SharePoint 2007 runtime.

    Knowing all this, the perfect choice would be the XHTML 1.0 Transitional doctype. Unfortunately some accessibility guidelines, among which the Dutch government guidelines (Webrichtlijnen: http://webrichtlijnen.overheid.nl), don't allow you to use the Transitional variants. This has serious consequences for the authoring environment and the content presentation. Because the XHTML pages are being processed like XML documents, even the smallest syntax error will result in an XML error message instead of rendering the document. SharePoint 2007 standard authoring environment produces standards incompliant markup. Furthermore SharePoint 2007 doesn't even allow you to use external tools as the markup is being modified on save anyway.

    By eliminating the possible solutions we came to the presentation layer of SharePoint 2007. As it is highly customizable - especially in Publishing Sites - it seems almost a perfect solution. Well it is, almost. You can very easily encapsulate the required logic in a custom control. The downside is, you will end up cleaning the output on the run-time so caching the rendered page will be required in most environments.

    Summary

    As a developer of an accessible or a standards compliant web site built upon SharePoint 2007 you have to deal with multiple challenges. First of all you have to use the XHTML doctype. If you're lucky you can apply the Transitional variant which will support you while dealing with the incompliant markup created by the SharePoint 2007 RTE. No matter if you use the Strict or the Transitional variant of XHTML you are very likely to need to do something about the markup generated by SharePoint 2007. As there isn't much choice left you will end up with a custom control responsible for rendering the markup compliant with the the XHTML standard. It doesn't seem to be the perfect solution but it works. Getting it done in a real life scenario will require a caching solution to suppress the extra load caused by content cleansing on the run-time.

  • SharePoint 2007 redirect solved: using 301 instead of 302 redirects

    Each time you request a Site Collection (http://domain/) or a Site (http://domain/foo/) of your Publishing Site you get redirected to the .aspx">http://domain/Pages/<WelcomePage>.aspx. SharePoint 2007 uses the 302 header (location temporarily moved) for this purpose. Surprisingly even WSS uses the 302 header to redirect a root url to the default.aspx. In comparison ASP.NET uses an internal redirect to render the default page when the root url requested: there is no redirect in this situation.

    The whole issue about the 302 headers is that the redirected locations don't get crawled by search spiders which don't follow temporarily moved pages. While it's not really an issue for intranet environments it has major impact on indexing the content of Internet-facing web sites and making them searchable using a search engine.

    Looking for an answer I have researched the SharePoint runtime: SPHttpHandler, SPRequestModule and PublishingHttpModule classes. As none of these has given me a clear answer I have noticed that there are multiple references to the Redirect method present in the code which uses the 302 header as well.

    To solve the issue I have designed a custom redirect HttpModule which uses 301 headers instead.

    The requirements

    The module must rewrite all request for a Site Collection or Site. Url's of these request might but don't have to contain trailing slash (/). Furthermore the module must distinct a WSS request from a Publishing Site / Publishing Web request. Also the module has to be aware of Variations if used by the Site Collection.

    The work

    Firs of all we create a new HttpModule. As we want the redirect to find place as soon as possible we will hook it up in the BeginRequest event. Furthermore we want the module to be the first one to interact with the request. As we use an external assembly we need to define it as the first element in the httpModules section of web.config.

    namespace Imtech.SharePoint.Enhancement.HttpModules
    {
    public class RedirectModule : IHttpModule
    {
    #region IHttpModule Members

    public void Dispose()
    { }

    public void Init(HttpApplication context)
    {
    context.BeginRequest +=
    new EventHandler(context_BeginRequest);
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
    HttpApplication app = (HttpApplication)sender;
    string requestUrl = app.Request.Url.ToString();
    }

    #endregion
    }
    }

    Because we will need the Request url later on in quite a few places I have decided to store it in a separate variable.

    The first requirement states that the module should redirect only requests for Site Collections and Sites. If the requirement wouldn't have say that the trailing slash is optional you could solve it using a simple if (requestUrl.EndsWith("/")). In our situation we will have to use a Regular Expression in order to figure out whether we need to rewrite the url or not.

    Regex regEx =
    new Regex(@"^https?://.*(?<itemUrl>/[^/]+\.[^/\.]+)$");
    if (regEx.IsMatch(requestUrl))
    return;

    if (!requestUrl.EndsWith("/",
    StringComparison.CurrentCulture))
    requestUrl += "/";

    If the url matches the regular expression it means it's a page request and should be passed on along the request pipeline unaltered. Later in the module we will combine the request url with the page url. As the trailing slash is optional I have decided to add it at the end if not present - just to be sure that combining the destination url of different parts will produce correct result.

    The next requirement is distinction between WSS and Publishing Site requests.

    string destinationUrl = String.Empty;

    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
    try
    {
    using (SPSite site = new SPSite(requestUrl))
    {
    using (SPWeb web = site.OpenWeb())
    {
    if (PublishingWeb.IsPublishingWeb(web))
    destinationUrl = String.Concat(requestUrl,
    publishingWeb.DefaultPage.Url);
    else
    destinationUrl = String.Concat(requestUrl,
    "default.aspx");
    }
    }
    }
    catch { }
    });

    Based on the request url we create a new instance of SPSite and then open the requested web. As we can fail at this point already (for example when passing a list url) I have decided to catch the thrown exception to avoid turning the request into an error message. The distinction itself is quite straight forward and makes use of the IsPublishingWeb method. One important thing: because we are very likely to use the module for anonymous users we need to run the code with elevated privileges: the IsPublishingWeb method requires some extra permission in order to run.

    Our last requirement was making the redirect module aware of Variations if used by the Site Collection. Depending on the requirements defined by your customer you might need to implement the standard SharePoint Variation logic which chooses the variation basin on the User Agent language settings. Unfortunately most users are not aware of the existence and usage possibilities of the language settings most of our customers choose to load the Dutch variation by default. If your customer requires the standard SharePoint approach you would need to implement the logic from the VariationRootLanding User Control in the ControlTemplates directory. I will focus on the scenario we're using.

    PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);
    if (publishingWeb.DefaultPage.Url.EndsWith("/VariationRoot.aspx",
    StringComparison.CurrentCultureIgnoreCase))
    {
    string defaultPage = String.Empty;
    using (SPWeb nlWeb = site.OpenWeb("nl"))
    {
    defaultPage =
    PublishingWeb.GetPublishingWeb(nlWeb).DefaultPage.Url;
    }

    destinationUrl = String.Concat(requestUrl, "nl/", defaultPage);
    }
    else
    destinationUrl =
    String.Concat(requestUrl, publishingWeb.DefaultPage.Url);

    In most scenarios the variation redirect finds place at the Site Collection level. The default page of the root web is then set to Pages/VariationRoot.aspx. Knowing this we can check whether we need to use the variation redirect or not. The rest is quite straight-forward: we obtain the Dutch site and its Welcome Page.

    The last part is the redirect itself using the 301 header:

    if (!String.IsNullOrEmpty(destinationUrl))
    {
    app.Response.AddHeader("Location", destinationUrl);
    app.Response.StatusCode = 301;
    }

    The destination url might be empty if an exception has occurred during the request processing. We will therefore redirect only if a destination url has been set by our module.

    Putting it all together:

    namespace Imtech.SharePoint.Enhancement.HttpModules
    {
    public class RedirectModule : IHttpModule
    {
    #region IHttpModule Members

    public void Dispose()
    { }

    public void Init(HttpApplication context)
    {
    context.BeginRequest +=
    new EventHandler(context_BeginRequest);
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
    HttpApplication app = (HttpApplication)sender;
    string requestUrl = app.Request.Url.ToString();
    Regex regEx =
    new Regex(@"^https?://.*(?<itemUrl>/[^/]+\.[^/\.]+)$");
    if (regEx.IsMatch(requestUrl))
    return;

    if (!requestUrl.EndsWith("/",
    StringComparison.CurrentCulture))
    requestUrl += "/";

    string destinationUrl = String.Empty;

    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
    try
    {
    using (SPSite site = new SPSite(requestUrl))
    {
    using (SPWeb web = site.OpenWeb())
    {
    if (PublishingWeb.IsPublishingWeb(web))
    {
    PublishingWeb publishingWeb =
    PublishingWeb.GetPublishingWeb(web);
    if (publishingWeb.DefaultPage.Url.
    EndsWith("/VariationRoot.aspx",
    StringComparison.CurrentCultureIgnoreCase))
    {
    string defaultPage = String.Empty;
    using (SPWeb nlWeb = site.OpenWeb("nl"))
    {
    defaultPage =
    PublishingWeb.GetPublishingWeb(nlWeb)
    .DefaultPage.Url;
    }

    destinationUrl = String.Concat(requestUrl,
    "nl/", defaultPage);
    }
    else
    destinationUrl = String.Concat(requestUrl,
    publishingWeb.DefaultPage.Url);
    }
    else
    destinationUrl = String.Concat(requestUrl, "default.aspx");
    }
    }
    }
    catch { }
    });

    if (!String.IsNullOrEmpty(destinationUrl))
    {
    app.Response.AddHeader("Location", destinationUrl);
    app.Response.StatusCode = 301;
    }
    }

    #endregion
    }
    }

    To see it working build the project, copy the assembly to the bin directory of your web application and add the following element to the httpModules section of the web.config:

    <add name="ImtechRedirectModule"
    type="Imtech.SharePoint.Enhancement.HttpModules.RedirectModule" />

    Summary

    Redirects using the 302 header can form a serious issue on Internet-facing web sites as it comes to indexing the content of a web site. Using custom HttpModules to overrule the standard behavior of SharePoint is a flexible solution for this challenge.
    The example above should work good enough in most scenarios. Depending on the requirements of your customers you might need to extend it with some extra functionality like for example standard Variations logic support. Custom HttpModules prove the extensibility and flexibility of SharePoint 2007 and the way it can be made to fit various requirements and scenarios.

  • The impact of developing an accessible web site in SharePoint 2007

    The development process of a typical Web Content Management solution based on Microsoft Office SharePoint Server 2007 consists of three main areas: User Experience, Functionality and Deployment.

    SharePointSolution

    The development process begins mostly with designing the total User Experience. Based on the business requirements the designers determine the particular areas on the web site and the corresponding functionality. The typical products of this stage are the User Interface (UI) design and the Interaction design – the way the UI responds to the user input.

    The next stage is translating the User Interface into code: HTML, Cascade Style Sheets (CSS) and JavaScript (if any client-side interaction required). As the products of this cycle form the baseline for the solution to be delivered it is crucial to name all the requirements of the total product and in particular the accessibility aspects.

    During the last stage of the User Experience phase the translated User Interface is being incorporated into SharePoint 2007. At this moment the Master Pages and Page Layouts are being formed and the content areas and the Web Part Zones are being determined and placed within the Page Layouts.

    In many cases the User Interface incorporated in SharePoint 2007 is being hand over to the developers who are going to fill it with the dynamic controls they are going to build. Using the original User Experience translations the developers start their work. First of all basing on the original User Experience they design the look & feel and the behavior of the dynamic control and Web Parts. Then they take the pieces of the User Interface translation and incorporate them within the custom controls.

    As soon as the development stage is finished and the whole solution has been tested, it is being prepared to be deployed on various environments such as test or production.

    The decision of developing an accessible WCM solution based on SharePoint 2007 has a major impact on the development process. The affected areas have been marked orange (product independent) and green (requires product specific knowledge) on the figure below.

    SharePointSolutionAccessibility

    First of all the designers have to take accessibility into consideration while designing the User Interface. Drop down menu’s dependant on JavaScript and mouse centered interface for example can lead to a totally inaccessible web site. Choices like these can have major impact on the overall accessibility of the web site and the presented information therefore they need to made very carefully and according to best practices for developing accessible web sites.

    The products made during the translation stage form the baseline for the whole web site. The accessibility of the information depends on the quality of the delivered code. That is why it is crucial to be sure that the right patterns and practices have been chosen to translate the drawing into code. The translation can also have impact on the total performance of the web site therefore it definitely shouldn’t be underestimated.

    During the incorporation of the translated User Interface into SharePoint the designers are very likely to face many of the undocumented features of SharePoint 2007. Originally SharePoint’s interface is table-based and my experience shows that even a little alteration can have impact on the SharePoint engine. If made carefully the decisions taken at this point can make the development stage easier and can guarantee very high level of accessibility.

    The developers are responsible for designing and developing custom controls which in many cases present dynamic content. Many of these developers are .NET developers with some SharePoint experience and very little to none accessibility knowledge. At this stage the web site has the biggest risk of getting inaccessible. Because of the little accessibility knowledge the most developers have, they are not able to estimate the impact of their choices on the accessibility aspects of a web site.

    Technical Challenges

    Microsoft Office SharePoint Server 2007 is built upon the ASP.NET 2.0 framework. In many aspects it enriches this platform and extends the available functionality. Unfortunately the dynamic interface and the extensibility have major impact on the standards compliance and the rendered output.

    Challenges

    The figure above presents various areas of custom development within ASP.NET 2.0 and SharePoint 2007. The green areas are the one which are fully controlled by the developers. The orange areas are the challenges that the developers will face while trying to deliver an accessible SharePoint 2007 solution. Though the impact on the accessibility depends on the particular area, all of them are required to make a web site fully accessible. The last piece – the Editors Area is left unaltered because it has no influence on the accessibility of the public side of a web site - the area you will focus on most of the times.

    Once again developing an accessible web site in SharePoint 2007 is doable yet very challenging. It requires understanding of the accessibility guidelines, SharePoint 2007 and ASP.NET 2.0 internals and accessibility issues within the both platforms.

  • Automatically marking up abbreviations and acronyms in SharePoint 2007

    Accessibility is a broad term and reaches way beyond the standards compliant code only. Accessibility is in my belief a set of features improving the understanding of information presented by an information system. I have to admit though compliant and semantic HTML is a very important factor of accessibility as it hosts the information. As I have recently solved the issue of standards compliant HTML in SharePoint 2007 I have started looking for new challenges and accessibility improving solutions. Almost immediately I have stumbled upon automatically marking up abbreviations in content.

    I have faced exactly the same challenge during the Rock My Website competition last year when John, Martijn and myself were building an accessible web site in ASP.NET. I wanted to implement a solution which would automatically markup all known abbreviations in de content using some kind of dictionary. As we weren't using any Content Management System we would need to think of another way to maintain the abbreviations dictionary. Eventually we have dropped the idea then, but now we have SharePoint 2007.

    Looking at standard SharePoint 2007 features I have almost immediately came up with a solution for this challenge.

    The requirements

    First of all to Provide a user friendly way to maintain the abbreviations dictionary. Storing it in a central location will decrease the amount of work required to maintain the dictionary and keep the definitions consistent. Then markup all the abbreviations found in the content so that HTML will become <abbr title="HyperText Markup Language">HTML</abbr> instead. Last but not least: replace only the first occurrence of an abbreviation on the page as it will provide enough information for a visually impaired visitor.

    The work

    I thought the best way for storing and maintaining the abbreviation dictionary would be a custom list consisting of two columns: Term and Definition. Let's call the list Abbreviations.

    AbbreviationsList

    Then the replacement logic. As replacing the abbreviations could be done in numerous ways I though it would be the easiest to customize the FieldValue web control and extend it with the required properties. You can find more information on this approach in one of my previous posts.

    I have decided to add two properties to our extended FieldValue control: boolean MarkupAbbreviations to be able to turn it on and off easily and AbbreviationsList to pass the URL of the list as a parameter instead of hard coding it.

    namespace Imtech.SharePoint.Compliancy.Controls
    {
    [ToolboxData("<{0}:FieldValue runat=\"server\" />")]
    public class FieldValue : WebControl
    {
    private Dictionary<string, string> abbreviations;

    private string _FieldName;
    [Bindable(true), Localizable(false)]
    public string FieldName
    {
    get { return _FieldName; }
    set { _FieldName = value; }
    }

    private bool _MarkupAbbreviations;
    [Bindable(true), Localizable(false)]
    public bool MarkupAbbreviations
    {
    get { return _MarkupAbbreviations; }
    set { _MarkupAbbreviations = value; }
    }

    private string _AbbreviationsList;
    [Bindable(true), Localizable(false)]
    public string AbbreviationsList
    {
    get { return _AbbreviationsList; }
    set { _AbbreviationsList = value; }
    }
    }
    }

    I have also added a dictionary to store the abbreviations obtained from the abbreviations dictionary list.

    Let's load the available abbreviations now:

    protected override void CreateChildControls()
    {
    LoadAbbreviations();
    base.CreateChildControls();
    }

    private void LoadAbbreviations()
    {
    try
    {
    Regex regEx =
    new Regex("(?<SiteUrl>/.*)/?Lists/(?<ListName>[^/]+)");
    Match m = regEx.Match(_AbbreviationsList);

    using (SPWeb site = SPContext.Current.Site.OpenWeb(
    m.Groups["SiteUrl"].Value))
    {
    SPList list = site.Lists[m.Groups["ListName"].Value];
    abbreviations = new Dictionary<string,string>(list.ItemCount);
    foreach (SPListItem abbreviation in list.Items)
    abbreviations.Add(abbreviation.Title,
    abbreviation["Comments"].ToString());
    }
    }
    catch { }
    }

    I have decided to get the URL of the list and the site where it resides by using regular expressions. After opening the site I open the list as we want to obtain all available items within it. Before we will walk through the available items we also need to instantiate the abbreviations variable to store the abbreviations and their definitions in code. Adding the the abbreviations to the dictionary is straight forward. In a real life scenario you might add an extra check just to get sure that you won't add the same abbreviation with various definitions twice.

    As we have the abbreviations available in code we can proceed and do the replacing in the content.

    protected override void Render(HtmlTextWriter writer)
    {
    string content = SPContext.Current.Item[_FieldName].ToString();
    markedAbbreviations = new List<string>(abbreviations.Count);

    foreach (KeyValuePair<string, string> abbreviation in abbreviations)
    {
    Regex regEx = new Regex(String.Format(
    CultureInfo.CurrentCulture, @"\b{0}",
    abbreviation.Key),
    RegexOptions.IgnoreCase);
    content = regEx.Replace(content, String.Format(
    CultureInfo.CurrentCulture,
    "<abbr title=\"{0}\">{1}</abbr>",
    abbreviation.Value,
    abbreviation.Key), 1);
    }

    writer.Write(content);
    }

    We will do the replace in the Render method. First of all we will need the content of the chosen field. To do the replace we will use the regular expressions again. This time we will use the abbreviation in combination with a word boundary (\b): if looked for HTML for example we want to find occurrences of HTML but not XHTML. Word boundary will pick only the complete matches we want. The last thing we want to add is the 1 telling the regular expression engine to replace only the first occurrence. That's it. Let's see how it works:

    Result

    In the preview example I have used Mozilla Firefox as it underlines the abbreviations with a dotted line and shows tooltips with the definition on mouse hover. The solution works according to the requirements: we have an easily maintainable abbreviations dictionary and an automatic replace of the first occurrence of an abbreviation only. The downside is that the replace occurs within a field. Should you have multiple fields containing content with abbreviations on one page and you would still want to replace the first occurrence only you would have to think of a solution to store the already replaced abbreviations in a page wide available place.

    Another extra feature might be extending the abbreviations dictionary with an extra column keeping the type of abbreviation so that they will be spoken out correctly when read using a screen reader. You would first define the CSS rules like for example:

    <style type="text/css">
    acronym {speak : normal;}
    abbr.initialism {speak : spell-out;}
    abbr.truncation {speak : normal;}
    </style>

    and then markup the various kinds of abbreviations:

    <acronym title="North Atlantic Treaty Organisation">NATO</acronym> 
    <abbr title="Hyper Text Mark-up Language" class="initialism">HTML</abbr>
    <abbr title="Europe" class="truncation">Eur</abbr>

    You could also make easily a distinction between abbreviations and acronyms (kind of abbreviations which can be pronounced as word). I hope that this solution proves how highly extensible SharePoint 2007 is and that it can support accessibility solutions as well. I would love to hear now your ideas on improving the accessibility experience in SharePoint 2007.

  • Automatically generating a hierarchical Title in <title> element

    Recently while working on an Internet facing web site for one of our customers I thought of creating a control which would automatically create a hierarchical Title in the <title> element, like: Site Collection - Current Site - Current Page. Standard SharePoint 2007 allows you to define the title on the Master Page and within a Page Layout. Default SharePoint 2007 displays the Page Title in the <title> element. As I've been recently researching the accessibility issues I have noticed that such a behavior can cause loosing the context - especially if the visitor is vision impaired. Secondly it might cause search engines indexing a page and not linking it to the organization (Site Collection) or for example a division within it (Site). You could solve it using the standard features like displaying the Site's Title using <SharePoint:ProjectProperty> but still it wouldn't provide me the flexibility I wanted it to have. That's why I have decided to make a control which would automatically generate a title based on the existing hierarchy.

    The requirements

    The most important is generating a title consisting of information from the three levels: Site Collection, Site and Page the user is currently on. As you might want to hide the Site Collection title in some cases it should be also optional whether it should be displayed or not. In my example I have used a minus as a separator: however it should be possible to set the separator without rewriting the control. The control should also be intelligent enough to be able to distinct whether the user is on the Root Web and Default Page. Another option is the possibility to reverse the title so it would display Page - Site - Site Collection instead Site Collection - Site - Page: some customers believe that it improves the readability and context. Personally I would love to leave this choice to them. Last but not least the control have to work with anonymous access - it's definitely something you should keep in mind from the very beginning as it influences the way we will be obtaining site information.

    The work

    First of all let's define the properties we will need:

    private string _Separator;
    public string Separator
    {
    get { return _Separator; }
    set { _Separator = value; }
    }

    private bool _Reversed;
    public bool Reversed
    {
    get { return _Reversed; }
    set { _Reversed = value; }
    }

    private bool _SuppressSiteCollectionTitle;
    public bool SuppressSiteCollectionTitle
    {
    get { return _SuppressSiteCollectionTitle; }
    set { _SuppressSiteCollectionTitle = value; }
    }

    Then let's implement the logic. As it's quite straight forward let's put it all simply in the Render method. Furthermore it will provide us with the required control over the rendered output as we don't want any extra elements rendered with the title.

    protected override void Render(HtmlTextWriter writer)
    {
    List<string> items = new List<string>(3);
    Guid siteId = SPContext.Current.Site.ID;
    Guid webId = SPContext.Current.Web.ID;
    }

    First of all we define some variables we are going to need later on: a list where we will store the pieces of the title (it can contain default 3 only 3 items because we will be needing only the titles of the Site Collection, Site and Page). We will need the ID's of both Site Collection and Site to open new instances with elevated privileges. As we need to obtain the titles of both of these for anonymous users as well we need to run this piece of code with elevated privileges.

    protected override void Render(HtmlTextWriter writer)
    {
    List<string> items = new List<string>(3);
    Guid siteId = SPContext.Current.Site.ID;
    Guid webId = SPContext.Current.Web.ID;

    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
    SPSite elevatedSite = new SPSite(siteId);
    SPWeb elevatedWeb = elevatedSite.OpenWeb(webId);
    if (!SuppressSiteCollectionTitle)
    items.Add(elevatedSite.RootWeb.Title);
    if (!SPContext.Current.Web.IsRootWeb || SuppressSiteCollectionTitle)
    items.Add(SPContext.Current.Web.Title);

    if (String.Compare(SPContext.Current.ListItem.Url,
    PublishingWeb.GetPublishingWeb(elevatedWeb).DefaultPage.Url,
    true) != 0)
    items.Add(SPContext.Current.ListItem.Title);

    elevatedWeb.Dispose();
    elevatedSite.Dispose();
    });
    }

    First of all we create new instances of the Site Collection and Site as we will be needing them to obtain the titles. Then we make the first check to find out whether the user has chosen to suppress the Site Collection's title part. As we go further we need to obtain the title of the Site but only if the visitor is not currently on the root site or if the developer has chosen to suppress the Site Collection's title. Without this check you would get the Site Collection's title twice while being on the home page of the web site.

    The last piece of the title is the page title. It should be displayed only if the current page is not a welcome page: in this case the page's title is equal to the Site's title. For the title comparison I'm using the Compare method instead String.ToLower() != String.ToLower() as it creates new instance of the compared strings in the memory.

    We still one requirement left:

    protected override void Render(HtmlTextWriter writer)
    {
    List<string> items = new List<string>(3);
    Guid siteId = SPContext.Current.Site.ID;
    Guid webId = SPContext.Current.Web.ID;

    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
    SPSite elevatedSite = new SPSite(siteId);
    SPWeb elevatedWeb = elevatedSite.OpenWeb(webId);
    if (!SuppressSiteCollectionTitle)
    items.Add(elevatedSite.RootWeb.Title);
    if (!SPContext.Current.Web.IsRootWeb || SuppressSiteCollectionTitle)
    items.Add(SPContext.Current.Web.Title);

    if (String.Compare(SPContext.Current.ListItem.Url,
    PublishingWeb.GetPublishingWeb(elevatedWeb).DefaultPage.Url,
    true) != 0)
    items.Add(SPContext.Current.ListItem.Title);

    elevatedWeb.Dispose();
    elevatedSite.Dispose();
    });

    if (Reversed)
    items.Reverse();

    string pageTitle = String.Empty;
    foreach (string item in items)
    {
    if (String.IsNullOrEmpty(item))
    continue;

    if (pageTitle.Length > 0)
    pageTitle += Separator;
    pageTitle += item;
    }

    writer.Write(pageTitle);
    }

    To handle the reversed order of the title we use the Reverse method of a List generic collection. All we need to do is to put the title together and render it.

    Using this control will allow you to display consistent titles across the Site Collection and will provide context information to the visitors. The best way to use it is to put it in the Master Page between the <title></title> elements. What you need to do is to hide the PlaceHolderPageTitle. You can't remove it from the Master Page and what's even more important you can't remove it from the Page Layouts as well. I have explained it why in one of my previous posts.


Need SharePoint Training? Attend a SharePoint Bootcamp!

Posts (c) their respective authors. Everything else (c) 2007 SharePoint Experts