Jaap Vossers' SharePoint Blog

Just another WordPress.com site

Archive for February 2012

Using jQuery to submit forms on remote SharePoint admin pages

with one comment

Imagine you need to develop a piece of functionality to extend SharePoint, but the available APIs do not directly allow you to do this for any of the following reasons:

  1. You are not allowed to deploy any server side code
  2. The server side code you can deploy has limited access to the server object model (e.g. Sandbox Solutions in SharePoint 2010).
  3. The API you need to access is private or internal

Consider the following scenario.

For our SharePoint Online site, we want to implement a Web Part that allows us to save the current site as a WSP in the solution gallery. As it’s SharePoint Online, we can’t deploy Farm Solutions, so we will have to deploy it as a Sandbox Solution. Unfortunately, we have limited access to the Object Model on the server, and there is nothing available in the Client Object Model which we can use to save the current site as a template.

Now, if there is an existing administration page that does what we want to do (in our case /_layouts/savetmpl.aspx does what we want to do), then technically all we need to do is a submit an HTTP POST request to that page with the right HTTP headers and form parameters and the server will happily process the request, as it has no way of telling whether the request was triggered by a user submitting the form, or by something else.

Welcome to the world of stateless protocols.

So we need to find out what the request should look like so that we can use jQuery to build it and issue it as an AJAX request from our own code.

image

Let’s open up Fiddler. With Fiddler open, in the browser we press the button that submits the form on /_layouts/savetmpl.aspx,. After that, we can inspect the form parameters of the HTTP POST request that the browser then makes. We are not really interested in the HTTP headers as the browser will take care of passing any headers related to the current browser session (including the authorization header) to the server when we issue an AJAX request.

image

We have three main categories of form parameters.

The first category is made up of parameters that directly map to user input fields.

  • ctl00$PlaceHolderMain$ctl00$ctl00$TxtSaveAsTemplateName
  • ctl00$PlaceHolderMain$ctl01$ctl00$TxtSaveAsTemplateTitle
  • ctl00$PlaceHolderMain$ctl01$ctl01$TxtSaveAsTemplateDescription
  • ctl00$PlaceHolderMain$ctl03$CbSaveData

The second category consists of a set of hidden fields which are used by ASP.NET & SharePoint to do its postback magic, including validation of the form post.

  • __REQUESTDIGEST
  • __VIEWSTATE
  • __EVENTVALIDATION

The third category is “the rest”. This is stuff we are not really interested in, but we still need to send it to the server.

  • MSOWebPartPage_PostbackSource
  • MSOTlPn_SelectedWpId
  • MSOTlPn_View
  • MSOTlPn_ShowSettings
  • MSOGallery_SelectedLibrary
  • MSOGallery_FilterString
  • MSOTlPn_Button
  • MSOSPWebPartManager_DisplayModeName
  • MSOSPWebPartManager_ExitingDesignMode
  • MSOWebPartPage_Shared
  • MSOLayout_LayoutChanges
  • MSOLayout_InDesignMode
  • MSOSPWebPartManager_OldDisplayModeName
  • MSOSPWebPartManager_StartWebPartEditingName
  • MSOSPWebPartManager_EndWebPartEditing
  • _maintainWorkspaceScrollPosition
  • __spText1
  • __spText2

It’s easy to determine what we want to submit as values for the first category. We either capture these values in our custom UI, or we have some logic in our code that determines these values for us.

The second category is a bit more difficult. Essentially, the server is expecting us to post back these values, which were provided by the server and rendered on the page as hidden input fields at the time of requesting the page which has the form on it. This means we need to make an initial GET request using jQuery so we can extract the values from the form, before we can submit them in our post.

The third category is easy, as we can copy the values from the request we captured with fiddler.

The script

Following the instruction above, we can write a bit of javascript like this to allow us to submit forms on “remote” pages.

function CreateWSP(callback) {

    var sitePrefix = "/";

    if (_spPageContextInfo.siteServerRelativeUrl != "/") {
        sitePrefix = _spPageContextInfo.siteServerRelativeUrl + "/";
    }

    var url = sitePrefix + "_layouts/savetmpl.aspx";

    $.get(url, function (data, textStatus, XMLHttpRequest) {

        var ctx = $(data);

        var rd = ctx.find("[name='__REQUESTDIGEST']").val();
        var vs = ctx.find("[name='__VIEWSTATE']").val();
        var ev = ctx.find("[name='__EVENTVALIDATION']").val();

        var postParams = {
            "MSOWebPartPage_PostbackSource": "",
            "MSOTlPn_SelectedWpId": "",
            "MSOTlPn_View": "0",
            "MSOTlPn_ShowSettings": "False",
            "MSOGallery_SelectedLibrary": "",
            "MSOGallery_FilterString": "",
            "MSOTlPn_Button": "none",
            "MSOSPWebPartManager_DisplayModeName": "Browse",
            "MSOSPWebPartManager_ExitingDesignMode": "false",
            "__EVENTTARGET": "ctl00$PlaceHolderMain$ctl02$RptControls$BtnSaveAsTemplate",
            "__EVENTARGUMENT": "",
            "MSOWebPartPage_Shared": "",
            "MSOLayout_LayoutChanges": "",
            "MSOLayout_InDesignMode": "",
            "MSOSPWebPartManager_OldDisplayModeName": "Browse",
            "MSOSPWebPartManager_StartWebPartEditingName": "false",
            "MSOSPWebPartManager_EndWebPartEditing": "false",
            "_maintainWorkspaceScrollPosition": "0",
            "__REQUESTDIGEST": rd,
            "__VIEWSTATE": vs,
            "__SCROLLPOSITIONX": "0",
            "__SCROLLPOSITIONY": "0",
            "__EVENTVALIDATION": ev,
            "ctl00$PlaceHolderMain$ctl00$ctl00$TxtSaveAsTemplateName": "VossersTeamSite.wsp",
            "ctl00$PlaceHolderMain$ctl01$ctl00$TxtSaveAsTemplateTitle": "Vossers Team Site",
            "ctl00$PlaceHolderMain$ctl01$ctl01$TxtSaveAsTemplateDescription": "Vossers Team Site Template",
            "ctl00$PlaceHolderMain$ctl03$CbSaveData": "on",
            "__spText1": "",
            "__spText2": ""
        };

        var options = {
            url: url,
            type: "POST",
            data: postParams,
            success: function (data, textStatus, XMLHttpRequest) {

                callback();

            }
        };

        $.ajax(options);

    }, "html");
}

Things to consider

When the page you are posting to is modified, for example by a SharePoint update, then it’s possible that your script breaks due to changes in form parameter names. This makes this technique a bit fragile. For this reason I recommend that you only consider using this technique once you have confirmed that it’s not possible to achieve what you want by using public APIs.

Written by jvossers

February 4, 2012 at 3:22 pm