We do not follow Sitemaps to buried treasure (Part 2 of 2)

Last week I described how we’re using sitemap files to provide custom navigational menus on our highly branded SharePoint site, and a couple of the pitfalls we encountered.  Now it’s time to discuss the meteor-impact that we fell into.

When we first extended the SP web app, and tried to test the site, the home page blue up with the ever-so-helpful “an unknown error has occurred” SharePoint error.  What made this even more useless was that the server application event log didn’t have any more detail, nor did the SharePoint server log.

With the help of one of our SharePoint infrastructure gurus, Ben, we managed to narrow the problem down through a lot of trial and error.

We changed master pages from our highly branded one to default.master, and the error went away.  We had created our master page from the MSDN-published minimal master page spec, so we were pretty confident that we weren’t missing anything critical (such as one of the required content placeholders).  Additionally, the exact same master page was being used by our internal version of the site.  Still, something about our master page wasn’t playing well with the extended SharePoint web app.

We originally thought that there was something wrong with Forms authentication (we’ve hit other problems and limitations with this on other projects).  However, a bit more testing showed that even if we left the site running with Windows authentication the error appeared.  We later found that if we did NOT enable anonymous access, the error did not appear.  It seems that hitting the site anonymously caused the error to manifest itself.

Next, we tried enabling anonymous access, and hitting the Site Settings page first.  It prompted us to log in, as we expected it would.  Then, using the same browser, we navigated back to the main page of the site.  The result?  No error.

So, the problem clearly seemed to be with enabling anonymous access, but we were still no nearer to figuring out what about the master page was really having problems with anonymous users.

I decided to create a new master page that was a copy of default.master, and piece by piece I copied over the structure from our custom master page.  When I got to the asp:Menu controls and the associated sitemap data sources, my new master page blew up.  I went back to my original master page, removed the menus entirely, and the page worked, even anonymously.  Further tinkering showed that if I left the menu and the data source controls on the page, but simply didn’t set the DataSourceID property of the asp:Menu control, the page worked.  I didn’t have any menus, but I also didn’t have any errors.

Ah, if only I could have stopped there.  Line 94 generating errors?  Well, simply comment it out!  It won’t through any more errors, will it?  Problem solved!

Alas, I had to press on.  I tried pointing my menus to one of the built-in datasource providers, and this worked.  So, the problem was definitely with my sitemap files.

On a hunch, Ben suggested checking the file security for the .sitemap files in the web root.  We did, and found that there wasn’t anything SharePoint-related in the list.  We tried giving “Everyone” full access, and tried the menu again.  This worked!  Now that we demonstrated that it was file-security related, we just needed to figure out which user and what level of security was needed.

Our process for installing SharePoint involves creating a handful of special system accounts on the network for the various SP services and jobs to run under.  We tried all of those, and none of them worked.  On another hunch we tried adding the IUSR account for the server.  Perhaps IIS itself was having problems getting to these files – maybe it wasn’t even a SharePoint thing.  That worked.  Granting the IUSR account “Read” and “Read & Execute” rights on the .sitemap files was enough to make them accessible to IIS and SharePoint, and avoids the “unknown” error.

Phew!

We haven’t gone back to try setting up Forms authentication again (we’re going to stick with Windows authentication for the content authors for the time being), but now that we’re pretty convinced that the issue was with Anonymous access, and not Forms authentication, it would probably work just fine.

What struck me as odd after all of this was that we have custom resource files in the App_GlobalResources folder, and we’ve never needed to manually tweak the access rights on these files to get them to work.  Even the web.config didn’t have these extra permissions.  I could understand it if everything that SharePoint creates in the virtual folder had this permission, but things that I drop in manually don’t.

Why do the base site files (like web.config) work without this permission?  Why do our custom resource files work without this permission?  Why doesn’t the virtual directory itself specify this permission, so that things below it can inherit that permission, and then work?  I’d love to hear if you have answers to these questions.

Advertisement

We do not follow Sitemaps to buried treasure (Part 1 of 2)

For several weeks now, I’ve been working on a MOSS project where we’re really pushing SharePoint Server to its limits in some areas, specifically branding.  Many weeks ago, when we were first putting together the new master pages (or more correctly, the first iteration of the master pages – we’ve revised them several times since then), we started realizing that the site we wanted to build had a much richer structure than what any of the built-in site map providers could generate for us.  There was a lot of cross linking going on, and multiple links to the same pages, and to be able to take advantage of the built-in site providers meant that we would have to have a REALLY convoluted site structure.

So, we opted to use a simple ASP.NET sitemap file, and build our navigation off of that.  That allowed us to keep the MOSS structure simple and logical, but still give us the flexibility to create the navigational structure we needed.  This decision came with a cost, though – the sitemap files would have to be manually maintained.  This cost seemed acceptable since the production site would only have a dozen pages added each year (at least, a dozen pages that would actually need links from the navigational menus).

The sitemap files themselves reside in the web root on the SharePoint server, and not in SharePoint-proper (aka, the content database).  For each web application that you create or extend in SharePoint, you get a new directory in C:\Inetpub\wwwroot\wss\VirtualDirectories.  Once you looking at a specific virtual directory, you’ll see the familiar folders associated with straight (e.g., non-MOSS) ASP.NET applications – bin, App_GlobalResources, etc..  The sitemap files have a very simple XML structure to them:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<siteMap xmlns=”http://schemas.microsoft.com/AspNet/SiteMap-File-1.0&#8243; >
      <siteMapNode title=”Menu” description=”Menu”>
            <siteMapNode title=”Leaf Node” description=”Leaf Node” url=”/LeafNode.aspx” />
            <siteMapNode title=”Submenu” description=”SubMenu”>
                  <siteMapNode title=”Leaf Node 2″ description=”Leaf Node 2″ url=”/LeafNode2.aspx”>
            </siteMapNode>
      </siteMapNode>
</sitemap>

The structure of the menus follows the structure of the siteMapNode tags where child tags translate into submenus.  Once you have this in place, you need to add a provider element to the web.config:

<?xml version=”1.0″ encoding=”UTF-8″ standalone=”yes”?>
<configuration>
      <system.web>
            <siteMap defaultProvider=”CurrentNavSiteMapProvider” enabled=”true”>
                  <providers>
                        <add name=”MySiteMap”
                                  type=”System.Web.XmlSiteMapProvider”
                                  siteMapFile=”MyCustomMap.sitemap” />
                  </providers>
            </siteMap>
      </system.web>
</configuration>

All of the built-in providers have entries in the sitemap/providers area.  The key attributes for your tag are:

  • “Name” will be referenced later in the master page markup (see below), and this can be anything you’d like, so long as there isn’t another sitemap provider with the same value.
  • “Type” needs to be System.Web.XmlSiteMapProvider.
  • “siteMapFile” is the name of the file itself. The path here (I think) is relative to the web root on the file system, so theoretically you could put all of your sitemap files in a subfolder, and then use a path such as “subfolder/MyCustomMap.sitemap” here. I’ve never tried that, so it may not work, or work without jumping through a few more hoops.

Finally, in the master page, create an asp:SiteMapDataSource control that points to your new site map provider:

<asp:SiteMapDataSource runat=”server” id=”SiteMap1″ SiteMapProvider=”MySiteMap”/>

And then create an asp:Menu control that uses this as its data source:

<asp:Menu runat=”server” id=”MainMenu” DataSourceID=”SiteMap1″>
</asp:Menu>

Now, the fun begins.

We had been working on our site internally and had been showing it to the stakeholders regularly, but always while it was running on our network.  The development had reached a point where we could let the client use the site directly.  To do that we needed to extend our original web application and create the external version of the site.

The complete details behind extending a web app are beyond the scope of this post (or even my area of expertise).  My purpose here is to provide enough details so that you can avoid the two issues we encountered when doing this.  Both of the issues that I will describe are specific to pages that use XML-sitemap files.

Extending a web application creates a new virtual directory.  This makes sense since the process of “extending” a SharePoint web app really means to create a second (or subsequent) IIS virtual site to point to the same SharePoint content database.  One major reason for doing this is to set up a new path to the site, and with it a new authentication mode.  In our case, we wanted to use Windows authentication internally, but allow our client to log in via Forms.  Additionally, we needed to turn on Anonymous access for the external users since less than 1% of the users on the site – the content authors – will actually need logins.  All content is publicly available for reading.

So we extended the site.  The vast majority of our site will work just fine because all of the content and structure is stored in the content database.  Our sitemap files, however, are not stored here – they are only the file system and as a result don’t get copied from the source IIS site.  So, our first issue is that we have to copy our sitemap files from one folder to another, and we have to replicate our web.config changes to match.

This isn’t a huge deal, but certainly one that could trip us up later if we’re not careful.  For example, if we’re making changes to the internal copy of the sitemap file, and we’re pointing our browser to the external copy, and beating our heads against the monitor for an hour trying to figure out why the changes aren’t being reflected.

Next week I’ll detail a much more involved issue – one that even after it was solved generated more questions than was answered.

CAML = Needle is passing through my eye

My current project called for a CAML query that took, as one of its filters, a Lookup field’s ID.  In past instances, I had been doing this based on the Lookup field’s text value – what the user actually sees when they select it from the SharePoint page layout drop down – but in my current case this wouldn’t do because the text wasn’t unique.  My data had perfectly valid, but duplicate, items in it.  Hence, I needed to filter based on the underlying ID.

Items in a SharePoint list have both an “ID” and a “UniqueID”.  The latter is a GUID, which, as the acronym indicates is a globally unique identifier.  The former is an integer that works more like an Identity in SQL Server – the number starts at 1 for the first item added to the list, and grows from there.  IDs for items that are deleted don’t appear to be reused for new items, so within the context of a specific list, IDs are also unique.

My original CAML query looked like this:

<Query>
  <Where>
    <And>
      <And>
        <Eq>
          <FieldRef Name=”Product_x0020_Field”/>
          <Value Type=”Lookup”>My Product</Value>
        </Eq>
        <Eq>
          <FieldRef Name=”Question_x0020_Field”/>
          <Value Type=”Lookup”>What is your favorite color?</Value>
        </Eq>
      </And>
      <Eq>
        <FieldRef Name=”Previous_x0020_Question_x0020_Re”/>
        <Value Type=”Lookup”>Blue</Value>
      </Eq>
    </And>
  </Where>
</Query>

Now, that worked to retrieve data, except that the data it pulled back had duplicates in it.  I needed a way to weed the unwanted duplicates out.  If you look at the “formatted” version of “Previous Question Required Answer” (which is what the last EQ clause is querying on; see the second rant below for an explanation as to why the field name appears as it does above), you will see that it returns both the ID of the lookup field value, as well as the text of the field value, separated by a ;# character combination, for example “1034;#Blue”.  Trying to query on the ID portion of that value is in vain – there doesn’t appear to be any way to do it.  CAML queries to that effect simply result in an error.

So, to get around this issue, I ended up using the query above, and then manually weeding through the results looking for the ID that I really wanted.  A hack, to be sure, but since my particular query was pretty well guaranteed to not bring back more than a dozen or so results ever, the performance hit walking through the records one by one was acceptable.

OK sports fans, now it’s time for a rant double header.

<rant>

Notice the use of the two AND keywords in the CAML query above.  It appears that the AND operator only allows two operands.  At most.  Ever.  To achieve a three-way filter, I have to nest one AND inside the other.

</rant>

<rant>

Notice the field name that I need to use for my third EQ test: “Previous_x0020_Question_ x0020_Re”.  Why would I use such a silly naming convention for my field?  Well, the field started out life as “Previous Question Required Answer”.  I couldn’t query on that name, however.  I am required to use the Internal Name of the field in the CAML query.  The Internal Name is assigned by SharePoint, and is created based on the name that you give the field.  So “Previous Question Required Answer” gets each of its spaces replaced with “_x0020_” to give you “Previous_x0020_Question_x0020_Required_x0020_Answer”.  If the conversion ended there, I’d be fine with it.  “x0020” is not that far off from “%20”, a common replacement in URLs.  However, why this renaming qualifies as a rant in my book is that the Internal Name property of a SharePoint field apparently has a 32-character limit, which is why the field needs to appear as “Previous_x0020_Question_x0020_Re” in the CAML.

</rant>

A camel passing through the eye of a needle may be difficult, but CAML sometimes feels like a needle is passing through my eye.

SharePoint Designer vs. Visual Studio

Joel and I got into a debate last week about some of the limitations that we’re running into with SharePoint Designer (SD).  Both he and I have been working on projects where we’ve had to make some more advanced customizations to SharePoint such as custom workflows and extremely branded, multi-lingual pages.  Our conversation largely revolved around source control, but there are some additional issues that I’d like to highlight.

Joel started the debate (he’s an instigator, let me tell ya!) with the following:

“I still don’t know of a good way to save pages that are created/edited via Sharepoint Designer. If nobody else knows of another good way, one thing I’m kicking around would be making all of my .aspx pages a custom content type that has an event handler that will programmatically add it to SourceSafe, or write it to the file system and then add it…”

To which I replied that what we had been doing lately is maintaining a Visual Studio (VS) project with our SharePoint features, which consist of a couple of XML files, and other files such as custom ASPX page layouts, images, etc..  The major issue with this approach is that there is no direct connection between Visual Studio and SharePoint, so every time I make a change to a page layout, I need to remember to change it in both places.

So, why the double work?  Why not simply use VS to update my SharePoint site, and skip SD altogether?  VS knows how to build an ASPX file (it doesn’t try to rewrite my markup, and it’s almost painful to write code without IntelliSense).  You can build custom SharePoint workflows in VS.  So why do I need SD at all?

Well, it turns out that you can’t connect to a SharePoint site using VS.  If you try to open the site using File/Open/Web Site/Remote Site from within VS, it generates this error message: “Visual Web Developer does not support opening SharePoint Web sites.  See Help for more details.”  Mind you, I have the full version of Visual Studio, not Visual Web Developer.  However they share a good amount of the same codebase, so seeing this message from within VS isn’t that surprising.

I think this is a fundamental mistake to force all developers to use SD.  I am by no means arguing for the demise of SD – I think the FrontPage-descendent looks and works better than its ancestors, and its capabilities are well-suited for the power-Office user.  What I am arguing is that for those of us that are diving even deeper into the product than an Office user would, SD’s limitations quickly start getting in the way.

As our first example, let’s return to the source control question that Joel raised.  In SharePoint, most things can have a version attached to them (in fact, versioning is generally turned on by default).  SD knows how to interact with the versioning system, and allows you to check things out before you edit them, and check them back in when you’re done.  But what happens when you open a file without checking it out, edit it, then try to save it?  SD throws up an error to the effect that the file is under source control, and can’t be saved.  You then have to go through a series of clicks to close this message, check the file out without replacing the local copy (containing your newest updates), and saving again.

What happens if you do this in VS?  It assumes that if you make a change to a file under source control, that you really wanted to change it, and it just checks the file out for you behind the scenes.  No error messages, no extra clicks – it just works.

I can’t tell you how many times this has tripped me up, and it usually isn’t when I first open a file to modify it, it’s when I happen to leave the file open (but checked-in) in SD, and then try to make a modification without explicitly checking it out.

For our second example, let’s look at SD’s limitations for what it can access on the server.  To date, SD has been able to access anything that I’ve needed it to at the site collection level and lower.  This includes both code (such as a page layout) and content (such as images or documents).  This is the content that exists in the SharePoint content database for your site.

What it can’t get to are things that I am calling at the “web application” level, or basically, anything on the file system itself.  When you create a new SharePoint web application it provisions (among other things) a new IIS web site on the server.  The root folder for this can be located in the “{ApplicationDrive}\Inetpub\wwwroot\wss\VirtualDirectories\” folder.  Why do you need to know about this folder?

  • If you need to modify the web.config (for example, install a new site map provider, or turn on verbose error messages), you have to log into the server and make the changes right on the file system in this folder.
  • If you need to update a custom sitemap for your site, you need to make those changes on the file system in this folder.
  • If you need to update the language resources (RESX) files for your site, you need to make those changes on the file system in the App_GlobalResources subfolder of this folder.

None of these changes can be made using SD.  None of these changes are tied into the versioning mechanism of SharePoint.  If you make changes to these files, the only place they will inherently exist is on the server.  If the server goes down, you’ve lost your work.  So, minimally, I need to maintain a separate source control system for these files.  Furthermore, I have to manually upload my changes to the server (or manually pull them down if I make the modifications on the server first).

Our third example is a little more philosophical.  Even if SharePoint could access and version these “web root” files, there is little or no separation between my source control system, and the runtime system.  All of the versions of all of my files are stored in the SharePoint content database.  What happens when that database becomes corrupted?  You potentially lose everything in one fell swoop.  Since we’ve had a few of our SharePoint databases become corrupt over the last couple of months (did I mention we beat them up pretty badly?), this has convinced us that we need an additional basket to put our digital eggs in.

So what’s the solution?  Open up SharePoint to allow other development environments (namely Visual Studio) to access its content – all of the content, from the web root on the file system on down.  Next, allow VS to access the versioning and content approval mechanisms of SharePoint, so I can update files and move them along their various states completely from within VS.

Finally, allow VS to manage a source control system that is separate from the SharePoint server.  This is really where things get a little tricky – how would the non-SharePoint source control system be kept in sync with the SharePoint versioning mechanism?  I have to admit I don’t have the answer here.  What I do know is that blurring (or eliminating) the line between “source control system” and “production system” is not a good idea.

I admit that I haven’t exhausted tested Visual Studio against SharePoint to try to determine why it won’t let me connect, or other ways to solve these problems using SharePoint Designer.  I also admit that I am ultimately lazy, and so if I have to spend a lot of time jury-rigging something together, I usually conclude that the application is designed poorly.  So, for those of you out there that are working with SharePoint and SharePoint Designer, please weigh in and enlighten me.  I want to have my cake, eat my cake, and back my cake up, too.

Not so Happy Feeture*

My current project involves building an internet-facing site on Microsoft Office SharePoint Server (MOSS) 2007.  For the last few weeks we’ve been working to get some of the “framework” pieces in place, such as custom master pages, picture galleries for the content, etc..  Last week we started wrapping our components and settings into “features”.

A feature allows you to encapsulate things like custom list columns, master pages, page layouts, etc. into an installation package (of sorts), and include instructions on how MOSS should install them.  Then, you install them, and activate them, either at the site or the site collection level.

A feature has the following benefits:

  1. Encapsulated (sort of) – the Feature.xml and associated files define everything that goes into that feature. For example, we built one that would install the master pages for our site, and all of the associated images. I say “sort of” because it’s not like building a .NET assembly that can be dropped in the GAC – the feature is merely a collection of files, strung together by a couple of XML documents, all in the same directory.
  2. Repeatable installs – for the development environment, we’re frequently making changes directly in SharePoint (or SharePoint designer). Eventually, we will have to reproduce this in the production environment, and it will be very time-intensive to do this by configuring SharePoint – both in terms of the time it takes us to write up a process for doing it, and the time to do it. Additionally, the more we can roll up into a feature, the fewer chances there will be for us to miss a step in our process, and have to rework something.
  3. Self-documenting – To a certain extent, the features can document the major steps of deploying the application. Our process might be “run these 15 features in this order to deploy the site”, and if the features are named like “SiteMasterPages”, “ProductPage”, “AssemblyInstructionLibrary”, etc., you can get a sense of what each of them is doing. Granted, there may be details that need some additional explanation, but at least you can get some value at the 5,000 foot level.

Looks like we just had our glitch for this mission

One of our tasks was to create a custom list, and then populate it.  Our first few attempts at doing this produced a feature “template”, what appears on the new list Create page.  Defining a template would allow us to define the structure of the new list, but wouldn’t actually create the list.  With a little more tweaking, we got it to work.

Houston, we have a problem

Unfortunately, somewhere along the line, we ended up hosing the entire server.  When we tried to visit the “Site Settings” page, that shows the entire site collection in a Windows Explorer-like screen, we got this:

Feature ’00bfea71-e717-4e80-aa17-d0c71b360101′ for list template ‘101’ is not installed in this farm.  The operation could not be completed.

The GUID referenced was not the one we were using with our feature, but we tried deactivating and uninstalling our feature – same error.

We tried deleting the web application and site collection that we had activated the feature under.  If you delete the code that is throwing the error, the error goes away, right?  Well it didn’t go away, which meant that our new feature wasn’t causing this error, at least not directly.

We tried access the Site Settings page for the other two major site collections being hosted on this server (representing two different development projects going on here at BlueGranite).  They were ALL throwing this error now.

Ok, so now that I had the attention of two entire development teams, we could put our collective grey matter to the task.  There must be an easier way to ask for help…

You are Go for the manual burn

After trying several things, digging through page after page of Google search results, and poking into the MOSS server with a custom tool that a colleague of mine (Joel) wrote, we discovered that the GUID being referenced was for the Document Library template.  It started looking like the out-of-the-box document library feature had somehow been corrupted by one (or more) of our early attempts to get our own feature working.  Based on this, Joel made a remarkable leap in logic.  The document library feature is in the same directory as all of the others.  Let’s try reinstalling THAT one, just like we would if it were a custom feature.

We ran two stsadm’s (one to install it, and one to activate it) and an iisreset.  Then we tried the Site Settings page again, and it worked!  We tried the Site Setting pages for all of the other affected sites, and they were working again as well.

The ship is secure

So, what the heck did we do?  As near as we can tell, we left out one or more required fields in the Elements.xml file.  This file defines the particulars about the list, the master page, the custom column, etc. that you are trying to install.  This link describes the ListInstance element in more depth, including which fields are required: http://msdn2.microsoft.com/en-us/library/ms476062.aspx.

Leaving out one of these required fields allowed the feature to be installed, but since it had to have something in that field it filled it in with whatever it could – in this case a reference to the Document Library feature.  When we didn’t see ours working, we would uninstall it, and try again.  Since we didn’t think to check the Site Settings page in between, we don’t know which of the steps broke it.  However, Joel made a blog entry on March 5 that also talks about this particular issue, and his is probably the best explanation of which field was the culprit – FeatureId.

Because there is only one Features folder per MOSS server (typically under C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\Template\Features), it affects every site collection hosted on it.  When we hosed the document library feature for our site collection, it took it out for all of them.

So, there are a couple of lessons to take away:

  1. When you’re building a feature, make sure all of the required fields are specified.
  2. Be mindful of the fact that installing features affects the entire server, and not just your site collection. The “12-hive” contains a lot of sensitive documents and settings, so you need to tread lightly. Unfortunately, while we thought we knew that going into this process, we have a much better appreciation now.
  3. It looks as though MOSS uses what’s defined by the Feature to set up structure in its databases. Once that structure is set up, the files on the file server are no longer used. That would explain why installing our new feature hosed the server, but re-installing the built-in Document Library feature fixed it. It would be interesting to see if this is a recommended solution to errors like this.

*A nod to one of Joel’s previous posts (http://www.blue-granite.com/blogs/joel, January 8, 2007) about features.

How resourceful of you (Part 2 of 2)

My previous post explored the details involved in using ASP.NET 2.0 resource files to localize your application.  In this post, I’ll talk about a few of the snags that I hit when implementing this technique.

Note: To prevent the blog from replacing my special HTML strings with the single character representations (for example, © or ®), I’ve added extra spaces in between each character (for example & c o p y ;).  In order for you to get these to work, please remove the extra spaces.

One of the first phrases that I needed to render included a copyright symbol.  Having some knowledge of HTML, my first stab at it was the following:

<data name=”SiteCopyright”>

<value>& c o p y ; 2007 My Company</value>

</data>

That caused the server to throw an exception.  Removing the & c o p y ; allowed the page to render normally.  I found that & r e g ; (for the registered trademark symbol) also caused the page to error out.

After some trial and error, I found that the “numeric” representation of the copyright and registered trademark symbols worked:

<data name=”SiteCopyright”>

<value>& # 1 6 9 ; Landscape Forms copyright, 2007</value>

</data>

“169” is the ASCII decimal value for the copyright symbol (174 is the value for the registered trademark symbol).

That was the first snag.  Luckily, I had been editing the RESX files directly on the server using Notepad, just to make the troubleshooting go faster.  My development team hit a second snag when a colleague of mine happened to open the file up in the Visual Studio editor, made an innocuous change to a completely unrelated phrase, and uploaded the new version.  The site started throwing errors again.  When we examined the file that made it up to the server – again, using Notepad – we found that the Visual Studio resource editor had replaced & c o p y ; with the ACTUAL copyright symbol of ©.  That of course didn’t sit well with the XML resource files, or with SharePoint.  Once we re-replaced the character, SharePoint accepted the file.

For this third and final snag, I need to climb up on my virtual soap box, so here goes:

<soapbox>In my first attempts to get resource files functioning within SharePoint, I got a LOT of messages to the effect that “resource X not found”.  For these early attempts, I was using a sample that I had copied down off a technical article that formatted the resource file contents as follows (I have since lost the link to that article, so I apologize for the hearsay):

<Data Name=”SiteCopyright”>

<Value>& c o p y ; 2007 My Company</Value>

</Data>

Compare it with the version at the very beginning of this post.  If it only takes you a second or two to see the critical difference, then you’re better than I was.  It took me an hour to figure out what was going on (keep reading).

After probably 45 minutes of my head making contact with the monitor muttering “Why doesn’t this work?!?”, I finally broken down, deleted my resource file, and just tried referencing one of the resource files that were installed with SharePoint.  At this point in my troubleshooting, I still wasn’t positive that I was even putting my custom RESX files in the correct directory.  Once I pointed my custom page layouts to the built in RESX files, and saw them working, I knew I had THAT part at least correct.  The issue then was with my custom RESX file itself.

I decided to take a different approach to building the new custom RESX files – copying the built-in file that I had just proven worked.  I deleted all but one of the <data> tags that were there originally, and pasted in my custom ones.  Once I had them side by side, the difference became glaringly obvious.

Are you ready?

<data name=”SiteCopyright”>

<value>& c o p y ; 2007 My Company</value>

</data>

<Data Name=”SiteCopyright”>

<Value>& c o p y ; 2007 My Company</Value>

</Data>

Notice the capitalization of the first letter in the keywords.  No, it can’t be THAT.  Can it?  The answer is “Yes, yes it can”.  Years of writing HTML by hand (and/or working with a very tolerant browser such as Internet Explorer) has led me into a bad habit of assuming that the markup was not case sensitive.  As I found out here XML is, in fact, case sensitive.  As a result, once I dropped the uppercase letters to lower, SharePoint recognized the tags just fine.

I want to be clear about what I found frustrating with this.  My issue was not that I WANTED to capitalize the first letter of all of my keywords.  In truth, I usually write everything using lower case.

My problem wasn’t even with the author from whom I grabbed the original sample (and where the upper case letters came from in the first place).

My problem was with how the XML itself was being interpreted, and that going from <Data> to <data> suddenly conjured my resources into existence, because as far as the SharePoint and ASPNET engines were concerned, they simply didn’t exist before I cast that level 6 spell of ToLower.</soapbox>

Ok, now that I’m off of my soapbox, I can continue to address this issue with a better semblance of calm.  I did some digging and found that the keywords are case sensitive for a couple of reasons – processing performance, and language localization.  Tim Bray (who was heavily involved in the development of the XML standard) answers this best.  These two links will show you the same response by Bray:

I unfortunately was not able to locate the original context that this post/response was made, so the best that I can do is say that these comments are attributed to Bray, and given his background it seems highly likely that he actually made them.

The reasons he cites for the case-sensitivity are good ones, and now that I realize that’s what’s going on, I can be more vigilant when I’m writing XML (which isn’t really all that often).  More importantly, I can add this as another check in my troubleshooting book, and hopefully save myself 59 minutes of head-banging the next time SharePoint says it can’t find a particular resource tag.

How resourceful of you (Part 1 of 2)

One of the coolest features in ASP.NET 2.0 is the mechanism for accessing resources when localizing an application.  Once you have the resources (RESX) file built, access to the resources can be completely done in the markup:

<asp:Literal runat=”server” Text=”<%$ Resources:MyApp, PageTitle %>” />

In this example, the Literal is referencing a resource file called something like MyApp.resx in the App_GlobalResources folder of your application, and a resource with the name of “PageTitle”.  This post is more about how these resources are applicable in building a SharePoint 2007 master page or page layout, so I’ll be assuming that you already how to apply this technology in a straight-ASP.NET 2.0 application.  Here are a couple of links that explain ASP.NET 2.0 resources in more depth:

For the remainder of this post, “web app” should be taken to mean the SharePoint web application, as seen by IIS.  Normally, all of the directory structure that you set up in SharePoint is virtual, but when you create a web application (the top-most level for the SharePoint structure), you get an IIS virtual directory, as well as a “real” one on the file system.  The root directory for the web app is:

{DriveLetter}:\Inetpub\wwwroot\wss\VirtualDirectories\{WebAppName}{PortNumber}

So, if I created a web application called “staging”, and set it to port 80, I would expect the root directory to be C:\Inetpub\wwwroot\wss\VirtualDirectories\staging80.

To start, we need to make a small modification to the web app’s web.config, found in the root directory.  We need to change the <globalization> tag to specify a couple of properties:

<globalization fileEncoding=”utf-8″ culture=”auto” uiCulture=”auto” />

By default, the “culture” and “uiCulture” properties are not included.  Setting them to “auto” allows the server to decide which cultural settings to serve up.  As you will see below we will end up overriding this at the page level, so this setting may be unnecessary in some cases.

Next, we need to create the resource files themselves.  Unlink in previous versions of .NET, the resource files in .NET 2.0 are not (or at least, do not HAVE to be) compiled into the assembly.  They remain (more or less) human readable text files on the server.  For a SharePoint web app, these files are located in the App_GlobalResources folder below the root directory.  They follow the same structure that they would for a straight-ASP.NET application (please refer to the links above for more information on these formats, or take a look at the RESX files already in the App_GlobalResources folder as examples).

I’ll assume that I have created MyApp.resx (the default), MyApp.en-US.resx (for US English), and MyApp.fr-CA.resx (for Canadian French).  Our next step is to get the SharePoint pages themselves to recognize and use them.

You can reference the resources in both master pages and page layouts.  The specific phrase used is determined by either the <globalization> tag in the web.config file, or by the page layout’s @Page directive.  The latter overrides the former.

The @Page directive has two properties that can be set to specify the language and locale to be used on that page, and they are the same properties that are specified in the web.config <globalization> tag: “culture” and “uiCulture”.  Any resources referenced on that page will be pulled from the RESX file that corresponds to these settings.  Any resources referenced in the master page will use the current page layout’s settings as well (the @Master directive does not directly set these properties).

Why would you want to hard-code these values into the page layout?  That’s where SharePoint’s variation mechanism comes into play.  When you set up variations, SharePoint creates a parallel site to your variation source.  SharePoint manages the variations through differing URLs.  For example, www.mysite.com/en, www.mysite.com/fr, and www.mysite.com/de.  Any time a page is published in the source variation site, a copy is made to the destination variation sites.  The idea is that a bi-lingual content author will then translate the source content to the destination language, and publish the page again.

SharePoint allows you to create your own master pages and page layouts, and each site (potentially) can use a separate set.  That means that if you have an English-US site, and a French Canadian site, you can create English and French master pages and page layouts, and customize each one to their respective language and locale.  If you hardcode the “culture” and “uiCulture” properties of each variation site’s pages, you can present a new localized version of your site by simply changing a portion of the URL.

This post explored the details involved in using ASP.NET 2.0 resource files to localize your application.  In my next post, I’ll talk about a few of the additional snags with this technique.

What’s on the ASP:Menu?

My current project involves building a public-facing site using Microsoft Office SharePoint Server (MOSS) 2007.  One of the first things that we needed to get set up was the navigational structure.  Much of the navbar could be constructed based on the SharePoint site structure, but one particular flyout menu needed to be built based on a custom list of links to various tools and pages in the site.  In all, this menu contains over 300 items among its three tiers.

Since this one menu item needed to be so highly customized, we decided to implement it using a site map data provider.  This allowed us to create a web.sitemap file in the web root (really just an XML file), add a custom provider to the web.config, and set our ASP:Menu control’s datasource to that provider.  It seemed like an elegant and quick way to build our custom menu.

In practice though, we hit a snag.  When the menu rendered, though, we found a 1/2 to 3/4 second delay in moving from one menu item to the next.  It was as if the page was trying to contact the server with each mouseover, even after the entire menu had been rendered and touched.

After doing some trial and error, we found that limiting the levels of the menu to 2 via the MaximumDynamicDisplayLevels property allowed the menu to respond at the speeds we expected.

Doing some more testing (with the full, 3-level, menu) showed that this was only happening when viewing the page through Internet Explorer (version 6 or 7), but NOT through Firefox 2.  The page in Firefox didn’t have any of the delays that we were seeing in IE.

This spurred us to examine what was being rendered for Internet Explorer and for Firefox.  We hypothesized that the server was trying to customize the experience for each user-agent, and perhaps wasn’t sending the most optimized code down for IE users.  Except for a few (seemingly inconsequential) differences, the page source was the between IE and Firefox.

After we had done this research, we found a forum posting (link: http://www.thescripts.com/forum/thread460679.html) on “The Scripts Developer Network” from January 2006 with this exact same scenario and results.  It was comforting to see that we weren’t the only ones hit with this, but it was frustrating that no one seemed to have a solution (even after a solid year in the field).

Seeing no good reason why this should render differently on IE and Firefox, we opened a support ticket with Microsoft.  We had created a small test application that demonstrated the issue, and emailed that to Microsoft so they could reproduce it on their end.  The tech was able to reproduce it, and did some research into what he referred to as “internal” documentation on possible causes.

In the end, the tech acknowledged that this is, in fact, a known bug with the ASP:Menu control and “will be rectified in the future release.” (source, the Microsoft email that I received letting me know that my ticket was being closed).  I imagine that the “future release” in this case is a patch or service pack for the .NET Framework, but I haven’t found a reliable post yet on when that might happen.  I welcome comments and responses to that effect.