Walkthrough: Creating a Basic ASP.NET AJAX-enabled Web Part

This walkthrough describes the steps for creating a basic ASP.NET AJAX-enabled Web Part that you can add to your Web Part page. The example creates a SayHello Web Part that derives from the ASP.NET 2.0 WebPart Class (in the System.Web.UI.WebControls.WebParts Namespace in the ASP.NET Class Library) for use in a Windows SharePoint Services 3.0 Web site.

Note Note:

For more information about ASP.NET Web Parts, see ASP.NET Web Parts Overview and Introducing Web Part Controls.

Prerequisites

Windows SharePoint Services 3.0

Visual Studio 2005

Step 1: Create a Web Part Project

To create an AJAX-enabled Web Part control, you start by creating a class library project in the class library in Visual Studio 2005.

To create an ASP.NET Web Part project in Visual Studio 2005

  1. Start Visual Studio 2005.

  2. On the File menu, point to New, and then click Project.

  3. In Project Types, under Visual Basic or C#, select Windows.

  4. In the Templates pane, select Class Library.

  5. Type Sample.SayHello as the project name.

Step 2: Rename the base class and add required namespaces

After you create the project, a blank class file is displayed. You can change the default class name of Class1 to easily identify your new Web Part. In a class library project, only a few namespaces are included. You need to add two required namespaces and references to their assemblies. You must also derive the base class from System.Web.UI.WebControls.WebParts.WebPart. Then, you must add two global variables to update the user interface (UI).

To add namespace references and shared UI components

  1. Rename the default class by selecting Class1.cs in Solution Explorer, right-click, click Rename, and type SayHelloWebPart as the file name.

  2. On the Project menu, click Add Reference.

  3. In the Add Reference dialog on the .NET tab, select System.Web.Extensions and click OK.

  4. Repeat steps 2 and 3 for the System.Web namespace.

  5. In the references area of the class file, add a reference to System.Web.UI.WebControls and create two private variables for the UI, as shown in the following code.

    C#
    using System;
    using System.Text;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    namespace Sample.SayHello
    {
       public class SayHelloWebPart : WebPart
       {
         private Label displayName;
         private TextBox inputName;
       }
    }

    Visual Basic
    Imports System
    Imports System.Text
    Imports System.Web.UI
    Imports System.Web.UI.WebControlsImports System.Web.UI.WebControls.WebParts
    Public Class SayHelloWebPart
       Inherits WebPart
       Private displayName As Label
       Private inputName as TextBox
    End Class
  6. You have now created a basic structure for the Web Part.

Step 3: Override CreateChildControls and create a button event handler

After configuring the new class to act as a Web Part, you must override the CreateChildControls method to build the UI. You must also add a button handler to refresh the display data.

To override CreateChildControls and create a button event handler

  1. In the SayHelloWebPart.cs file, copy and paste the following code to override the CreateChildControls method

    C#
    protected override void CreateChildControls()
    {
       base.CreateChildControls();
    
       //Fix for the UpdatePanel postback behaviour.
       EnsurePanelFix();
    
       LinkButton sayHello = new LinkButton();
       UpdatePanel refreshName = new UpdatePanel();
       ScriptManager scriptHandler = new ScriptManager();
       displayName = new Label();
       inputName = new TextBox();
    
       //Set up control properties.
       this.displayName.ID = "displayName";
       this.displayName.Text = "Hello!";
       this.inputName.ID = "inputName";
       sayHello.ID = "sayHello";
       sayHello.Text = "Say Hello";
       scriptHandler.ID = "scriptHandler";
       refreshName.ID = "refreshName";
       refreshName.UpdateMode = UpdatePanelUpdateMode.Conditional;
       refreshName.ChildrenAsTriggers = true;
    
       //Add the EventHandler to the Button.
       sayHello.Click += new EventHandler(ClickHandler);
    
       //Add the user interface (UI) controls to the UpdatePanel.
       refreshName.ContentTemplateContainer.Controls.Add(this.inputName);
       refreshName.ContentTemplateContainer.Controls.Add(sayHello);
       refreshName.ContentTemplateContainer.Controls.Add(this.displayName);
    
       //The ScriptManager control must be added first.
       this.Controls.Add(scriptHandler);
       this.Controls.Add(refreshName);
    }

    Visual Basic
    Protected Overrides Sub CreateChildControls()
       MyBase.CreateChildControls()
    
       'Fix for the UpdatePanel postback behaviour.
       EnsurePanelFix()
    
       Dim sayHello As New LinkButton
       Dim refreshName As New UpdatePanel
       Dim scriptHandler As New ScriptManager
       displayName = New Label
       inputName = New TextBox
    
       'Set up control properties.
       Me.displayName.ID = "displayName"
       Me.displayName.Text = "Hello!"
       Me.inputName.ID = "inputName"
       sayHello.ID = "sayHello"
       sayHello.Text = "Say Hello"
       scriptHandler.ID = "scriptHandler"
       refreshName.ID = "refreshName"
       refreshName.UpdateMode = UpdatePanelUpdateMode.Conditional
       refreshName.ChildrenAsTriggers = True
    
         'Add the EventHandler to the Button.
       AddHandler sayHello.Click, _
         New EventHandler(AddressOf ClickHandler)
    
       'Add the user interface (UI) controsl to the UpdatePanel
    
       refreshName.ContentTemplateContainer.Controls.Add(Me.displayName)
       refreshName.ContentTemplateContainer.Controls.Add(Me.inputName)
       refreshName.ContentTemplateContainer.Controls.Add(sayHello)
    
       'The ScriptManager must be added first.
       Me.Controls.Add(scriptHandler)
       Me.Controls.Add(refreshName)
    End Sub
  2. Then, in the SayHelloWebPart.cs file, copy and paste the following code.

    C#
    private void ClickHandler(object sender, EventArgs args)
    {
       this.displayName.Text = "Hello, " 
         + this.inputName.Text.ToString() + ".";
    }

    Visual Basic
    Private Sub ClickHandler(ByVal sender As Object, _
                  ByVal args As EventArgs)
       Me.displayName.Text = "Hello, " & Me.inputName.Text & "!"
    End Sub
  3. Now you have created the basic UI and button handling event.

For ASP.NET controls that use the JavaScript _doPostBack() function to commit changes, a regular full-page postback event may occur even when the Web Part is inside an UpdatePanel control. Windows SharePoint Services 3.0 and ASP.NET AJAX cache certain form actions, which can cause a conflict between SharePoint and ASP.NET AJAX. To change this behavior, you must add code to scripts that are running in Windows SharePoint Services 3.0.

Step 4: Modify Windows SharePoint Services 3.0 scripts to change doPostBack() behavior

To modify scripts to ensure proper doPostBack() behavior

  1. In the SayHelloWebPart.cs file, copy and paste the following code.

    C#
    private void EnsurePanelFix()
    {
       if (this.Page.Form != null)
       {
         String fixupScript = @"
         _spBodyOnLoadFunctionNames.push(""_initFormActionAjax"");
         function _initFormActionAjax()
         {
           if (_spEscapedFormAction == document.forms[0].action)
           {
             document.forms[0]._initialAction = 
             document.forms[0].action;
           }
         }
         var RestoreToOriginalFormActionCore = 
           RestoreToOriginalFormAction;
         RestoreToOriginalFormAction = function()
         {
           if (_spOriginalFormAction != null)
           {
             RestoreToOriginalFormActionCore();
             document.forms[0]._initialAction = 
             document.forms[0].action;
           }
         }";
       ScriptManager.RegisterStartupScript(this, 
         typeof(SayHelloWebPart), "UpdatePanelFixup", 
         fixupScript, true);
       }
    }

    Visual Basic
    Private Sub EnsurePanelFix()
       If Me.Page.Form IsNot Nothing Then
         Dim fixupScript As New StringBuilder()
    
         With fixupScript
           .AppendLine("_spBodyOnLoadFunctionNames.push" & _
           "(""_initFormActionAjax"");")
           .AppendLine("function _initFormActionAjax()")
           .AppendLine("{")
           .AppendLine("if (_spEscapedFormAction == " & _
           "document.forms[0].action)")
           .AppendLine("{")
           .AppendLine("document.forms[0]._initialAction = " & _
           document.forms[0].action;")
           .AppendLine("}")
           .AppendLine("}")
           .AppendLine("var RestoreToOriginalFormActionCore = " & _
           RestoreToOriginalFormAction;")
           .AppendLine("RestoreToOriginalFormAction = function()")
           .AppendLine("{")
           .AppendLine("   if (_spOriginalFormAction != null)")
           .AppendLine("   {")
           .AppendLine("       RestoreToOriginalFormActionCore();")
           .AppendLine("       document.forms[0]._initialAction = " & _
           "document.forms[0].action;")
           .AppendLine("   }")
           .AppendLine("}")
         End With
         ScriptManager.RegisterStartupScript(Me, _
           GetType(SayHelloWebPart), "UpdatePanelFixup", _
           fixupScript.ToString(), True)
       End If
    End Sub
  2. Now you have modified the scripts to ensure proper postback handling.

After you have added all of your code to your Web Part project, you can build your sample Web Part and deploy it. For more information about deploying a Web Part, see Walkthrough: Creating a Basic Web Part.

See Also

Other Resources

Solutions and Web Part Packages

Tags :


Community Content

James Zheng
Avoiding Stack Overflows

I encountered an issue when I tried to add webparts of differing types into the same web part page - the result was a stack overflow causing IE to abruptly terminate.

The problem arises when the script defined in the EnsurePanelFix method above, is inserted more than once into a page. The execution of the first script block is harmless. The execution of any subsequent block however, eventually results in a stack overflow. The reason is that the subsequent assignment to RestoreToOriginalFormActionCore is from the newly defined RestoreToOriginalFormAction (defined in the frist script block), and not the original RestoreToOriginalFormAction method.

In the subsequent execution of the script block, RestoreToOriginalFormActionCore and RestoreToOriginalFormAction are made equivalent, so the RestoreToOriginalFormAction method becomes infinately recursive.

     var RestoreToOriginalFormActionCore = 
RestoreToOriginalFormAction;
RestoreToOriginalFormAction = function()
{
       if (_spOriginalFormAction != null)
{
RestoreToOriginalFormActionCore();

There are a number of ways around this, (that I can think of) but none of them are ideal. The first is to ensure that only one script block is inserted into the page, no matter how many types of your web parts are added to the page. This is done by modifying the call to RegisterStartUpScript. I found that it is the combination of the type parameter and key parameter together that determines whether the script is inserted. So I just created a base web part (MyBasePart), and used that type as the type parameter. typeof(object) also worked; I assume you can use any type that you want.

ScriptManager.RegisterStartupScript(this, typeof(MyBasePart), "UpdatePanelFixup",fixupScript, true);

Well this works if you publish all of the webparts that go into the page, but there will be issues if you also want to use web parts published by others, because depending on what type/key they used, you might still end up with more than one script block in your page. So the solution that I'm using for the time being is to define RestoreToOriginalFormAction based on what it is originally. (But this will break if the definition of RestoreToOriginalFormAction in WSS changes)

String fixupScript = @"
if (typeof(_spBodyOnLoadFunctionNames) !== 'undefined'){
_spBodyOnLoadFunctionNames.push(""_initFormActionAjax"");
function _initFormActionAjax() {
if (_spEscapedFormAction == document.forms[0].action){
document.forms[0]._initialAction =
document.forms[0].action;
}
}
  
      RestoreToOriginalFormAction = function() {
if (_spOriginalFormAction != null) {
if (_spEscapedFormAction==document.forms[0].action){
document.forms[0].action=_spOriginalFormAction;
}
_spOriginalFormAction=null;
_spEscapedFormAction=null;
  
document.forms[0]._initialAction = document.forms[0].action;
}
};
  
}";

I used the VS script debugger to find the original definition of RestoreToOriginalFormAction and incorporated it into the above code block. The new definition simply adds one line at the end:

document.forms[0]._initialAction = document.forms[0].action;

I removed the call to RestoreToOriginalFormActionCore. This is important because if a third party web part appears in the page after yours and again redefines the RestoreToOriginalFormAction method, there is still no possibility for recursion. i.e. RestoreToOriginalFormAction will become:

     RestoreToOriginalFormAction = function()
{
if (_spOriginalFormAction != null)
{
RestoreToOriginalFormActionCore();
document.forms[0]._initialAction =
document.forms[0].action;
}
}";

But RestoreToOriginalFormActionCore is our function, which is not recursive. The only side effect is that

document.forms[0]._initialAction = document.forms[0].action;

will be called twice in succession, but that should be harmless.

Naturally, all bets are off if you want to use your web parts with more than one web part from elsewhere.

***********

The other way to work around this problem is to use a Page property – ClientScript. The ClientScript property gets a ClientScriptManager object to manage, register, and add script to a web page. This is to keep the original script as it is.

The ClientScript returns a ClientScriptManager object, and the object exposes a method, IsClientScriptBlockRegistered() that we use to make sure whether the same script block has already been registered into the web page or not.

This keeps the script block presented in this walkthrough as it is. Below is the sample code:

string scriptKey = "UpdatePanelFixup";

if (!Page.ClientScript.IsClientScriptBlockRegistered( scriptKey ))

ScriptManager.RegisterStartupScript( this, typeof( SayHelloWebPart ), scriptKey, fixupScript, true );


Hope it helps!

- James Zheng


Noelle Mallory - MSFT
This code is causing a regular postback

I've downloaded this code and built a WebPart using it with no changes. I deployed the WebPart to my MOSS 2007 site collection, and added it to a page. Clicking the LinkButton causes a full postback each time with the whole page refreshing. There's no partial rendering of the Label control.

How can I get around this?

I even tried both 1.0 and 3.5 versions of System.Web.Extensions.

Thanks.

[WolfyUK] Have you followed the steps at http://msdn2.microsoft.com/en-us/library/bb861898.aspx to configure WSS 3.0 SP1 for AJAX-enabled Web Parts?

[Noelle Mallory - MSFT] Please post questions to the MSDN Forums at http://forums.microsoft.com/msdn. You will likely get a quicker response through the forum than through the Community Content.

Tags : postback

kjkj3jf
Multiple AJAX-enabled SharePoint Web Parts using this example
I too have encountered issues when using this sample code in WSS 3.0 SP1 with ASP.NET AJAX Extensions 1.0.

Similar to the JavaScript overflow issues identified by jsum, you are only allowed one ScriptManager per ASP.NET page. This means that a check will need to somehow be made to see if a ScriptManager is already present on the SharePoint page before another is added by the AJAX-enabled Web Part. I haven't quite figured out the best way to achieve this yet, but catching the System.InvalidOperationException that is thrown when the second or subsequent ScriptsdddddddfsdfsdfsdfManagers are added to the Page seems to be a temporary workaround.

Stanley Roark
Page Title gets changed when the ajax method is invoked
So I used the same type of architecture as listed here. However, whenever I click the method that performs the ajax call the page title gets changed to illegiable characters. Any idea why this would happen?



This issue is with publishing portal sites.

Publishing->Publishing Portal(site template) and above code works fine with Publishing->Collaboration Portal(site template).
Tags : ajax

Neil I
RE: Page Title gets changed when the ajax method is invoked
Tragen: It appears to be related to using Publishing Webs. If you try it on a regular site it'll probably work. I haven't been able to determine a workaround for that yet. If you do find one, post it here.

rsoares
This code does not work

I am still getting the post backs and you also forgot to Render the controls as follows

protected override void RenderWebPart(HtmlTextWriter output)
{
this.EnsureChildControls();


displayName.RenderControl(output);
inputName.RenderControl(output);
sayHello.RenderControl(output);


//output.Write("Hello FAQ");
}

Tags :

Chad Kapatch
Has anybody found a solution to the post back issue?
Still having postback problems with this web part.
Tags :

Pascal van Alphen
RE: Page Title gets changed when the ajax method is invoked
Simply go to your master page, in the head section, place all the title tag on the same line.

Wrong
<title>
My Title
</title>

Good
<title>My Title</title>

That's it!

---------> Thanks stephaneperron! That works with my problem. - James Zheng

---------> That's it, thanks Stephane ! I'm still not sure why this is happening though. It's like clicking the AJAX control causes the page title control to be 'read' in a different way, also reading the returns in that bit of code. We had

<titleid="idTitle">
<asp:ContentPlaceHolderID="PlaceHolderPageTitle"runat="server"/>
</title>

which resulted in two blocks being shown as the page title.
Changing this to <titleid="idTitle"><asp:ContentPlaceHolderID="PlaceHolderPageTitle"runat="server"/></title>
in the masterpage fixed the page title issue immediately.

- Pascal van Alphen




rkapiljith
The above sample works only first time

Hi'

This example works only for the first time. After adding webpart to a page enter any string in textbox and click the button, first time it works but if i try again it doesn't seems to work. Any clue?

Srinivas

Hi you try this piece of code...


#region KAPILAJAXFixingFormAction
private void KAPILAJAXFixingFormAction()
{
if (this.Page.Form != null)
{
string formOnSubmitAtt = this.Page.Form.Attributes["onsubmit"];
if (formOnSubmitAtt == "return _spFormOnSubmitWrapper();")
{
this.Page.Form.Attributes["onsubmit"] = "_spFormOnSubmitWrapper();";
}
}
ScriptManager.RegisterStartupScript(this, typeof(<put the class name here>), "UpdatePanelFixup", "_spOriginalFormAction = document.forms[0].action; _spSuppressFormOnSubmitWrapper=true;", true);
}
#endregion KAPILAJAXFixingFormAction

Tags :

Brock Luty
Example that works?

As mentioned above, this example does not work if more than one copy of a web-part using the same technique is on the same page b/c they all try to register a script manager.

That seems a bit limiting to only have one Ajax WebPart per page.

Is there an official Microsoft example with a recommended pattern for creating a Ajax enable web-part that can be used more than once on a page?

Brock

Tags :

James. Tsai
Example that works

The script in this example will only register once event if you have multiple instances of the same web part. Because ScriptManager will not register scripts with same key value more than once.

More more info and example, you can read from

http://www.jamestsai.net/Blog/post/How-To-Create-AJAX-enabled-SharePoint-Web-Part-with-UpdatePanel-and-UpdateProgress-in-10-minutes.aspx

Tags :

Page view tracker