AJAX Style Web Parts in SharePoint the Easy Way

With most things in life there’s the right way and the easy way of doing them. And that is true for implementing AJAX style Web Parts in SharePoint, too. So if you just want to get things done read on.

The idea is to simply augment your Web Part with a Web Service and then call it’s methods from JavaScript. This demo Web Part will just display a timestamp and a URL when you press a button–you’ll have to find more interesting applications yourself. 😉

Creating the Project

One shouldn’t have to explain this. But to promote the new Visual Web Part there is no project template in Visual Studio 2010 for the traditional Web Part.

The only benefit you get from Visual Web Parts is being able to manipulate their UI in a designer. Not very convincing as it makes your project more complex and Web Part UIs tend to be pretty simple. Of course this is a general issue and not limited to AJAX style Web Parts. And if you used SmartParts you will probably prefer Visual Web Parts.

Anyway my solution is:

  • create an Empty SharePoint Project
  • add your Web Part as a new item

Here is a screenshot of the final solution:

Solution structure

Creating the Web Service

The easy way to implement AJAX style Web Parts is to simply add the Web Service class to the Web Part–no additional DLLs needed. It could be even more easy but as Microsoft wants to educate you to use WCF services instead of the traditional Web Services there is no template for them when working on a Web Part project.

So you have to manually add a class and convert it like this. The highlighted lines make GetInfo() callable from JavaScript and the EnableSession attribute would give you access to the session state:

using System;
using System.Web.Script.Services; // Needs reference to System.Web.Extensions.dll
using System.Web.Services; // Needs reference to System.Web.Services.dll
using Microsoft.SharePoint;

namespace AjaxWP {

    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ScriptService]
    class AjaxWS : System.Web.Services.WebService {

        //[WebMethod(EnableSession = true)]
        [WebMethod]
        [ScriptMethod]
        public string GetInfo() {
            //throw new Exception("Just a test ...");
            string s = DateTime.Now.ToString("s")
                + ", " + SPContext.Current.Web.Url;
            return s;
        }
    }
}

Now create a one-line .asmx file like this for example in your Layouts subfolder. The class attribute contains the strong name of your assembly as Web Part assemblies are usually deployed to the GAC.

<%@ WebService Language="C#" Class="AjaxWP.AjaxWS, AjaxWP, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d76004e99c897b9f" %>

Creating the Web Part

And finally the calling Web Part’s code. While it consists of nearly 100 lines it’s actually just a few simple steps:

  • OnInit() makes the Web Service callable by JavaScript on the page by letting the ScriptManager create a JavaScript proxy.
  • OnPreRender() generates the JavaScript. Calling Web services from JavaScript is always asynchronous and follows the same pattern:
    • Create a method that calls your Web Service’s method providing the required parameters and two callback methods.
    • Create a callback method for successful calls. It’s parameter will contain the Web Service’s response.
    • Create a callback method for error cases. It’s parameter will contain an error object. The example code has additional info.
  • Call your JavaScript method. In this case the button’s OnClientClick event is responsible for this.
using System;
using System.ComponentModel;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

namespace AjaxWP.AjaxWP {

    [ToolboxItemAttribute(false)]
    public class AjaxWP : WebPart {

        private Panel _infoPanel;

        protected override void CreateChildControls() {
            base.CreateChildControls();
            Button getInfoButton = new Button();
            Controls.Add(getInfoButton);
            getInfoButton.ID = "getInfoButton";
            // "return false" prevents postback
            getInfoButton.OnClientClick = "javascript:getInfo(); return false;";
            getInfoButton.Text = "Get Info";
            Panel infoPanel = new Panel();
            Controls.Add(infoPanel);
            _infoPanel = infoPanel;
            infoPanel.ID = "infoPanel";
        } // CreateChildControls()

        private void CreateJavaScript() {
            StringBuilder sb;
            string script;
            if (Page.ClientScript.IsClientScriptBlockRegistered(
                this.GetType(), this.ToString() + "ClientScript") == false) {
                sb = new StringBuilder();
                sb.Append("<script type='text/javascript'>");

                #region getInfo() function
                sb.Append("\r\n");
                sb.Append("\r\nfunction getInfo()");
                sb.Append("\r\n{");
                //sb.Append("\r\n debugger;");
                sb.Append("\r\n AjaxWP.AjaxWS.GetInfo(");
                sb.Append("\r\n  onGetInfoSuccess, onGetInfoFailure");
                sb.Append("\r\n );");
                sb.Append("\r\n}");
                #endregion getInfo()function

                #region onGetInfoFailure()function
                sb.Append("\r\n");
                sb.Append("\r\nfunction onGetInfoFailure(error)");
                sb.Append("\r\n{");
                ///sb.Append("\r\n debugger;");
                sb.Append("\r\n var stackTrace = error.get_stackTrace();");
                // If web.config <customErrors mode="On" /> this will always be
                // "There was an error processing the request.":
                sb.Append("\r\n var message = error.get_message();");
                //sb.Append("\r\n var statusCode = error.get_statusCode();");
                //sb.Append("\r\n var exceptionType = error.get_exceptionType();");
                //sb.Append("\r\n var timedout = error.get_timedOut();");
                sb.Append("\r\n alert(message);");
                sb.Append("\r\n}");
                #endregion onGetInfoFailure()function

                #region onGetInfoSuccess() function
                sb.Append("\r\n");
                sb.Append("\r\nfunction onGetInfoSuccess(result)");
                sb.Append("\r\n{");
                //sb.Append("\r\n debugger;");
                sb.Append("\r\n var infoPanel = document.getElementById('"
                    + _infoPanel.ClientID + "');");
                sb.Append("\r\n infoPanel.innerHTML = result;");
                sb.Append("\r\n}");
                #endregion onGetInfoSuccess()function

                sb.Append("</script>");
                script = sb.ToString();
                Page.ClientScript.RegisterClientScriptBlock(
                    this.GetType(), this.ToString() + "ClientScript", script);
            }
        } // CreateJavaScript()

        protected override void OnInit(EventArgs e) {
            base.OnInit(e);
            // Make Web service accessible by JavaScript
            ServiceReference sr = new ServiceReference();
            sr.Path = "_layouts/AjaxWP/AjaxWS.asmx";
            ScriptManager sm = ScriptManager.GetCurrent(Page);
            sm.Services.Add(sr);
        } // OnInit()

        protected override void OnPreRender(EventArgs e) {
            base.OnPreRender(e);
            // Create JavaScript in OnPreRender as ClientIDs are determined now
            CreateJavaScript();
        } // OnPreRender()

    }
}

That’s it. Visual Studio 2010 will take care of deploying everything to the correct location and adding the required SafeControl element to web.config. Just make shure you are Site Collection administrator of the site you specified as target site, add the Web Part to the page, and press the button.

While trying this out put a breakpoint in CreateChildControls(). It will not be hit when you press the button–no ASP.NET postback involved! Had you used an UpdatePanel instead of typing in all this JavaScript there would be an ASP.NET postback causing for example the View State to be sent around. See the MSDN article from the external references section for details and for information about how to use JSON in your Web Service.

Another noteworthy point is the value you get from SPContext.Current.Web.Url–this will always be the root Site Collection’s URL. If your Web Service’s method needs the correct URL simply pass it as a parameter.

And a bonus tip for everybody who read this to the end: as double-clicking a Web Service code file in Visual Studio generates an “Error loading workflow” you should open it by selecting “View Code” from the context menu.

External References

UpdatePanel Tips and Tricks

Advertisements
This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

6 Responses to AJAX Style Web Parts in SharePoint the Easy Way

  1. Matthias says:

    … da fehlt noch der kleine Hinweis, dass man erst einmal kompilieren muss, per Reflector den Strong Name rauskriegt und erst dann die asmx erzeugen kann 🙂 Oder gibt es einen anderen Weg?

  2. Alan Cantor says:

    I’m getting a Microsoft Jscript runtime errror: ‘AjaxWP’ is undefined.

  3. Pingback: Update Assembly References in SharePoint Related .asmx Files Automatically | Dieter Bremes's Blog

  4. stevocodes says:

    It works well mate. Thanks for sharing this.

  5. Rafik says:

    Thanks a lot for this article, It works very well!
    For those who have the Jscript runtime errror: ‘AjaxWP’ is undefined, just be sure that you put the correct PublicKeyToken in the asmx file (AjaxWS.asmx) :

    to get YOUR PublicKeyToken just issue this command in a Visual Studio command prompt :
    sn –T AjaxWP.dll

  6. vblain says:

    how would you pass in a value if needed??

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s