Getting SharePoint and ASP.NET AJAX to Work Together

Todd Snyder / Tuesday, July 29, 2008
While, working on Pomegranate I did a lot of research to figure out the best options for using the ASP.NET AJAX library under SharePoint. Unfortunately, I came across a lot of samples that did not work or only worked in some cases.  I am hoping in a future SharePoint service pack Microsoft includes a base AJAX web part that properly sets up the JavaScript to get AJAX to work.
For Pomegranate we followed the approach outlined in the following KB Article. The approach suggests injecting JavaScript that runs during the start up of a page that update the HTML document form’s initial action to be the same as the form’s action (See Code Listing #3). This is needed because by default SharePoint prevents multi post backs from occurring. One caveat with using this approach is that sometimes the AJAX call causes a full post back instead of doing a partial page rendering. To Work around this you must included one of the SharePoint list (Tasks or Announcements) web parts. Note: If you prefer you can hide the list web part and the AJAX call will still work.  I plan on doing some additional research and will let you know if I find a better way to get AJAX to work with SharePoint.
Ok, enough with the war story about getting AJAX to work with SharePoint, let’s move on to an example of building a custom web part that uses AJAX. The example focuses on using a Standard ASP.NET Button control to dynamically load the contents of the Infragistics WebGrid. Once the grid is loaded the example uses the built-in AJAX capabilities of the WebGrid control to filter data.
Image #1: WebGrid displayed in a custom web part
Web Grid AJAX Sample
Before we look at the code for the custom web part we need to take care of a few prerequisites to get AJAX to work under SharePoint. First make sure you are using Windows SharePoint 3.0 SP1 or Microsoft Office SharePoint Server 2007 SP1. These are the only versions of SharePoint that support AJAX. Once you have installed the SharePoint service pack you need to make sure your SharePoint Applications web.config contain all the necessary entries for enabling AJAX to work and create a custom master page that contains an AJAX Script Manager control. See Setting up AJAX to run in SharePoint for more details.
Now that you have AJAX setup to run under SharePoint let’s move onto our example. The first thing you need to do is create a new ASP.NET Server control library (or us an existing one if you prefer) and a new server control to the project. Open the code for the new server control and update it to inherit from System.Web.UI.WebControls.WebParts.WebPart and override the create child control event. When I create a web part I always start simple and just add a label that says “Hello World”. Once the code compiles I sign the assembly for the control, set it up to support Allow Partial Trusted Caller, and deploy it to the BIN folder of my SharePoint application. I then update the SafeControl section of the applications web.config and install the Web Part in SharePoint. I then test to make sure the web part works in SharePoint by adding the web part to web part page in SharePoint.
Code Listing #1: Web.config Safe Control Entry
<SafeControl Assembly="Sample.GridAJAX.Controls,Version=1.0.0.0, Culture=neutral, PublicKeyToken=4e50f862043ae8c9" Namespace="Sample.GridAJAX.Controls" TypeName="*" Safe="True" />
Code Listing #2: Update the Web Part’s assemblyInfo class to include the Allow Partially Trusted Caller Attribute.
using System.Security;
[assembly: AllowPartiallyTrustedCallers()]
Now that my basic web part is working, let’s update the Web Part to support AJAX by adding the necessary code to fix the AJAX Update Panel.
Code Listing #3: Code used for getting an AJAX Update Panel to work properly under SharePoint:
public const string AJAX_HOSTPANEL_NAME = "HostPanel_";
public const string SHAREPOINT_AJAX_POSTBACK_SCRIPTNAME = "UpdatePanelFixup";
ublic const string SHAREPOINT_AJAX_POSTBACK_SCRIPT =
@"_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;
            }
      }";
private ScriptManager _scriptManager;
private UpdatePanel _hostPanel;
 
 
/// <summary>
/// Create Script Manager control.
/// </summary>
private void CreateScriptManager()
{
this._scriptManager = new ScriptManager();
this._scriptManager.EnablePartialRendering = true;            this._scriptManager.EnableScriptLocalization = true;
      this.Controls.AddAt(0, this._scriptManager);
}
 
/// <summary>
/// Setup Sharepoint's JS postback code to handle AJAX calls.
/// </summary>
private void SetupSharePointAjax()
{
      this._hostPanel = new UpdatePanel();
      this._hostPanel.ID = AJAXCommon.AJAX_HOSTPANEL_NAME + this.ClientID;
this._hostPanel.UpdateMode = UpdatePanelUpdateMode.Conditional;
this._hostPanel.ChildrenAsTriggers = true;
this.Controls.Add(_hostPanel);
 
if (this.Page.Form != null)
{
ScriptManager.RegisterStartupScript(this, typeof(GridAJAX),
                    AJAXCommon.SHAREPOINT_AJAX_POSTBACK_SCRIPTNAME,
                    AJAXCommon.SHAREPOINT_AJAX_POSTBACK_SCRIPT, true);
       }
}
 
/// <summary>
/// On Init Control Event.
/// </summary>
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
 
      this._scriptManager = ScriptManager.GetCurrent(this.Page);
      if (_scriptManager == null) // Add a script manager
      {
            this.CreateScriptManager();
      }
 
      this.SetupSharePointAjax();
      this.EnsureChildControls(); // Make sure create child control is called
}
 
I then update the web part to contain a button and setup a click event that changes the text of the label to Hello World AJAX. Once the assembly compiles I copy it to the SharePoint applications BIN folder and test out the updated web part. At this point, if AJAX is configured properly you should be able to click on the button and have the label updated without causing a post back. If a full page post back happens make sure your master page contains a Script Manager Control, your web.conig contains the necessary AJAX configuration entries, and your web part page contains at least one of the Standard SharePoint List (Tasks, Announcements) web parts. If all this is true then AJAX should work without causing a full page post back.
Now that the AJAX is working we can move on to adding the Infragistics WebGrid to the custom Web Part. To use the WebGrid you need to make sure you have Net Advantage for ASP.NET installed and a reference to the following assemblies:
  • Infragistics2.WebUI.Misc.v8.2
  • Infragistics2.WebUI.Shared.v8.2
  • Infragistics2.WebUI.UltraWebGrid.v8.2.
Make sure you use the Allow Partial Trusted Caller (APTC) version of these assemblies. SharePoint only works with assemblies that allow partial trusted callers.
Now that you have references setup to use the Infragistics WebGrid lets update the web part’s Create Child Controls method to setup the WebGrid (See Code Listing #4).  Inside of the create child control method we create an instance of the WebGrid. Then define the columns to display in the grid and set filtering. We finished out the method by adding a button to load the grid. When the button‘s click events fires we will make an AJAX call and bind the grid to a collection of customers.
A few important things to remember about the code listed below. First make sure you setup the grid’s initializeDataSourceEventHandler this is needed for the grid to properly handled data binding. Second make sure that you add all child controls (Grid and Button) to the update panel’s ContentTemplateContainer control collection.
Code Listing #4: Code to setup a WebGrid to display and filter a list of customers
/// <summary>
/// Layout the web part and its children controls
/// </summary>
protected override void CreateChildControls()
{
base.CreateChildControls();
      this._grid = new UltraWebGrid();
           
this._grid.InitializeDataSource +=
new InitializeDataSourceEventHandler(_grid_InitializeDataSource);
 
      this._grid.DisplayLayout.AutoGenerateColumns = false;
      this._grid.DisplayLayout.CellPaddingDefault = 3;
this._grid.DisplayLayout.FilterOptionsDefault.AllowRowFiltering = RowFiltering.OnClient;
this._grid.DisplayLayout.FilterOptionsDefault.AllString = "(All)";
this._grid.DisplayLayout.FilterOptionsDefault.EmptyString = "(Empty)";
this._grid.DisplayLayout.FilterOptionsDefault.AllowRowFiltering = RowFiltering.OnClient;
 
UltraGridBand dataBand =
this.CreateGridBand("Customers", "Customers", "Id");
this._grid.Bands.Add(dataBand);
      dataBand.Columns.Add(this.CreateGridColumn("Name", "Name"));
dataBand.Columns.Add(this.CreateGridColumn("Activation Date", "FormatedCustomerSince"));
dataBand.Columns.Add(this.CreateGridColumn("Balance", "AccountBalance"));
 
this._hostPanel.ContentTemplateContainer.Controls.Add(_grid);
 
this._hostPanel.ContentTemplateContainer.Controls.Add(new LiteralControl("<BR />"));
 
      Button testAJAX = new Button();
      this._hostPanel.ContentTemplateContainer.Controls.Add(testAJAX);
      testAJAX.Text = "Load Grid";
      testAJAX.Click += new EventHandler(testAJAX_Click);
}
 
void _grid_InitializeDataSource(object sender, UltraGridEventArgs e)
{
}
/// <summary>
/// Button Event to load grid data.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void testAJAX_Click(object sender, EventArgs e)
{
      this._grid.Rows.ColumnFilters.Clear();
      this._grid.DataSource = new SampleDataSource().GetCustomers();
this._grid.DataBind();
}
 
/// <summary>
/// Create grid band
/// </summary>
/// <param name="baseTable">Table Name</param>
/// <param name="key">Data Source Key</param>
/// <param name="dataKey">Data Key</param>
/// <returns></returns>
private UltraGridBand CreateGridBand(string baseTable, string key,   
string dataKey)
{
UltraGridBand gridBand = new UltraGridBand();
      gridBand.BaseTableName = baseTable;
      gridBand.Key = key;
      gridBand.DataKeyField = dataKey;
      return gridBand;
}
 
/// <summary>
/// Create grid column
/// </summary>
/// <param name="caption">Column Caption</param>
/// <param name="dataKey">Column Data Source Key</param>
/// <returns>Grid Column</returns>
private UltraGridColumn CreateGridColumn(string caption, string   
dataKey)
{
UltraGridColumn gridColumn = new UltraGridColumn();
      gridColumn.BaseColumnName = dataKey;
      gridColumn.Header.Caption = caption;
      gridColumn.IsBound = true;
      return gridColumn;
}
 
After you have setup the grid control and the updated the button to load data into the grid. Compile and deploy the updated web part to the BIN folder of my SharePoint application. Make sure to copy the three Net Advantage assemblies you reference to the BIN folder too.
In this example we examined the challenges of getting AJAX to work with SharePoint. We built a custom web part that uses a Microsoft AJAX update panel and includes the necessary code for an update panel to work under SharePoint. The custom web part displays a list of customers in an Infragistics WebGrid and uses the AJAX cabbalists of the grid to filter data. If you like to see a fully functional Line of Business application running under SharePoint check out Pomegranate.