Friday, June 5, 2009

updating a layout using features

It is well known now that probably the best way to use site definitions and features is to start off with the most basic definition and do as much as possible in the feature. That also includes things like page layouts and master pages.
Words of warning. Ghosting and Unghosting can trick you into thinking that the updates are not working. As soon as you decide to customise a page using SPD which was deployed via a feature, SharePoint will put a copy into the database and always load that one instead of the ghosted one on the file system. Thus you can update your feature as often as you like, you won't see the changes. Solution?
make sure you have all modificaitons in the source file and reset the layout page to Site definition. Strictly speaking it never was part of a "site definition" but part of a feature, so you won't find the Reset button in SPD, but you can still reset it manually using the web interface. Simply copy the url of the page in question, go to Site Settings and there you will find a link to Reset to Site Definition where you can past the link.

Something Else I noticed though. If you customise the layout page to include web parts on creation using the AllUsersWebPart tag, like the Table of Contents Webpart in Andrew Connel's Minimal Site Definition example, each time you reinstall the feature a new instance of the web part will be added to the layout definition, causing multiple instances to be created on the new page creation. So try to stay away from web part customisation in layouts if you can.

Finally, you might want to make other property changes, like providing a new Preview image url or modifying the content type or such. These settings live on the list item and not in the file. Editing the item will solve those problems but as soon as you check your changes in, the page will be unghosted and you're back at the first dilemma. No worries though. Simply reset to site definition again and you're as good as gold. Yup, the changes to the list item don't go lost when doing that reset, so you won't lose those updates.

Regards

Bye Bye Groove, Hello SharePoint Workspaces

In the past many people have asked me what this Groove thing is about. Looks like a miniature version of SharePoint I was told by some. Others called it the Briefcase on Steroids. Most people who were implementing SharePoint though called it surperflous. And they had a point!
With a good Information Architecture and Enterprise Content Management Strategy babsed on SharePoint a tool such as Groove should not be required. Especially as it was not SharePoint compatible and mimicking a file sharing application more than a collaboration tool.
I'm not saying that Groove was a bad idea. Having a desktop tool which makes working on shared documents easier and more intuitive without the need to use a web interface for access is a great idea. That's why making Groove SharePoint savvy and making it more like a SharePoint Workspace tool makes even more sense.

Still in its infant stages, but there is now a new blog for the new project
http://blogs.msdn.com/sharepoint_workspace_development_team/
A good place to keep tabs on to see what kind of SharePoint Integration is coming in the next version of Office and how the bridge between SharePoint ECM and productivity applications is becoming stronger and more seamless.

Wednesday, June 3, 2009

Integrating AJAX Control Toolkit into SharePoint

Hey folks, sorry it took me a week to get round to this blog post, but I needed to make sure I could upload all the stuff somehow. This is going to be a big one, so sit back, relax and enjoy the show:

Ajax Control Toolkit

A compilation of dynamic controls
—With and without Server Side interaction
—Built on the System.Web.Extensions Components
—Samples at http://www.asp.net/ajax/
—Source at http://www.codeplex.com/

Prerequisites

Minimum
—ASP.NET 2.0 AJAX Extensions v1.0
—Ajax Control Toolkit 20229 (.net 2.0)
—Windows SharePoint Services 3.0 SP1
Recommended
—NET Framework 3.5 SP1
—Ajax Control Toolkit 30512 (.net 3.5sp1)
—Windows SharePoint Services 3.0 SP2

How to get it running? a combination of tweaking the web.config, and clever web part design

Configure the web.config

<configSections>
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication"/>
<sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="Everywhere" />
<section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" />
<section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" />
</sectionGroup>
</sectionGroup>
</sectionGroup>
</configSections>

Allow Ajax controls on the pages

<pages>
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</controls>
</pages>

Add Extensions Assembly

 <assemblies>
<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</assemblies>

Add Ajax Http handlers

<httpHandlers>
<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
</httpHandlers>

Add Http Module

<httpModules>
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</httpModules>

Add WebServer config section (IIS7.0 only)

<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules>
<add name="ScriptModule" preCondition="integratedMode" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</modules>
<handlers>
<remove name="WebServiceHandlerFactory-Integrated" />
<add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode“
type="
System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</handlers>
</system.webServer>

Optional, add profile config

<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled="true" requireSSL = "truefalse"/>
<profileService enabled="true” readAccessProperties=“First,Last"
writeAccessProperties=“First,Last" />
</webServices>
<scriptResourceHandler enableCompression="true" enableCaching="true" />
</scripting>
</system.web.extensions>

IMPORTANT: Mark Ajax controls as Safe

<SafeControls>
<SafeControl Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI" TypeName="*" Safe="True" />
</SafeControls>

Add Script Manager to Page

To the master page right at the top
<asp:ScriptManager runat="server" ID="ScriptManager1"></asp:ScriptManager>
Or programatically in a baseclass for your AJAX enabled Webparts
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (ScriptManager.GetCurrent(this.Page) == null)
{
ScriptManager scmanager = new ScriptManager();
scmanager.ID = "scriptManager";
Page.Form.Controls.AddAt(0,scmanager);
}
}

Next up: a sample WebPart that uses the AutoComplete control from the toolbox . Before you can use the toolkit you will need to download and compile the project though. Then add the reference to the toolkit in your WebPart Project.

using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using AjaxControlToolkit;

namespace KnowledgeCue.WSS.AjaxWebParts
{
[Guid("6ef07bde-99b3-45d0-8dd4-33ce7ae25572")]
public class AutoCompleteTextBox : KnowledgeCue.WSS.BaseWebPart
{
Literal textBoxLabel = new Literal();
TextBox textBox = new TextBox();
AutoCompleteExtender autoComplete = new AutoCompleteExtender();

public AutoCompleteTextBox()
{
}

protected override void CreateChildControls()
{
base.CreateChildControls();
string webServicePath = "http://wss-1:90/_layouts/AutoCompleteService.asmx";

textBoxLabel.Text = "Type the name of a cool dude";
this.Controls.Add(textBoxLabel);
textBox.Attributes.Add("id", "SelectCoolDudeTextBox");
textBox.ID = "SelectCoolDudeTextBox";
this.Controls.Add(textBox); ;

autoComplete.MinimumPrefixLength = 1;
autoComplete.ServicePath = webServicePath;
autoComplete.ServiceMethod = "GetCoolDudes";
autoComplete.TargetControlID = "SelectCoolDudeTextBox";
this.Controls.Add(autoComplete);
}
//Script Manager Fix
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (ScriptManager.GetCurrent(this.Page) == null)
{
ScriptManager child = new ScriptManager();
child.ID = "scriptManager";
if (this.Page.IsPostBack)
{
this.Page.ClientScript.RegisterStartupScript(typeof(AutoCompleteTextBox), this.ID, "_spOriginalFormAction = document.forms[0].action; _spSuppressFormOnSubmitWrapper=true;", true);
}
if (this.Page.Form != null)
{
string str = this.Page.Form.Attributes["onsubmit"];
if (!(string.IsNullOrEmpty(str) !(str == "return _spFormOnSubmitWrapper();")))
{
this.Page.Form.Attributes["onsubmit"] = "_spFormOnSubmitWrapper();";
}
this.Page.Form.Controls.AddAt(0, child);
}
}
}
}
}

Finally, the Webservice which the autocomplete textbox relies upon:
<%@ WebService Language="C#" Class="AutoCompleteService" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class AutoCompleteService : System.Web.Services.WebService {

[WebMethod]
[ScriptMethod]
public System.Collections.Generic.List<string> GetCoolDudes(string prefixText, int count) {
string[] dudes= new string[] { "Alex Dean", "Chandima Kulathilake", "Rachael Greene", "Dean Moor", "Duncan Hammond" };
System.Collections.Generic.List<string> dudeList = new System.Collections.Generic.List<string>();
int i = 0;
foreach (string dude in dudes)
{
if (i == count) break;
else
{
if (dude.ToLower().StartsWith(prefixText.ToLower()))
{
dudeList.Add(dude);
i++;
}
}
}
return dudeList;
}

}

SPVisualDev File exists in Template Error

I've stumbled across this one and it took me some time to figure it out. That error will crop up if you are deploying a file either via a feature or site template twice. In my case I had two Modules in an elements file to split up the Preview images from the actual files being deployed to the masterpage gallery. that's where SpVisualDev gets its knickers in the twist. Can't deal with multiple modules in one file and keeps on adding all files to the last module. also the ones that are already referenced in the first one.
Fix? put all files into one module or SPVisualDev will screw with the elements and it won't work.

Basic Publishing Site Definition

Andrew Connell wrote in his great SharePoint 2007 WCM book how to create a Minimal Site Definition to use for Publishing Sites. The idea is to have a clean starting point for new Projects which use Features as the main tool for customisation. The Site Definition itself is pretty basic and stripped down to its bare necessities while all the fun stuff lives in Features. A great way to start a project is to plan ahead what functionality will be living in different features, adding basic shell like features to the definition at the start and actually do the coding later. But even if you didn't plan for everything in advance and realise that there is a feature missing or a piece of code that can't be integrated into an existing feature, simple staple it on top of the Site Definition in hind sight and you're as good as gold.
Wrox Press also give you the code for that Book for download on their website. The Solution for the Minimal Site Definition you'll be lookng for lives in Chapter 5. But beware, there are a few bugs in the download. Check the Errata for Errors in Download and fix up the onet.xml and publishing layout page.
One thing that was missed totallly in this definition is the fact that all pages should live in the Pages Library. Also the homepage. In the example from Andrew, the home page lives WSS style in the Root. Not very clean. The fix is simple enough though: Change the url of the page (preferably even changing the default.aspx to actually use the layout properly!) and you're ready to go. Here is my version of the last entry in the onet.xml file:


OLD:
<Modules>
<Module Name="Default" Url="" Path="">
<File Url="default.aspx" NavBarHome="True" Type="Ghostable" />
</Module>
</Modules>

NEW:

<Modules>
<Module Name="Default" Url="$Resources:cmscore,List_Pages_UrlName;" Path="">
<File Url="Default.aspx" Type="GhostableInLibrary">
<Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/PSATOC.aspx, ~SiteCollection/_catalogs/masterpage/PSATOC.aspx" />
<Property Name="ContentType" Value="$Resources:cmscore,contenttype_welcomepage_name;" />
</File>
</Module>
</Modules>

Thursday, May 28, 2009

Exception handling Base WebPart

This piece of code will revolutionise the way you develop web parts in sharepoint. No more pages crapping out on you, no more crashes or unsolvable problems. All exception handling done for you automatically without the need to include a single try/catch block. I'm not saying you should not handle exceptions which you can forsee. But those are the easy ones. What about those unexpected ones?
Andreas Knudsen, a fellow SharePoint Programmer came up with a beautiful solution which takes full advantage of a combination between early and late binding to force the runtime to execute the exception handling version of the base routines before execuing your implementation code. As I'm not even going to try to take credit for this gem, get the code directly from the genius himself

http://andreascode.blogspot.com/2007/12/general-exception-handling-in-web-parts.html

How does it work? Playing with polymorphism is the answer.
The runtime always tries to find out the best version of a method on an object to call, no matter what type the pointer is.
When you put a line of code like

MyWebPart.CreateChildControls()

into your own code, the runtime will not have to look far, as you're calling it on the type of which the object is. But...

WebPart x = MyWebPart;
x.CreateChildControls();

Now the runtime will need to start at the base class WebPart and figure out which version is the best to call. Normally each new version overrides the old one and the runtime will end up at MyWebPart.CreateChildControls(). Unless you put a stop sign in between. Such a stop sign is the new keyword. When you have a class in between WebPart and MyWebPart (like MyBaseWebPart) and instead of writing public override CreateChildControls() but public new CreateChildControls() the runtime is forced to take a step back and execute the one a level up. To make it more visible you can seal off the exception handling method, just to be sure nobody tries to override it.

So now we've forced the runtime to execute the version of code that has exception handling. How do we get back to our implementation code? Through sidestepping the Stop sign. You need a new class which inherits from the exception handling class for this. the exception handling one calls a new function like CreateChildControlsX and the runtime will again try to figure out the best version and will end up in derived.CreateChildControlsX because you overrode it there. Here you simply call the original function name again (CreateChildControls) and the runtime will find the best version again. Now the runtime is not starting at the top (WebPart) but at the current location (derived) and thus the Stop sign is left behind and it can go all the way down to your implementation and execute that.

I know it sounds so geeky, But thise piece of code is seriously cooooooool.

Tuesday, May 26, 2009

SharePoint Explorer View in Windows 2008 and 2003

If anybody out there has been building demo machines on Windows Server 2008 or Windows Server 2003 you will have run into this issue at some point. Explorer View does not work on document libraries, nor can you map a network drive to a sharepoint site.
Following constellations come together:

a) the server platforms effectively don't like web folder views until you install the fix KB907306
That fix works for both Windows 2003 and 2008.
Now you can happily map a sharepoint site to your network places and use the Windows Explorer to browse the sharepoint sites.

b) your browser now complains that "This folder cannot be opened in Internet Explorer". This will happen when you have IE 7 or higher and protected mode is on. Simple. Disable protected mode via Tools>Internet Options>Security. Remember to add the site to the Intranet Zone

c) What? can't disable the protected mode on Windows Server 2008? No problem. Open the Server Manager. On the root node of the tool you should see Server Summary within there is a section for Security Information. Here you will find a link to Configure IE ESC (Enhanced Security Configuration). That's where you can disable the "microsoft parental controls for dummies" tick box.
d) now you can disable protected mode on the Intranet Zone.

You might ask, why not simply move the site to Trusted Sites. Well, Trusted sites actually have a higher security rating than Intranet zone. Plus you automatically get signed into the intranet zone and not to th trusted sites. Plus the sharepoint site is on the Intranet. So that is also the Zone it should live under!

PS. Running Windows Server 2008R2 or Windows Server 2008 SP1? Then try enabling the Feature called  "Desktop Experience" in the Roles and Features configuration of the Server

Happy Exploring!