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;
}

}

2 comments:

nemesis_2007 said...

Hi!!!
Great article. But i´m have a problem. Im trying to develop a webpart with a textbox and a calendar extender. The thing is show the date in a label from that textbox, but when i click de buttton, i lost the date in the textbox and i cant show it in the label.
What is wrong?
Do i post the code?

Thanks.

Eric Ramírez

Unknown said...

Sounds to me like you have a viewstate problem.
Make sure that the viewstate for the textbox is working and populating.

Things that can destroy viewstate:
Creating controls on the fly in the page load
Setting EnableViewState to false
Setting control defaults in the page load

If you can't figure it out, try to read out the value of the textbox on the postback.

regards,
Alex