We recently wrapped up a project where we migrated a .com site built using Microsoft Content Management Server (MCMS) 2002 to take advantage of the web content management features available in SharePoint 2007.
Microsoft is no longer offering MCMS as a standalone product; all enterprise and web content management is now part of the MOSS platform. I'm not a Microsoft licensing expert (is anyone?), but customers with active Software Assurance for MCMS as of November 2006 are eligible for heavily discounted (or free) licenses for SharePoint 2007. At $50K a pop per server for a SharePoint Internet license, there's money to be saved here! Check out http://www.microsoft.com/cmserver/roadmap.mspx for some more information.
Anyway ... we really learned a lot of good stuff on this project, but also suffered through some other things the hard way. Together with the help of the rest of the team, I put together a list of the top ten things we learned on this SharePoint WCM project:
10 - CMS Assessment Tool - inventory your migration
Microsoft provides a tool for MCMS 2002 sites to "determine the level of work needed to migrate to SharePoint 2007". In our experience, this tool was really only good for one thing: getting an inventory of the assets in the MCMS 2002 site, e.g. the number of page templates, etc.
I wouldn't recommend using any output of this tool as input into the development process, the source code for your MCMS templates (if you have access to it) is more valuable than anything this tool puts out.
One thing I highly recommend is to store your old site's content in Xml files using the technique I described in my blog post Bulk Importing Content into a SharePoint Publishing Site to import it into the new site.
9 - Continuous Integration - how far to take it
The benefits of true continuous integration on a SharePoint project are debatable. By no means am I saying that continuous integration isn't valuable, it's just that the effort involved in actually making it work in SharePoint might just not be worth it.
We found a great middle ground: we have TFS configured to automatically build our source on every check in. After every successful build, all of our compiled WSPs are neatly arranged in our build drop location.
If you broke the build, you got yelled at over the next cube row ;)
8 - SSL - don't wait until the site is live
SSL is one of those things that developers tend to leave to the infrastructure and network dudes.
On this project, we implemented an HTTP module that would switch the site in and out of SSL depending on the area of the site that the user was visiting. We tried to fake it out in dev, but only saw the real issues once we had gone live with the site.
As cumbersome as this seems, get a real SSL certificate and make it available in an environment where developers can test any of the site's functionality related to SSL.
VeriSign even offers free trial SSL certificates (their sales reps won't stop calling you though) that developers can install in their individual environments.
7 - FireBug and Fiddler - the two amigos
Two development tools that we couldn't have gone without are FireBug and Fiddler.
The most visible part of the site migration process was to replicate the site's look and feel within SharePoint; this wasn't as straightforward as we initially thought. Forget the IE Developer Toolbar, FireBug is where it's at. It was invaluable when we were debugging CSS issues, or looking for the proper SharePoint style to override.
Oh yeah, and Heather Solomon rocks. Anyone that suffered through documenting every single SharePoint CSS style deserves some props.
Ah Fiddler, how I love thee. When we were knee deep in SSL issues, it was Fiddler that showed us the HTTP 301s that were causing FireFox and IE8 to complain about mixed content on the page.
6 - IETester - test your site on crap browsers (ahem, IE6)
Unfortunately, there are still quite a few IE6 installs out there, I wish it would just die already.
However, that meant that we couldn't ignore IE6 in our testing. IETester allowed us to easily test our site in different versions of IE without having to actually install any of those versions anywhere.
I don't run IE8 anywhere either, I'm really gun shy when it comes to installing beta versions of IE. IETester helped us spot any issues that came up when using different versions of IE, such as warnings about mixed content on a page when we were troubleshooting SSL issues. It's also worth nothing that IETester can also be configured to proxy through Fiddler for HTTP debugging.
5 - SharePoint Content Deployment - works great but tough to troubleshoot when things go wrong
Microsoft's recommended topology for an internet publishing web site is one where authors create content in a dedicated Authoring environment, and scheduled Content Deployment Jobs copy approved and published content to a Staging (optional) and Production (live) environment.
For the most part, this worked out pretty well for us. Here are a few things to watch out for:
Your content deployment destination site collection is NOT a Publishing site
When first setting up content deployment, you always deploy to a site collection that was created using the Blank Site site definition. No big deal, except that you need to understand that this site isn't truly a Publishing site. This makes sense in some ways - you don't want content authors to edit content on the live site, but it's confusing in some other ways.
For example, there are several options you would usually see under the site settings of a Publishing site, e.g. Caching, and Site Usage Reports. You won't see these options in your content deployment destination site, although this is where you would actually need to configure caching!!
The workaround here is that you have to know the urls to those options, they will work if you type them manually into the browser.
Troubleshooting failed content deployment jobs
Content deployment jobs fail for the damndest reasons, and when they do fail they're nearly impossible to troubleshoot. Maxime Bombardier has a great blog post called Debugging Content Deployment Issues, worth a look.
One technique for debugging failed jobs is to examine the exported package, and look in the manifest.xml for the item that failed. This will be the item which has a +1 count from the Number of Objects Imported. The problem with this technique is that it might sometimes point to an item that doesn't necessarily make sense, e.g. /Pages/Forms/AllItems.aspx - what on earth are you supposed to do here?
So if all else fails, what do you do?
One thing that worked consistently for us is the following:
- Switch the failed job to deploy ALL content (if it was initially set to incrementally deploy content)
- Run the full content deployment job
- This might take a while, make sure there's enough space for the SQL transaction logs to grow
- Verify that the full content deployment job succeeded
- Reset the job back to incremental
- Run all the other jobs you have configured along this content deployment path at least once (more on why next)
A side effect of running a FULL content deployment job is that it might reset any custom permissions you have implemented on sub sites in your site collection - even though you configure the job to not deploy any security settings.
Additionally, any incremental content deployment jobs that run after the full job might do the same.
We never figured out why exactly this happens, which is why we run all the incremental jobs again, and then correct any of the security settings that were messed up.
Yay for lame workarounds!
4 - Andrew Connell's WCM Book - worth its weight in gold
Professional SharePoint 2007 Web Content Management Development, really the best SharePoint book out there.
Don't let the title fool you, this is a must for every SharePoint developer - WCM or not.
I love the approach that Connell uses when demonstrating how to build a piece of functionality. He shows you how do it in the SharePoint UI, how to do it in SharePoint Designer, and how to do it using SharePoint Features and Solutions.
Just packed with awesomeness, and small enough that you can always carry it around.
While you're at it, check out @andrewconnell on twitter. Really easy to get in touch with, and always more than happy to answer questions.
3 - CSSRegistration - order matters
Typically, you will link to one or more CSS files from within your master page. You might have special taken care to order your CSSRegistration controls - after all, the order of CSS declarations matters.
Well, don't bother. SharePoint will render them alphabetically... !@#!#@$
2 - The F5 Experience - not
Simply put, there is no F5 experience in SharePoint. If you use tools such as the Visual Studio Extensions for WSS, you're going to make tradeoffs in some other places.
So your head doesn't explode a few weeks into the project, take the time up front to optimize your team's edit-compile-deploy process.
For example, use MSBuild targets to automatically build your WSP and generate your install/deploy/retract/remove/upgrade scripts. Instead of manually doing everything via Central Administration and Site Collection Features, compile and run one or more of your scripts, or have a script that does a retract/remove/install/deploy in one step!
Work smarter, not harder!
1 - Feature Receivers and HTTP Modules - the control you need
Feature Receivers
If you find yourself manually pasting content in your web.config file as part of your deployment process, step back and look at automating the process.
A few of our features depended on the existence of certain sections and items in a SharePoint site's web.config file. We wanted to have these sections automatically created and deleted as part of our feature activation and deactivation process.
For example, if a feature depended on the existence of a custom web.config section, we would store the text for the section in a resx file, and then create it using the SPWebConfigModification class as part of our feature activation process. Taking this a step further, you could use a different version of the resx file depending on which source control repository you were building out of.
There are a lot of uses for feature receivers, we just found this one particularly useful.
HTTP Modules
Before this project, I hadn't written a single HTTP module. I can't say that anymore, here are some examples of functionality we implemented using HTTP modules:
- Override Layouts pages master page
- Custom 500 error page
- Custom authentication for different sub sites in a site collection
- Switch site in and out of SSL depending on the site or page being requested
You might be intimated by the idea of writing an HTTP module, but they're actually no big deal since they're pretty easy to implement. Just remember though, that the order that you list them in your web.config actually matters.
Also keep in mind that each module will execute for every request, there will be performance implications if you go nuts and implement a lot of these.
Hopefully you find this list somewhat useful, and it saves you some head banging on your next SharePoint project. Would love to hear your suggestions in the comments, or catch me on twitter @gdurzi.
I recently wrote about a Combined SSL HttpModule for SharePoint Webs and Pages that we used on a project to switch a SharePoint site in and out of SSL for certain _layouts pages as well as specific webs within the site collection.
Turns out there was a flaw in the HttpModule that appeared when I was using newer builds of FireFox and IE8. With the HttpModule enabled, I would get browser warnings that my entire connection wasn't encrypted.
FireFox handled this somewhat gracefully, putting a red exclamation mark over the padlock icon. IE8, on the other hand prompted the user if they would like to load the page without the items in question, causing the page to barf <-- technical term.
After spending a lot of time analyzing the traffic with Fiddler (a must in every developer's toolkit), as well as a very helpful suggestion on StackOverflow, I figured out the cause of the issue.
When analyzing the traffic in Fiddler, you could see that the request started as SSL, but was followed by a 301 response, redirecting it to a non-HTTPS link to the resource I was retrieving.
This was happening for items I had in my master page (using relative links!), e.g. my CSS, JavaScript, and general images for look and feel. Interestingly, IE6, IE7, and Chrome ignored the error and showed my site as incorrectly running full SSL. Odd given that I now understood that some resources were in fact not being transmitted over SSL.
Obviously the HttpModule will fire even for requests for "non-HTML" content. Knowing this, I modified the HttpModule to ignore that type of content.
Check out the original post for most of the background code, here's the updated PreRequest handler:
HttpContext ctx = HttpContext.Current;
SPContext spContext = SPContext.Current;
if (spContext != null)
{
if (spContext.Web != null)
{
// Assume that request does not require SSL
bool requestRequiresSSL = false;
// Check if the current request is for page content
// not gif, js, css, etc.
bool isHTMLContent = !_ignoreExt.Contains(
Path.GetExtension(ctx.Request.PhysicalPath).ToLower());
// Get the request Path and QueryString value
string pathAndQuery = ctx.Request.Url.PathAndQuery;
if (isHTMLContent)
{
// Layouts pages need to be handled separately
//
if (pathAndQuery.ToLower().Contains("_layouts"))
{
// Check if the current layouts page requires SSL
foreach (var page in _sslPages)
{
if (pathAndQuery.ToLower().Contains(page.ToLower()))
{
requestRequiresSSL = true;
break;
}
}
}
else
{
// Check if the current site requires SSL
if (spContext.Web.Properties.ContainsKey
(SITE_REQUIRES_SSL_PROPERTY))
requestRequiresSSL = (spContext.Web.Properties
[SITE_REQUIRES_SSL_PROPERTY].ToString()).ToLower()
== Boolean.TrueString.ToLower();
else
return;
}
}
if (isHTMLContent)
{
// SSL required and current connection is not SSL
if (requestRequiresSSL && !ctx.Request.IsSecureConnection)
ctx.Response.Redirect(_baseURLWithSSL + pathAndQuery);
// SSL not required but current connection is SSL
if (!requestRequiresSSL && ctx.Request.IsSecureConnection)
ctx.Response.Redirect(_baseURLNoSSL + pathAndQuery);
}