Web service call on ribbon button click in SharePoint 2010

Right, hello everyone!

Today I’ll show you how to make a JavaScript call from a SharePoint Ribbon button to a web service that you created and deployed to SharePoint.
Now, we all know that when defining a custom Ribbon button through CustomAction element we can only declare a URL call or a JavaScript call as a command argument. Sometimes this is quite enough, as you can use SharePoint JavaScript Client Object Model, and that gives you quite some possibilities. However yet, there will be times when this is not enough, as some actions you would like to perform can be achieved only through server-side object model.

This leaves us with two options.

First is described in this MSDN article. Basically, a JavaScript is invoked, that is making a custom postback to the page. Inside the page you are placing your custom web control that listens to specific postback parameter and performs server-side logic. This has two drawbacks, one of which can be eliminated: 1) page actually performs a post back and 2) you get an overhead (extra “if” statement on every load) if your control is placed in the master page. This can be avoided, however, with proper use of SharePoint DelegateControl in master page declaration.

Second option, and this is what I’m doing to discuss today, is calling a web service from the JavaScript. Contrary to first method this one does not cause overhead nor does it cause postback. This can be a turning point for someone deciding which of these methods to use. Let’s begin.

Solution structure plan/overview

Here are the steps needed to make this work:
- Create/define a web service
- Create a definition of CustomAction, describing your Ribbon button
- Create a custom JavaScript file which will store the methods needed to query the web service and embed it inside your page (for this article let’s assume we will be adding a ribbon button to a Display form of a custom list). This can be done by adding a CEWP (Content Editor WebPart) to that page, containing the script inclusion, or by implementing a certain web part architecture, which I have described in one of my previous articles.

Implementation

Here I will introduce critical code parts in Visual Studio 2010 that you must implement in order for this to work.

Creating a web service
Create a new web service file from inside your mapped Layouts folder like shown. web service
The code inside this file should be like this:

1
2
3
4
5
6
7
8
<%@ Assembly Name="MyCustomAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=(enter key token of your assembly here)"%>
<%@ServiceHost
   Debug="true" Language="C#"
   CodeBehind="MyITService.cs"
   Service="MyCustomNamespace.MyITService, MyCustomAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=(enter key token of your assembly here)"
   Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory,
            Microsoft.SharePoint.Client.ServerRuntime, Version=14.0.0.0, Culture=neutral,
            PublicKeyToken=71e9bce111e9429c"
%>

Note, in the Service attribute we are referencing our class called MyITService that will perform server-side logic. This file can be created anywhere in your solution. Here are the contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Web;
using Microsoft.SharePoint;
using System.ServiceModel.Activation;

namespace MyCustomNamespace
{
    [ServiceContract(Namespace = "MyCustomNamespace.MyITService")]
    interface IMyITService
    {
        [WebInvoke(UriTemplate = "/getStatus", Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json)]
        [OperationContract]
        string getStatus(string selectedVal, int locationId, string fullSiteUrl, string relativeWebUrl);
    }

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class MyITService : IMyITService
    {
        public string getStatus(string selectedVal, int locationId, string fullSiteUrl, string relativeWebUrl)
        {
           // Your logic goes here
        }

Obviously, this requires some explanations…
First thing, you must have an interface defined for the methods you would like to call. In the interface-scoped attribute “ServiceContract” you must specify a path to actual class (with namespaces) that will contain an implementation of the interface. Then, each method declaration in the interface must have “OperationContract” attribute, as well as “WebInvoke” attribute. Only thing to notice here is a parameter of “WebInvoke” attribute called “UriTemplate”. It defines the name by which this interface will be called from JavaScript (forward slash is mandatory in declaration, but is not typed when calling).
Inside the implementing class there’s only one thing to remember – to put a class-scoped attribute “AspNetCompatibilityRequirements”. This will ensure SharePoint “understands” the web service.
Now, when you have this sorted out it’s time to create a Ribbon button definition.

Ribbon button definition
Add a new item in your Visual Studio 2010 and select “Empty SharePoint Module” type.

When created, create a button definition inside Elements.xml file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
 Description="MyStatusAction"
 Title="Get Status"
 Id="{E538E8C7-65DA-454E-AD87-4A603B6CC569}"
 Location="CommandUI.Ribbon.DisplayForm"
 RegistrationId="0x0142F521C73B34476F87444C6CD8075C62"
 RegistrationType="ContentType"
 Sequence="0"
 Rights="EditListItems"
 xmlns="http://schemas.microsoft.com/sharepoint/">
    <CommandUIExtension xmlns="http://schemas.microsoft.com/sharepoint/">
      <!-- Define the (UI) button to be used for this custom action -->
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.ListForm.Display.Manage.Controls._children">
          <Button Id="{B511A716-54FF-4EAE-9CBE-EA02B51B626E}"
                 Command="{4E2F5DC0-FE2C-4466-BB2D-3ED0D1917763}"
                 Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png" Image16by16Top="-176" Image16by16Left="-64"
                 Image32by32="/_layouts/$Resources:core,Language;/images/formatmap32x32.png" Image32by32Top="-320" Image32by32Left="-64"
                 Sequence="100"
                 LabelText="Get Status"
                 TemplateAlias="o1" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <!-- Define the action expected on the button click -->
        <CommandUIHandler Command="{4E2F5DC0-FE2C-4466-BB2D-3ED0D1917763}"
                         CommandAction="javascript:invokeGetStatus({ItemId});" />
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>
</Elements>

Now, what this code does is binds this button to a particular content type, with ID of 0x0142F521C73B34476F87444C6CD8075C62. This is the best approach when you are also creating this list from your custom definition, since then you will define a unique content type for this particular list, and reference it here, thus uniquely binding the button only to this list. More information about how to define Ribbon buttons can be found here and here. Also, note that the “Command” attribute value in “Button” element must be the same as “Command” attribute value in the “CustomUIHandler” element. This is how we tie the button to the action that this button performs. In our case the action is (“CommandAction” attribute) calling a certain JavaScript method.

Fine, so far looking good – we have created a web service definition, it’s server side code, and we have created a Ribbon button, that is calling the JavaScript. IMPORTANT to notice that JavaScript method that is called is not the web service method that we defined earlier!!! True, we can call it here directly, if we have enough parameters already. But I have found that this is rarely the case. So, most of the times we need an additional JavaScript file with functions that collect the needed parameters via SharePoint JavaScript Client Object Model. In the next section I’ll describe the contents of that intermediate JavaScript file and method “invokeGetStatus” in particular.

Intermediate JavaScript
This file should be included with the page where our custom Ribbon button resides. To remind, you can add it in the CEWP web part or by implementing this architecture.

Just add the file in your mapped “Layouts” folder.

And, here are the contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var currentUser = null;
var currentUserLogin = null;
var web = null;

var relativeWebUrl = null;
var fullWebUrl = null;
var fullSiteUrl = null;
var itemId = null;

function invokeGetStatus(currItemId) {
    var context = new SP.ClientContext();
    relativeWebUrl = context.get_url();
    fullWebUrl = window.location.protocol + '//' + window.location.host + relativeWebUrl;
    fullSiteUrl = window.location.protocol + '//' + window.location.host + _spPageContextInfo.siteServerRelativeUrl;

    SP.UI.Notify.addNotification("Working...", false);
   
    itemId = currItemId;
    getWebUserData();
}

function getWebUserData() {
    var context = new SP.ClientContext.get_current();
    web = context.get_web();
    currentUser = web.get_currentUser();
    currentUser.retrieve();
    context.load(web);
    context.executeQueryAsync(Function.createDelegate(this, this.onSuccessGetUser), Function.createDelegate(this, this.onFailureMethod));
}

function onSuccessGetUser(sender, args) {
    currentUserLogin = web.get_currentUser().get_loginName();

    // Call the web service code
    $.ajax({
        type: 'POST',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        //url to web service
        url: '/_layouts/MyCustomSolution/WebServices/MyITService.svc/getStatus',
        data: JSON.stringify({ fullSiteUrl: fullSiteUrl, relativeWebUrl: relativeWebUrl, currentIncidentId: itemId, userAccountName: currentUserLogin }),
        success: function (data) {
            // do something with the data returned, if any
        },
        error: function (msgObj, url, status) {
            SP.UI.Notify.addNotification("Error: " + status, false);
        }
    });    
}

function onFailureMethod(sender, args) {
    SP.UI.Notify.addNotification("Failed to retrieve current user, aborting...", false);
}

OK, so basically what “invokeGetStatus” function does is it collects various parameters that I need: web/site URLs and current user account name. When this is done without errors than the actual web service gets called. Whew!~

And here you have it, a working solution! Hope this saves some time to you and see you next time.


2 Responses

  1. If you are the one who’s looking for a new
    racing game to go down town, Dr. Driving is there for you.

  2. Josette says:

    I am really pleased to glance at this webpage posts which consists of plenty oof valuable data, thanks for providing
    such statistics.

Leave a Reply

Using Gravatars in the comments - get your own and be recognized!

XHTML: These are some of the tags you can use: <a href=""> <b> <blockquote> <code> <em> <i> <strike> <strong>