Wednesday, 22 October 2008

My top 5 WCM tips presentation

Had a great time presenting my WCM tips presentation over the last week or so - first to a record attendance at the UK SharePoint user group (we hit 200+ attendees for the first time!) and then to a Gold partner audience at Microsoft today. I'd like to think the user group record was due in part to the great agenda, but suspect it was really down to the draw of free beer, pizza and curry from LBi ;-) Instead of posting a simple link to the slides, I want to run through the info here because:

  1. I can attempt to convey some of the info which was in my demos here.
  2. I know how many of you will read a blog post but not follow a link to a PowerPoint deck!

First off, this presentation isn't about discussing the standard (but critical) WCM topics which you can easily find quality information on these days. Personally I think if you're embarking on a WCM project your starting point for information should be Andrew Connell's book (and his blog articles if you don't already subscribe), and reading in detail some of the key MSDN articles which have been published on the subject (I list my favorite resources at the end). In these places, you'll find discussion on some of the sub-topics (non-exhaustive list) which I see as the bedrock of WCM:

  • Security
  • Accessibility
  • Optimization
  • Deployment

So instead of covering these in detail, my tips focus on key techniques I've found to be powerful in delivering successful WCM projects. They won't be suitable for all WCM projects, but hopefully they give some food for thought and provide some extra value to the WCM info already out there.

Tip #1 - Implement HTML markup in user controls, not page layouts in SPD

Explanation:

Instead of adding your page layout HTML to the page layout in SPD, create a 'parent' user control for each layout which then contains the HTML and child user controls for this layout. This means you have a 1-to-1 mapping between page layouts in SPD to 'parent' user controls within your VS web project. These parent user controls contain the actual markup and also any child user controls used on your page.

Slide bullets:

  • Faster development experience - less SPD pain [instantaneous save rather than 3-5 second save plus occasional unwanted changes to markup]
  • Page layout markup now in primary source control [alongside your other code]
  • Much simpler deployment of updates [simply XCOPY user controls when you deploy updates to other environments]

What it looks like:

So we see that our page layouts now have very little markup (note the ASP ContentPlaceholder controls must stay at the top level, so our parent user control gets referenced in PlaceholderMain):

PageLayoutMarkup

..and then all of our real markup code is in the parent user control and child user controls which it references:

UserControlMarkup

Tip #2 - create custom IIS virtual directory pointing to your web project files

Explanation:

Most project teams I see put their user controls under 12/CONTROLTEMPLATES (often in a sub-folder) so as to follow what SharePoint does. This is mandatory in some techniques (e.g. custom field controls, delegate controls), but is not required for the type of WCM page user controls discussed in tip #1, and there are arguments for not storing those in the 12 hive. In summary, having a custom IIS virtual directory pointing to your VS project means we avoid having separate design-time and run-time locations for the project files.

Slide bullets:

  • Store user controls/page furniture files (e.g. image/XSL) here. Remove code files (e.g. .cs files) for non-dev environments
  • Faster development experience – no files to copy, no post-build events. Just save and F5!
  • Important if using tip #1 – don’t want to have to compile project [for post-build event to copy files] just for a HTML change

What it looks like:

In our case we created a virtual directory with an alias of 'MIW' (the name of our website) which points to our project files:

CustomIisVirtualDir

All our user control/page furniture file paths then look like '/MIW/PageLayoutsControls/foo.ascx'  etc.

Tip #3 - make life easier for the site authors/admins [reduce their stress and they'll be on your side]

Explanation:

This one is a non-technical tip I wanted to throw in there - whilst we're busy getting the front-end of the website right, I think it pays to also think about how authors/admins will use the back-end of the website (N.B. here I mean 'business admin' rather than IT pro). Although this is probably verging on the 'political' side of things, I'd advocate making their life as easy as possible - they often have a loud voice within the client organisation, and if they have bad feedback on what you're building that's a black mark against you.

Slide bullets:

  • Consider providing custom tools if the ‘SharePoint way’ is not simple enough (e.g. user management)
  • If you use custom lists for site data, provide a link for authors to find them (e.g. using CustomAction)
  • Remember these people are rarely SharePoint gurus!

What it looks like:

Clearly this will look different for every project, but for our client we created a custom Site Actions sub-menu with some key links:

CustomSiteActionsOptions

This sub-menu provides navigation to a couple of key lists used to power the site, but also to some custom screens we created for user management. Here, we also did some work to simplify things by wrapping up the relatively complex process of creating a user in the membership provider, adding them to various security groups across 2 sites (we had 2 'sister' MOSS sites with single sign-on) and setting certain profile fields, into some simple screens which give a convenient summary of what just happened after creating a new user:

CustomCreateUser

Finally, the 'edit profile' screens used by business administrators were adapted from those used by end users, so that the admins became very familiar with each step of the 'profile wizard' and were better able to support their users.

Tip #4 - plan for unexpected errors

This is an interesting area, partly because we're talking about that category of things which 'should never happen', but sometimes do. Having this conversation with a client (or non-technical management within your own organization) can be fun because the typical response is "whaddya mean the website going to go wrong?", but anyone familiar with software development principles knows there is no such thing as bug-free software. So the important thing is how do we handle these cases when they do occur.

There are several tips here, so I'll break them down into 4.1, 4.2 and 4.3:

 Tip #4.1 - implement 'friendly' pages for 404s and unhandled errors

Explanation:

In brief, users of a public-facing website should never see a .Net error if something goes wrong on the website! If this ever does happen, the user is left with the feeling that your website is unreliable and can lose confidence in the organisation - think about it, do you ever remember seeing this kind of error on amazon.com/eBay.com/microsoft.com?

Slide bullets:

  • Typically use custom HTTP module to override SharePoint's default error-handling behaviour, checking for:
    • HttpContext.Current.Server.GetLastError()
    • HttpContext.Current.Response.StatusCode = 404

What it looks like:

On the Standard Chartered site, our 'friendly' message looks like:

CustomErrorScreen

Tip #4.2 - implement e-mail notifications to developers for errors

Explanation:

Sorting the user experience is one thing, but what about actually fixing the source of the problem? A key element of this is being alerted whenever a user does experience a problem, rather than relying on them reporting it. When I first told the team we'd be implementing this, I was really thinking about the UAT phase, and also that critical week or two after go-live when you'll occasionally discover some latent issue which had managed to hide itself throughout all the testing. However, what we found is that we got even more value from this in the earlier dev/testing phases. At LBi, the test team sit near the development team, so when the Outlook 'toast' popped up with an exception message which wasn't 'known' by the team, I'd use the information in the e-mail to work out which tester triggered the error, then armed with the stack trace and other information, shout over and ask exactly what they had done to arrive at the problem. Much more efficient than waiting for a full bug report at the end of the day/week!

Slide bullets:

  • Means an error cannot happen without the team being aware
  • We built this for production, but was even more useful in dev/testing!
  • Implemented in same custom HTTP module as error page redirection

What it looks like:

ExceptionEmail

Tip #4.3 - implement proper tracing in your code

Explanation:

So being alerted about unhandled exceptions is one thing, but the stack trace and other details in the e-mail are often not enough information to actually fix the bug easily. This can be because we can't see exactly what happened in the code (e.g. values of variables) leading up to the error. The only real way to obtain this information from production servers (or anywhere where we can't use the debugger) is to add trace/log statements to your code, which when enabled will write out these details as the code executes. Since this has to be done manually, clearly the trade-off here is the time to implement this robustness. I would strongly recommend using a code productivity tool such as ReSharper (e.g. going beyond Visual Studio snippets) to drop in these statements quickly rather than relying on typing or copy/paste.

Slide bullets:

  • Provides ability to quickly locate bugs in your code
  • Trade off is time/effort to implement
  • Consider productivity tools such as ReSharper/CodeRush to lessen impact

What it looks like:

This is showing trace output with DebugView - notice I've configured DebugView to show me 'warning' trace statements in yellow and 'error' statements in red:

TracingError

Tip #5 - design for flexibility

Again, this one is broken down into two tips:

Tip #5.1 - using SharePoint lists for configuration data

Explanation:

As I said in my presentation abstract, since the only certainties in life are death, taxes and clients changing their minds, we should always aim to develop a solution which is flexible enough to accommodate some of the changes we may be asked to implement. We're probably all used to the idea of storing site data which may change in SharePoint lists, but I like to extend this to 'configuration' information which dictates how the site behaves. The 'Config Store' framework I wrote for this can be found on Codeplex at www.codeplex.com/SPConfigStore - this provides the list, content type, caching and API to retrieve 'config items' in code.  So to take an example from our project, we had a switch for whether newly-created users need to change their password on first logon. Clearly this is something we need enabled in production, but can be a pain in our test environments where the client needs to create users and then get on with running specific test scripts. So, by having such a switch in a SharePoint list, we get the benefit that key configuration of the site can be changed as easily as changing a SharePoint list item, as opposed to perhaps being stored in web.config where I'd need to RDP onto the server, open a file, trigger an app pool recycle etc.

We stored 130+ configuration settings in our Config Store list, and of course, applied appropriate permissions so that unauthorized users could not access the list.

Slide bullets:

  • Use SP lists for values the client may wish to edit, but consider caching

What it looks like:

ConfigStore

Tip #5.2 - implement a custom master page class

Explanation:

Although SharePoint gets master pages from .Net 2.0, these really deal with implementing a consistent look and feel only. If you want consistent functionality or behaviour across your pages, a custom master page class can help here. To implement this, the key is to modify the class which your master page derives from in the @Master directive. We used this approach to implement code which needs to execute on every page load, and even if you don't have this requirement from the start, I'd advocate using it so you have a convenient place to put such code if the need arises.  

Slide bullets:

  • Use for any code which should execute on every page load
  • Examples on our site:
    • Check if trial user/when trial access ends
    • Check if accepted Terms & Conditions
    • Check has supplied their initial user profile info
    • Enforce use of HTTPS
  • Can also use to expose properties for commonly checked items (e.g. username, logged in time)

What it looks like:

<%@ Master language="C#" Inherits="MyCompany.MyClient.MyProject.BasePage" %>

Conclusion/resources

So that's it for my tips, all feedback/suggestions welcome! In terms of key resources, in addition to AC's book here are some selected articles and so on I'd recommend looking into:

17 comments:

Andy Burns said...

One tip - in the tracing example, you were putting the name of the method into the trace message itself. That works, but typing it is a pain for those of us without Resharper (too tight!)

Alternatively, you could could just override/encapsulate the tracer, and dig the calling object/method out of the StackFrame using the Reflection API. Not as bad as it sounds:

http://www.novolocus.com/2008/04/02/use-reflection-to-find-a-calling-method/

I also used that to encapsulate the levels of the .writeif(), so I'd trace things with just:

trace.warning("some warning);

...and get the benefit of Visual Studio typeahead.

Anonymous said...

Hi Chris,

First of all let me say well done on the presentation. You communicated your material brilliantly with a nice balance of talking and demonstration.

In regards to tip #2 - I am extremely surprised you are not using a web deployment project to package all your user controls into a single DLL, which can then be dropped into the bin directory or GAC.

This then saves all the hassle of needing to drop ascx files into the CONTROLTEMPLATES directory. Plus more importantly it allows you to work with a standard web project containing user controls and test pages, which can be run up within seconds to pre-test. In order to test the user controls fully within SharePoint, a quick compile of the web deployment project, drop the DLL into the bin or GAC, IISRESET and then refresh the sharepoint page!

Another added benefit is when it comes to developing custom WebParts which are build using your custom user controls. Instead of having to load the ascx templates you simply have a reference from the WebPart DLL project to the single user control DLL (outputted from the WDP). The user control is then instantiated with MyUserControl muc = new MyUserControl().

Cheers,

DanH

ps - not to sure about your chosen approach in tip #1. Going to give it some thought and may post in a separate comment!

Anonymous said...

This is great info. I will try some of them and leave you an feedback.

MOSSBUDDY said...

Those are really very important tips. In relation to the master page and code I had a query, what if there is a requirement where in on a internet portal they would like to display navigation bar with all their sites (4 sites) and on click of each tab it takes the user to a landing page of that site. Now this behaviour is different for anonymouse user (not logged in user) and logged in user, how do we achieve this? how do we have 2 different landing pages i.e. one for anonymous user and other for authenticated user (FBA auth and internet portal).

Gunnar said...

Thanks for some very useful tips. Everything that makes wcm development in sharepoint easier is highly interesting.
Does anybody know why SPD sometimes untabifies your code into a total mess, just out of the blue?
/Gunnar

Ian Morrish said...

See what can be done with SharePoint Internet sites. Top 100 examples http://www.wssdemo.com/Pages/topwebsites.aspx

Chris O'Brien said...

Hey Andy,

Great tip. I think going up the stack level by one frame is probably fine for this - I think at one point I did have some trace code which did this, but I'd bet there is a performance penalty. However, we're not too bothered at this stage (hey, we've enabled tracing so we've got bigger problems to solve!) so I think that kind of mitigates it. In terms of having the 'neater' form where the trace level is only passed to the method once, I'm intentionally not doing this since I occasionally want the flexibility to trace something at a different level to the trace switch. This is very rare, but I do occasionally come across circumstances where this seems to make sense - possibly connected with doing something 'trace-like' which isn't actually tracing - e.g. dropping a separate XML file on the filesystem if any tracing is enabled.

In the end, I kind of still think you gain from ReSharper though as you can use it for all sorts of things. Nevertheless, yours is a good tip.

Chris.

Chris O'Brien said...

DanH,

Not quite sure I see how a Web Deployment Project would help in our particular scenario, but that could be because I've not used them extensively. I didn't talk about this in the presentation, but our setup is as follows:

- use web project template to give us a single assembly for the web project (needless to say, the entire solution has many VS projects)
- use MSBuild scripts to copy run-time files (assemblies, user controls, other asset files) to a build server
- MSBuild scripts also plug into XmlPreprocess so we can do conditional processing of web.config files based on custom VS build types (e.g. for a 'Debug' build, use local appSettings variables, for a 'QA' build, use appropriate QA values etc.)
- after testing on the build server, do a simple XCOPY/gacutil operation to deploy to QA and then other downstream environments when ready

We're fairly happy with this aspect of development. It's possible a web deployment project would help streamline this further but I probably don't know enough about them to see what..

Cheers,

Chris.

Chris O'Brien said...

Mossbuddy,

If I understand correctly, you might have a couple of options:

- write some code which checks if the user is authenticated or not and then redirects to the appropriate landing page (this could be in the form of a control which is added to the top-level page)
- use something like the ASP LoginView control to show different page content to authenticated/anonymous users

Does that answer your question?

Chris.

MOSSBUDDY said...

Thank you Chris, I was thinking to use a custom PortalSiteMapProvider which would have the GetChildNodes() method that will query a Sharepoint List (this list will have entries for various URLs for top navigation for anonymous and authenticated users, so basically 2 links or entries inside the list for each Tab: one for anonymous users and other for authenticated users).
So this way the global navigation would have a different link for different set of users.

Do you think this would be a good idea? Also the pages shown to the anonymous users are based on a layout with content editor webpart to change the static content whenever required.

Appreciate your advice.

Rahul said...

Thank you Chri O'Brien, this is very helpful. Can you please share how did you implement the custom error message page, was it a custom aspx page in the layouts folder that was shown. I mean was the message just passed to the branding site? not sure of how it should be implemented so asking this basic question. thank you.

Chris O'Brien said...

Rahul,

The custom error page was implementing by doing the following:

- creating a standard publishing page in SharePoint and adding the error message text into a field
- using code in a HTTP module to redirect to this page if an unhandled exception occurs

I recently publishing some sample code to do this at Sample code from my WCM tips presentation.

HTH,

Chris.

Chris O'Brien said...

Mossbuddy,

I still think it's possible to do what you're trying to do with much less work - it shouldn't be necessary to write a custom navigation provider as far as I can see.

Remember that SharePoint will automatically 'security trim' navigation links based on the current user's permissions. So if certain site areas are not accessible to anonymous users they will not see the link, but authenticated users with the correct permissions will.

Suggest designing your site's information architecture (structure of child sites and pages) in a way which supports your split of authenticated/anonymous users. You can then apply appropriate permissions and let SharePoint do the rest.

HTH,

Chris.

Anonymous said...

Hello Chris,

I've been experimenting with tips 1 and 2 of your blog, but am not able to reproduce them, without some extra steps:

- Convert the virtual directory to an application in IIS
- Manually copy the assembly from my web application's bin folder, to the bin folder in the root of the sharepoint application

Do you have any thoughts on this? (We're using IIS7/MOSS2007/VS2008)

Thanks, Dave

Chris O'Brien said...

Dave,

You certainly shouldn't need the IIS virtual directory to be an application - as I show in the image, it's just a regular virtual directory in our site.

On the second point, yes you will need to copy the assembly to the SharePoint web app bin folder (i.e. the 'real' bin folder for a SharePoint site) - or the GAC if you're happy for the assembly to run from there. I still prefer a post-build event on the VS project to do this, but any of the various methods would be fine.

HTH,

Chris.

Andy Burns said...

Regarding point 2 - removing the .CS files implies that your user controls aren't code behind - is that right? Are you creating an assembly that the user controls inherit from?

If so, why, what's wrong with codebehind user controls? I keep seeing instructions to compile the code behind to an assembly, but I don't get the reasoning behind it...

(Might be a stupid question!)

Chris O'Brien said...

@Andy,

Yep, generally all my user controls go exclusively in the web project, so the code-behind stuff gets compiled into that assembly. I much prefer this as then there are fewer files to deploy (and potentially somehow end up out of sync/with a versioning issue etc. etc.). There is no perf difference though as it gets compiled down to the same underlying MSIL.

So probably down to individual preference, but is there ever a compelling reason to not have all that code pre-compiled into the assembly?

C.

P.S. I use a class libary project type even for my web project, then edit the .csproj file so that Visual Studio gives me "Add new user control" - I think this is a fairly common approach.