SharePoint Dragons

Nikander & Margriet on SharePoint

Uploading and activating sandbox solutions via CSOM/JSOM

In this post we’re discussing how to upload and activate a sandbox solution via CSOM/JSOM. First, we’ll do it using C#. That’s a little bit easier and we’ve seen several examples discussing that (such as this one: http://blogs.msdn.com/b/frank_marasco/archive/2014/08/10/upload-and-activate-sandbox-solutions-using-csom.aspx , probably the first one written about this topic and without it, we couldn’t have written this post). Then, we’ll do it using JSOM which has some additional challenges. We didn’t find any resources discussing that, so there should be value in that.

First off, create a new sub site using the template team site. Then, save that sub site via Site Settings > Save site as template and go to the Solution Gallery and download it somewhere on your local file system. That way, you’ve created a site template that can be used to upload and activate later on. When finished, remove that solution from the Solution Gallery (otherwise, there’s not much point in uploading it again programmatically, now is there?).

In the C# version, we’re basically doing this:

  1. Establish the correct client context
  2. Upload the solution directly to the Solution gallery.
  3. Read the site template (*.wsp) from the local file system.
  4. Create a new FileCreationInformation object representing the site template that will be uploaded.
  5. Upload the solution somewhere. Please note: somewhere can be the solution gallery, but it can also be any other document library. This is because the DesignPackageInfo class accepts a relative URL of an asset located in SharePoint, takes it, and installs it in the Solution Gallery.
  6. Use the DesignPackageInfo class to install and activate the sandbox solution.

The code to do that looks like this:

using Microsoft.SharePoint.Client;

using Microsoft.SharePoint.Client.Publishing;

using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Web;

namespace SolutionUploader

{

class Program

{

static void Main(string[] args)

{

try

{

ClientContext context = new ClientContext(“http://%5Burl of site collection] “);

Web web = context.Web;

context.Load(web);

var lib = web.Lists.GetByTitle(“[destination lib]”);

context.Load(lib);

var filePath = @”D:\TestTemplateFolder\TestTemplate.wsp”;

var file = new FileStream(filePath, FileMode.Open);

var fileCI = new FileCreationInformation()

{

ContentStream = file,

Url = “TestSiteTemplate-v1.2.wsp”,

Overwrite = true

};

var uploadedFile = lib.RootFolder.Files.Add(fileCI);

context.Load(uploadedFile);

context.ExecuteQuery();

var wsp = new DesignPackageInfo()

{

// Guid can be empty and is autofilled,

// but specifying it explicitly makes it easier if you want to remove it

// later.

PackageGuid = new Guid(“[GUID]”),

//PackageGuid = Guid.Empty,

MajorVersion = 1,

MinorVersion = 0,

PackageName = “[package name]”

};

string filerelativeurl = “[site relative path of *.wsp] “;

DesignPackage.Install(context, context.Site, wsp, filerelativeurl);

context.ExecuteQuery();

}

catch (Exception ex)

{

Console.WriteLine(ex.Message);

}

}

}

}

You may notice that after installation, the solution is always named like so:

[package name]v[major version].[minor version].wsp

Let’s move on to the JSOM example. In other to write it, there where bits and pieces we’ve copied related to the binary encoding and uploading of files and we’ve tried to give credit where possible, but we’re pretty sure we’re missing an acknowledgement. Rest assured, this wasn’t because of any bad intentions.

We found doing the same thing in JSOM is harder, especially when you’re trying to do it before creating a POC in C#. Part of the reason for this is that the JSOM DesignPackageInfo documentation isn’t really helpful at the time of writing, but it’s here: https://msdn.microsoft.com/en-us/library/office/jj954421.aspx in case you want to take a look.

This example involves an HTML page that uploads the site template and activates it in the Solution Gallery. It goes something like this:

  1. You need a reference to the sp.publishing.js library, because it contains the DesignPackageInfo class.
  2. Include a file control to allow end users to upload the site template.
  3. Use a client-side FileReader object to read the binaries.
  4. Upload the file to a SharePoint library and give it a title. Failing to give the file a title may result in situations where the eventual package name is [a guid consisting of 0’s]v.[major].[minor]. We’ve seen this happen on several occasions and it seems to be a problem that doesn’t happen in C#/CSOM.
  5. Use the DesignPackageInfo class to install and activate the solution.

<!DOCTYPE html>

<html lang=”en” xmlns=”http://www.w3.org/1999/xhtml”&gt;

<head>

<meta charset=”utf-8″ />

<title></title>

<script type=”text/javascript” src=”/_layouts/15/sp.publishing.js”></script>

<script type=”text/javascript” src=”[reference to jquery library]”></script>

</head>

<body>

<h1>Upload en Activeer Solution</h1>

<input id=”inputFile” type=”file” />

<input id=”uploadDocumentButton” type=”Button” value=”Upload Document”/> <p/>

<script>

$(“#uploadDocumentButton”).click(function () {

if (document.getElementById(“inputFile”).files.length === 0) {

alert(“Select a file!”);

return;

}

CreateFile();

});

var file;

var fileCreateInfo;

function CreateFile() {

// Ensure the HTML5 FileReader API is supported

if (window.FileReader) {

input = document.getElementById(“inputFile”);

if (input) {

file = input.files[0];

fr = new FileReader();

fr.onload = receivedBinary;

fr.readAsDataURL(file);

}

}

else {

alert(“The HTML5 FileSystem APIs are not fully supported in this browser.”);

}

}

// Callback function for onload event of FileReader

function receivedBinary() {

var clientContext = SP.ClientContext.get_current();

this.oWebsite = clientContext.get_web();

clientContext.load(this.oWebsite);

var listTitle = “[title of destination library for *.wsp”;

var list = this.oWebsite.get_lists().getByTitle(listTitle);

fileCreateInfo = new SP.FileCreationInformation();

fileCreateInfo.set_url(file.name);

fileCreateInfo.set_overwrite(true);

fileCreateInfo.set_content(new SP.Base64EncodedByteArray());

// Read the binary contents of the base 64 data URL into a Uint8Array

// Append the contents of this array to the SP.FileCreationInformation

var arr = convertDataURIToBinary(this.result);

for (var i = 0; i < arr.length; ++i) {

fileCreateInfo.get_content().append(arr[i]);

}

// Upload the file to the root folder of the document library

this.newFile = list.get_rootFolder().get_files().add(fileCreateInfo);

clientContext.load(this.newFile);

var item = this.newFile.get_listItemAllFields();

item.set_item(“Title”, “[title value]”);

item.update();

clientContext.executeQueryAsync(onUploadSuccess, onUploadFailure);

}

function onUploadSuccess() {

// File successfully uploaded

var clientContext = SP.ClientContext.get_current();

var wsp = new SP.Publishing.DesignPackageInfo();

wsp.set_packageName(“[package name]”);

//wsp.set_packageGuid(“{GUID makes deleting easier}”);

wsp.set_majorVersion(3);

wsp.set_minorVersion(0);

var filerelativeurl = “/[site relative url to *.wsp] “;

SP.Publishing.DesignPackage.install(clientContext,

clientContext.get_site(),

wsp,

filerelativeurl);

clientContext.executeQueryAsync(onActivateSuccess, onActivateFailure);

}

function onUploadFailure() {

// Error occurred

console.log(“Request failed: ” + arguments[1].get_message());

}

function onActivateSuccess() {

alert(“Solution successfully activated!”);

}

function onActivateFailure() {

alert(“Solution activation failed: ” + arguments[1].get_message());

}

// Utility function to remove base64 URL prefix and store base64-encoded string in a

// Uint8Array

// Courtesy: https://gist.github.com/borismus/1032746

function convertDataURIToBinary(dataURI) {

var BASE64_MARKER = ‘;base64,’;

var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;

var base64 = dataURI.substring(base64Index);

var raw = window.atob(base64);

var rawLength = raw.length;

var array = new Uint8Array(new ArrayBuffer(rawLength));

for (i = 0; i < rawLength; i++) {

array[i] = raw.charCodeAt(i);

}

return array;

}

</script>

</body>

</html>

And that’s all folks!

Advertisements

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

%d bloggers like this: