I have noticed quite a few posts/projects out there that focus on using ASP.NET AJAX within custom MOSS web parts. Mike Ammerlaan has a great post on some of the background around AJAX and MOSS
This post describes what my team and I have been using for the past couple of months. It is a simple base class called AjaxBasePart.
Background:
I developed a .NET class in early November that allowed my team to take advantage of the MS AJAX Extensions from custom MOSS web parts. There were a number of problems that I did not resolve on my own though.
For the next month or so I worked on and off with the AJAX Extensions and MOSS product teams and with our powers combined we were able to come up with the AjaxBasePart class that custom MOSS web parts can derive from and fully enables all AJAX Extensions functionality within MOSS.
Very Simple Setup:
There is no need to modify master pages after the AJAX Extensions have been installed and the web.config is setup your web parts will manage the registration of a shared ScriptManager or in the case that one exists use that. If I'm missing anything please notify me in the comments and I will update.
- Download and install ASP.NET 2.0 AJAX Extensions 1.0
- Configure your MOSS web.config to support ASP.NET AJAX 1.0
Mike's post covers most of the configuration you need to support AjaxBasePart within your own MOSS environment. I've listed below a couple of points that you should be aware of when reading his article if you are using the AjaxBasePart- You can skip the "Adding a ScriptManager into a SharePoint MasterPage" section. No modification of the .master page is necessary with AjaxBasePart
- Leave the EnsureUpdatePanelFixups method out of your custom web parts, that is handled by the AjaxBasePart.
- Add the AjaxBasePart class (at the end of this post) to your solution and derive all custom web parts from CapDes.SharePoint.AjaxBasePart instead of Microsoft.SharePoint.WebPartPages.WebPart.
Support:
If you are looking for support I will do my best to assist via the comments on this post. This is in no way supported by Microsoft.
Credits:
The following Microsoft employees assisted me in the development of the AjaxBasePart. Thanks guys!
Bonus:
You will also notice a public RegisterError method on the AjaxBasePart. I find it to be a clean way to show users web part related error messages in a common way.
To use all you need to do is; from your custom web part call the RegisterError message and pass the string that you would like displayed to the user.
base.RegisterError("Some error message.");
Code:
using System;
using System.Xml.Serialization;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Drawing;
using System.Web;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
namespace CapDes.SharePoint
{
/// <summary>
/// AjaxBasePart allows Microsoft ASP.NET AJAX Extensions to work within Microsoft Office SharePoint Server 2007 webparts.
/// </summary>
[XmlRoot(Namespace = "CapDes.SharePoint.AjaxBasePart")]
[CLSCompliant(false)]
public abstract class AjaxBasePart : WebPart
{
private string _ValidationGroupId;
private ValidationSummary _ErrorContainer;
private ScriptManager _AjaxManager;
/// <summary>
/// Exposes the Page's script manager. The value is not set until after OnInit
/// </summary>
[WebPartStorage(Storage.None)]
public ScriptManager AjaxManager
{
get { return _AjaxManager; }
set { _AjaxManager = value; }
}
/// <summary>
/// Oninit fires before page load. Modifications to the page that are necessary to support Ajax are done here.
/// </summary>
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
//get the existing ScriptManager if it exists on the page
_AjaxManager = ScriptManager.GetCurrent(this.Page);
if (_AjaxManager == null)
{
//create new ScriptManager and EnablePartialRendering
_AjaxManager = new ScriptManager();
_AjaxManager.EnablePartialRendering = true;
// Fix problem with postbacks and form actions (DevDiv 55525)
Page.ClientScript.RegisterStartupScript(typeof(AjaxBasePart), this.ID, "_spOriginalFormAction = document.forms[0].action;", true);
//tag:"form" att:"onsubmit" val:"return _spFormOnSubmitWrapper()" blocks async postbacks after the first one
//not calling "_spFormOnSubmitWrapper()" breaks all postbacks
//returning true all the time, somewhat defeats the purpose of the _spFormOnSubmitWrapper() which is to block repetitive postbacks, but it allows MS AJAX Extensions to work properly
//its a hack that hopefully has minimal effect
if (this.Page.Form != null)
{
string formOnSubmitAtt = this.Page.Form.Attributes["onsubmit"];
if (!string.IsNullOrEmpty(formOnSubmitAtt) && formOnSubmitAtt == "return _spFormOnSubmitWrapper();")
{
this.Page.Form.Attributes["onsubmit"] = "_spFormOnSubmitWrapper();";
}
//add the ScriptManager as the first control in the Page.Form
//I don't think this actually matters, but I did it to be consistent with how you are supposed to place the ScriptManager when used declaritevly
this.Page.Form.Controls.AddAt(0, _AjaxManager);
}
}
}
/// <summary>
/// Needs to be called to ensure that the ValidationSummary control is registered on the page. Any child web parts will need to have base.CreateChildControls() at the top of their own CreateChildControls override.
/// </summary>
protected override void CreateChildControls()
{
base.CreateChildControls();
if (!this.Controls.Contains(_ErrorContainer))
{
_ValidationGroupId = Guid.NewGuid().ToString();
_ErrorContainer = new ValidationSummary();
_ErrorContainer.ID = "_ErrorContainer";
_ErrorContainer.ValidationGroup = _ValidationGroupId;
_ErrorContainer.BorderStyle = BorderStyle.Solid;
_ErrorContainer.BorderWidth = Unit.Pixel(3);
_ErrorContainer.BorderColor = Color.Red;
this.Controls.Add(_ErrorContainer);
}
}
/// <summary>
/// Used to provide a common way to display errors to the user of the current web part.
/// </summary>
/// <param name="message">Description of the error that occured.</param>
public void RegisterError(string message)
{
if (this.Controls.Contains(_ErrorContainer))
{
//this way of generating a unique control id is used in some of the OOB web parts
int uniqueCounter;
if (HttpContext.Current.Items["GetUniqueControlId"] != null)
{
uniqueCounter = (int)HttpContext.Current.Items["GetUniqueControlId"];
}
else
{
uniqueCounter = 0;
}
uniqueCounter++;
HttpContext.Current.Items["GetUniqueControlId"] = uniqueCounter;
//create a custom validator to register the current error message with the ValidationSummary control
CustomValidator cv = new CustomValidator();
cv.ID = string.Concat("_Error_", uniqueCounter);
cv.ValidationGroup = _ValidationGroupId;
cv.Display = ValidatorDisplay.None;
cv.IsValid = false;
cv.ErrorMessage = message;
this.Controls.Add(cv);
}
else
{
//if RegisterError is called before the CreateChildControls override in AjaxBasePart then transfer the user to an error page using the SPUtility
SPUtility.TransferToErrorPage("The CreateChildControls function of the AjaxBasePart has not been called. You probably need to add \"base.CreateChildControls()\" to the top of your CreateChildControls override.");
}
}
}
}
