Microsoft Certified SharePoint Trainer ranting on about SharePoint, Windows, SharePoint Training and Sharepoint Software
Friday, June 5, 2009
updating a layout using features
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
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
Fix? put all files into one module or SPVisualDev will screw with the elements and it won't work.
Basic Publishing Site Definition
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
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
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!