You are here: Home

Waldek Mastykarz moved to http://blog.mastykarz.nl

Posted by waldek mastykarz
No Comments »

A bit more than a half year ago, when I decided to start blogging, I wasn't sure whether it would going to work. Most of all I was afraid about the number of interesting articles I would write and finding the right balance between my work, this blog and my personal life. That is why I initially decided to start this blog together with my two colleagues: Erik and John. We have decided to support each other in continuing providing of new and interesting content. If one of was busy the other one would take it over.

Half year of blogging isn't that much I admit. Yet I have succeeded in dividing my time into work, my hobbies and my personal life. By getting more confidence into blogging I have decided to start my own blog at http://blog.mastykarz.nl. Next to starting a blog under my own name, and on my own domain (what sometimes provide you just the right amount of flexibility), my decision has good influence on the 'Innovation matters' program running here at Imtech ICT Business Solutions since a few months. More about the program and our work coming really soon.

So far I have migrated all my articles from this blog so you will find all the articles on my new blog as well. Furthermore I'm going to pick it up just where I stopped: I'm going to post the new version of the SharePoint Solution Generator exporting Lists to List Definitions and continue sharing my experience with SharePoint and related technologies.

This is my last post at Tech MOSS Team blog. Although this blog with stay available I'm not really sure for how long. I have already asked Dustin Miller to publish my new blog on SharePoint blogs so my articles will still be accessible to all of you subscribed to the SharePointBlogs.com feed.

Inconvenient SharePoint 2007 localization

Posted by waldek mastykarz
No Comments »

SharePoint 2007 has built-in support for Resource files helping you achieve localization. Using these files you are able to set localized labels for the resources being provisioned. Furthermore you are able to use Resource files within your assemblies to support localization on run time. Yet there is something wrong with localization in SharePoint 2007…

One of the requirements for the project I'm currently working on is that the solution must be built on top of Dutch version of SharePoint 2007. Just to spare myself some unpleasant surprises I have decided to use that particular locale from the very beginning of the project. I got really surprised when I got a vague exception in Dutch while activating one of the Features we deliver standard with our projects. After a little research I have found out the reason of the exception: I have tried to access the Master Page Gallery using SPWeb.Lists["Master Page Gallery"]. However, since I was working with the Dutch locale it didn't exist. There was that strange thing called "Galerie met basispagina's" instead.

Looking for a solution I took a look at the SharePoint object model. SPListCollection gives you three ways to obtain a list: you can use either the ID (GUID), list index (???) and the list name. It's not really convenient as all these methods use parameters which refer to variable values. Of course you could try loading the Dutch resource file shipped with the Dutch language pack for SharePoint and then retrieving the "Galerie met basispagina's" string – it just seems a little too much to me. Why you can't get a list using the only constant information like… it's URL?!

If you take a look at the SPList class you will find that there is no Url property. Instead there is this thing called RootFolder (SPFolder) which does contain a Url property which contains the site relative url of the particular list – _catalogs/masterpage in case of "Galerie met basispagina's".

Recently I have started working with Extension Methods. Briefly: they allow you to add a custom method to an existing type, for example: SPListCollection.GetListByRootFolderUrl(string rootFolderUrl). Personally I think it's a great possibility to extend existing object models allowing you to work with the code in a more natural way.

The solution

public static SPList GetListByRootFolderUrl(this SPListCollection lists,              string rootFolderUrl)
{
    SPList list = null;
    foreach (SPList l in lists)
    {
        if (String.Compare(rootFolderUrl,            l.RootFolder.Url,            StringComparison.CurrentCultureIgnoreCase) == 0)
        {
            list = l;
            break;
        }
    }

    return list;
}

The solution is really straight-forward: just iterate through all available lists in the SPListCollection and return the one which has the RootFolder Url which corresponds to the rootFolderUrl parameter. If you implement the above method in a public static class, you will be able to use it like SPWeb.Lists.GetListByRootFolderUrl("_catalogs/masterpage") what will solve your SharePoint 2007 localization problem… this time.

Inconvenient SharePoint Solution Generator

Posted by waldek mastykarz
No Comments »

While working with the SharePoint Solution Generator (a utility which ships with Visual Studio 2005 extensions for Windows SharePoint Services 3.0) I have stumbled upon an interesting by-design feature which affects the way you develop Features – at least if you're using or planning to use the SharePoint Solution Generator (SPSolGen).

SPSolGen allows you to create a List Definition based on an existing list. It is extremely useful since the CAML schema definition is almost unreadable if you work with complex lists. What SharePoint Solution Generator does is it allows you to select an existing list using a GUI and then creates for you a Visual Studio 2005 project which contains the exported List Definition wrapped up in a SharePoint Feature.

In my previous article I have described the problems I have faced while using SharePoint Solution Generator on my development environment using Visual Studio 2008. Briefly: SharePoint Solution Generator doesn't work at all. While working on a solution for this problem I have had a look at the code of SharePoint Solution Generator to figure out the way it works.

After I have finished my solution I have started it. Exporting a list created using one of the standard List Definitions (like Documents, Custom List, etc.) worked perfectly. Then I have added the exported List Definition into my existing ContentDefinition Feature which wraps up provisioning custom Columns, Content Types and List Definitions. After creating a list based on my newly exported List Definition I have noticed that I'm not able to export it using SharePoint Solution Generator anymore. An export resulted each time in an exception: Object reference not set to an instance of an object. After some research I have found out the reason of this error.

In my previous article I have mentioned that SPSolGen modifies the Schema.xml after it has been exported. These modifications are required in order to make the List Definition work. At some point SPSolGen checks to which Feature the List Definition belongs. This information is obtained using the XmlNode.SelectSingleNode method which, I have figured out, was the source of the exception I have faced.

At the point of deployment my Feature contained the definition of custom Columns, Content Types and List Definitions in this particular order. The SelectSingleNode method, used by SharePoint Solution Generator for obtaining Feature information, retrieves the first node which complies to the given XPath parameter. In case of my Feature the Columns definition instead of the Element Manifest containing my List Definition. As the Columns definition doesn't contain the ListTemplate element, the export method throws the exception I have mentioned before. Turning the Element Manifests around and putting the List Definitions before Columns solved the problem eventually. The Element Manifest containing the List Definition has to be the first Element Manifest defined in your Feature in order to be able to export a list based on this List Definition using SharePoint Solution Generator.

Such an approach has a major drawback: you are not able to provision multiple List Definitions from one Feature. Both methods for obtaining Feature and List Definition information use the SelectSingleNode method which retrieves the first node selected by the passed XPath query. This fact changes the approach to the custom development in SharePoint 2007 as it requires you to define a Feature for each List Definition you want to provision.

In my previous article I have mentioned that I will try to publish a custom StsAdm command allowing you to export existing lists to List Definitions in development environments using Visual Studio 2008 as soon as possible. Before the release I will try to think off a better alternative to the SelectSingleNode method which will provide the developers some more flexibility. Feel free to comment if you have any ideas or suggestions.

Exporting List Definitions in a development environment using Visual Studio 2008

Posted by waldek mastykarz
No Comments »

Visual Studio 2005 extensions for Windows SharePoint Services 3.0 (VSeWSS) ship with a useful tool called SharePoint Solutions Generator. One of its features I use most often is creating a List Definition based on an existing list. Since the CAML schema of a list isn't really straight-forward – especially if the list uses complex settings and multiple custom views it's almost undoable to create such schema manually. Another possibility could be using List Templates instead of List Definitions. List Templates are unfortunately impossible/difficult to customize and should be used, in my opinion, in SharePoint configuration scenarios when there is no other choice. SharePoint Solution Generator comes very useful if you want to work with List Definitions but you don't want to type all the CAML yourself.

VSeWSS has however one major flaw: it doesn't work on development environments using Visual Studio 2008. First of all the extensions will not even install as the setup checks the Visual Studio 2005 dependency. Even bypassing the setup and copying the installed version of SharePoint Solution Generator to a development environment using VS 2008 doesn't work: SharePoint Solution Generator throws an exception during the start. The fact that there is no tool for exporting an existing list to a List Definition has suddenly become a serious risk in the project I'm currently working on. Installing VS 2005 next to VS 2008 might be a solution. I haven't tried it personally because it simply didn't seem right to me: installing a huge IDE in order to be able to run a tiny (yet very useful) utility.

While exploring the possible solutions I have found out that SharePoint Solution Generator exports lists to List Definitions using the /_vti_bin/owssrv.dll. The call is very simple: http://yoursite/_vti_bin/owssrv.dll?cmd=ExportList&List=<List GUID>. This call returns the contents of the Schema.xml file of a List Definition. The list forms (AllItems.aspx, NewForm.aspx, etc.) can be retrieved using the Files property of a list. Further research has proven unfortunately that the exported Schema.xml doesn't work: creating a list based on this schema results in an exception. Looking deeper into SharePoint Solution Generator I have noticed that the tool alters the exported Schema.xml after it has been exported. It's quite odd that the WSS team has created a functionality for exporting the Schema.xml which cannot be exported unless altered!

Eventually I have succeeded in wrapping it all together into a working package. I have created a custom StsAdm command called ocdexportlist (ocd stands for One Click Deployment – the development strategy we have designed and developed together with some custom tools. More about OCD coming soon). I will try to publish the command before I go to the Microsoft Office System Developer Conference 2008. Stay in touch for updates.

Configuring SSL in SharePoint 2007 development environment

Posted by waldek mastykarz
No Comments »

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?

Posted by waldek mastykarz
No Comments »

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

Posted by waldek mastykarz
No Comments »

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

Posted by waldek mastykarz
No Comments »

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

Posted by waldek mastykarz
No Comments »

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, @"{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 (): 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

Posted by waldek mastykarz
No Comments »

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.