Nintex Custom Actions – Error Handling


A while ago now, I wrote a blog post about Nintex Custom Actions. This was written for SharePoint 2010. I have recently worked on a project where I’ve got a chance to use Nintex again. My own blog post came in very handy, and I have updated it so it now explains what you need to do for SharePoint 2013. To be honest there isn’t much difference. Mainly location of Nintex dll’s, and remembering to put /_layouts/15 when pointing to layout files.

Some of the out of the box Custom actions has an error handling section when configuring.

There are 3 parts to error handling.

  1. Capture Errors – If there is an error should the custom action capture it (Yes/No)
  2. Store error occurrence in – This stores if an error occurred or not. (Yes/No)
  3. Store error text in – This stores the actual error message returned if there is one (Single line of text)

This blog post will show you which files you will need to update and what code is requried, to make your custom action have Error handling section. Please ensure you know how to create a Custom Action from reading my previous blog post, as I won’t be going over old ground. I have added custom error handling to my previous project “Retrieve from Property Bag.” If the property doesn’t exist in the property bag then it will throw an error, this will allow my workflow to take a different path.

ReadFromPropertyBagActivity.cs

First we will need to add public static DependencyProperties for the 3 fields within Error Handling, add these below the previous Dependency properties.

#region Error Handling
//Determines if error handling has been switched on for this activity by the workflow designer.
public static DependencyProperty CaptureErrorsProperty = DependencyProperty.Register("CaptureErrors", typeof(bool), typeof(ReadFromPropertyBagActivity));

//Stores whether or not an error occured.
public static DependencyProperty ErrorOccurredOutputProperty = DependencyProperty.Register("ErrorOccurredOutput", typeof(bool), typeof(ReadFromPropertyBagActivity));

//Stores the details of an error if one occurred.
public static DependencyProperty ErrorMessageOutputProperty = DependencyProperty.Register("ErrorMessageOutput", typeof(string), typeof(ReadFromPropertyBagActivity));
#endregion

Then each of these Dependency Properties will have its own public property.

#region Error Dependency Properties
public bool CaptureErrors
{
    set { base.SetValue(CaptureErrorsProperty, value); }
    get { return (bool)base.GetValue(CaptureErrorsProperty); }
}

public bool ErrorOccurredOutput
{
    set { base.SetValue(ErrorOccurredOutputProperty, value); }
    get { return (bool)base.GetValue(ErrorOccurredOutputProperty); }
}

public string ErrorMessageOutput
{
    set { base.SetValue(ErrorMessageOutputProperty, value); }
    get { return (string)base.GetValue(ErrorMessageOutputProperty); }
}
#endregion

Still within the ReadFromPropertyBagActivity.cs file, the Execute method will needs to be updated. Below I have put the original Execute method and highlighted the extra code required for Error Handling.

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
    //Standard Nintex code to obtain context.
    ActivityActivationReference.IsAllowed(this, __Context.Web);
    NWWorkflowContext ctx = NWWorkflowContext.GetContext(
       this.__Context,
       new Guid(this.__ListId),
       this.__ListItem.Id,
       this.WorkflowInstanceId,
       this);
    base.LogProgressStart(ctx);
 
   //Get the property value.
   string resolvedProperty = ctx.AddContextDataToString(this.Property);

   var result = "";

   //Using the context get the property if it exists.
   if (ctx.Web.AllProperties.ContainsKey(resolvedProperty))
   {
       result = ctx.Web.AllProperties[resolvedProperty].ToString();

       if(CaptureErrors)
            ErrorOccurredOutput = false;

        //store the result.
        this.ResultOutput = result;
    }
    else
    {
        if (CaptureErrors)
        {
            ErrorOccurredOutput = true;
            ErrorMessageOutput = String.Format("Unable to find property bag key:{0}", resolvedProperty);
        }
    }

    //End Execution.
    base.LogProgressEnd(ctx, executionContext);
    return ActivityExecutionStatus.Closed;
}

What is happening here is that if the web site contains the property, then if the user sets the custom action to capture errors then set the ErrorOccurredOutput to false. If the property doesn’t exist, and the user has set the custom action to capture errors then will need to set the ErrorOccurredOutput to true and set the error message in the ErrorMessageOutput property.

ReadFromPropertyBagAdapter.cs

In this file there are only two methods that require code change to include the Error Handing. These methods are AddActivityToWorkflow and GetConfig.

Within the AddActivityToWorkflow, after setting the activity.SetBinding but before the ActivityFlags add the following piece of code.

if (context.Config.ErrorHandling != null)
{
    context.Config.ErrorHandling.AssignTo(activity,
        ReadFromPropertyBagActivity.CaptureErrorsProperty,
        ReadFromPropertyBagActivity.ErrorOccurredOutputProperty,
        ReadFromPropertyBagActivity.ErrorMessageOutputProperty,
        context);
}

Within the GetConfig method, add the error handling code just before we return the NWActionConfig.

config.ErrorHandling = ErrorHandling.BuildFrom(context.Activity,
                ReadFromPropertyBagActivity.CaptureErrorsProperty,
                ReadFromPropertyBagActivity.ErrorOccurredOutputProperty,
                ReadFromPropertyBagActivity.ErrorMessageOutputProperty,
                context.Variables);

ReadFromPropertyBagDialog.cs

This is the final piece of the puzzle. This is the configuration screen when you edit the custom action. There is two javaScript functions (TPARetrieveConfig and TPAWriteConfig) that needs updating, then lastly we will need to update the HTML to display, that uses a registered control.

Add the register control.

<%@ Register TagPrefix="Nintex" TagName="ErrorHandlingConfig" Src="~/_layouts/15/NintexWorkflow/ErrorHandlingConfig.ascx" %>

Nintex has already written the JavaScript to make the error handling work, therefore we just need to call it.

TPARetrieveConfig – This loads and displays previous saved values.

function TPARetrieveConfig() {
            setRTEValue('<%=propertyProperty.ClientID%>', configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='Property']/PrimitiveValue/@Value").text);
            document.getElementById('<%=resultOutput.ClientID%>').value = configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='ResultOutput']/Variable/@Name").text;
 
            LoadErrorHandlingSection();
}

TPAWriteConfig – This saves properties back to the custom action.

function TPAWriteConfig() {
            configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='Property']/PrimitiveValue/@Value").text = getRTEValue('<%=propertyProperty.ClientID%>');

            var resultOuputCtrl = document.getElementById('<%=resultOutput.ClientID%>');

            if (resultOuputCtrl.value.length > 0) {
                configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='ResultOutput']/Variable/@Name").text = resultOuputCtrl.value;
            }

            SaveErrorHandlingSection();

            return true;
        }

Finally we need to add the HTML to the page. This is within the content place holder ID PlaceHolderMain. This is added just before the closing </TemplateRowsArea>.

<Nintex:ConfigurationPropertySection runat="server" Id="MainControls1">
              <TemplateRowsArea>
               <Nintex:ConfigurationProperty runat="server" FieldTitle="Property Bag Property" RequiredField="True">
                   <TemplateControlArea>
                        <Nintex:SingleLineInput clearFieldOnInsert="true" filter="number" runat="server" id="propertyProperty"></Nintex:SingleLineInput>
                  </TemplateControlArea>
                </Nintex:ConfigurationProperty>

                <Nintex:ConfigurationProperty runat="server" FieldTitle="Result Output" RequiredField="False">
                  <TemplateControlArea>
                    <Nintex:VariableSelector id="resultOutput" runat="server" IncludeTextVars="True"></Nintex:VariableSelector>
                  </TemplateControlArea>
                </Nintex:ConfigurationProperty>

                <Nintex:ErrorHandlingConfig runat="server" id="errorHandlingConfig1"></Nintex:ErrorHandlingConfig>
              </TemplateRowsArea>
            </Nintex:ConfigurationPropertySection>

  <Nintex:DialogBody runat="server" id="DialogBody">
  </Nintex:DialogBody>

After I have deployed this custom action to SharePoint (All explained how in previous Nintex blog) my custom action when configured now shows the Error Handling section. I have already configured my custom action in the below screenshot.

You will need to create two workflow variables. I have created ErrorOccurred (Yes/No) and ErrorMessage (Single line of text).

Then in my workflow I have a “Set a condition” workflow action, that is configured to equal to true if ErrorOccured equals Yes. If no error occurs then I set my field value as I did previously before I include error handling, however if there is an error I log this error in the history list.

When running the workflow I have looked for a property bag key called NotThere. This has caused the workflow to error when retrieving the property from the property bag.

And below is the error in the history list.

You can download my updated SharePoint 2013 version of my ReadFromPropertyBag custom action with Error Handling from my OneDrive.

Using REST to upload a File to Document library (Including different site collection)


The code I’m going to show you today will show you how you can upload a file to a document library using REST. This solution could be used within a Sandbox application.

To upload files, it is much better to use REST than the SharePoint API. The main reason is that SharePoint JSOM/CSOM can only upload files a maximum size of 1.5MB, where REST is good up to 2GB.

My example here has a simple GUI, it is an Upload form, which allows the user to pick from a list the site they wish to upload the file to, a file upload box, and an Upload button.

HTML Code.

<SharePoint:FormDigest runat="server"/>
<div>
   <label id="lblInfo" class="label" for="ProposalSiteUrl">Please enter your Site Collection URL or leave blank to upload file to this Site Collection document library:</label>
</div>
<div>
   <select id="ProposalSiteUrl" class="dropdown" title="Select a site">
                    <option value="http://intranet.cannonfodder.local">This Site</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite1">Team Site 1</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite2">Team Site 2</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite3">Team Site 3</option>
      </select>
</div>
<div class="div">
        <input id="AttachmentUploadField" type="file" size="98"/>
        <input id="AttachAttachmentButton" class="button" onclick="CannonFodder.Utilities.SaveAttachment();" type="button" value="Upload"/>
</div>
<div class="div">
         <label id="lblResult" class="result"></label>
</div>

The user can select from the dropdown a site to send the file to. Select the file using the file picker and click Upload. You can see in this example I have hard coded the option values for the dropdown list. Any results pass or failed will be displayed to the user using the lblResult label.

JavaScript

This code will require JQuery for grabbing the element values from the page and to perform JQuery Ajax request. Ensure that your page has JQuery loaded. In this example I grabbing JQuery from Google.

<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Personally, JavaScript isn’t my preferred programming language and I’m trying to write better JavaScript. I’m ensuring I’m not making it part of the Global namespace, and using boxing methods etc. Therefore in this example here you should find this a good practice writing JavaScript.
First I’m going to namespace my JavaScript Methods.

var CannonFodder = CannonFodder || {};
CannonFodder.Utilities = function(){
var currentDlg;
 //Code to write in here.
 return {
      SaveAttachment:saveAttachment
 };
}();

The above piece of code, will create a namespace called CannonFodder.Utilities with one public method called SaveAttachment which calls the private method saveAttachement. We haven’t written saveAttachment yet. We will write a bunch of functions inside the section where I have commented ‘//Code to write in here‘. The first one will be saveAttachement. (Notice small ‘s’ for save) This method will be the only method in this section that can be called from outside of CannonFodder.Utilities. The variable currentDlg
is a global variable for the scope of CannonFodder.Utilities. It is so I can display a waiting dialog screen to the user. This isn’t necessary for this code to work, it just gives the demo a nicer user experience.

The saveAttachment method

var saveAttachment = function(){
    //Validate if the upload file has a file selected.
    if(!validated()){
            jQuery('#lblResult').text("Please choose a file!");
            return;
    }
    
    //Dialog variables
    var dlgTitle;
    var dlgMsg;
    var dlgWidth;
    var dlgHeight;
    dlgTitle = "Uploading Your File...";
    dlgMsg = "<br />Please wait whilst your file is being uploaded<br /><br />Please do not close your browser window.";
    dlgHeight = 300;
    dlgWidth = 500;
        
    //Create Dialog box.
   if (currentDlg == null) {
           currentDlg = SP.UI.ModalDialog.showWaitScreenWithNoClose(dlgTitle, dlgMsg, dlgHeight, dlgWidth);
   }

   //Get File        
   var file = jQuery("#AttachmentUploadField")[0].files[0];

   //Upload the file
   uploadFile(file);
};

The code above doesn’t do much, it first calls a validation method (yet to write) to check if the user has selected a file to upload. If not, it updates the lblresult text to inform the user they must upload a file. If validation passes, we set up a dialog box to inform the user that the upload is taking place (it will take a long time for a 2GB file). Next we display the dialog to the user, grab the file and passes it to the uploadFile method.

The validated method

var validated = function(){
   var file = jQuery("#AttachmentUploadField")[0].files[0];
   if(file == null){        
          return false;
  }
  else{
    return true;
  }
};

The validated method grabs the Upload control and checks if there is a file available. If it is there then the function returns true, else it returns false.

The uploadFile method

var uploadFile = function (file) {
    proposalSiteUrl = jQuery('#ProposalSiteUrl option:selected').val();        
        
    if (proposalSiteUrl == _spPageContextInfo.webAbsoluteUrl) {
        uploadFileLocal(file);
    }
    else{
        uploadFileCrossSite(file, proposalSiteUrl);
    }
};

Our upload file still isn’t the actual code that uploads the file to the given URL. Here I’m checking if the site I’m uploading to is the same Site Collection I’m running my upload page from or not. If it is, then I can do a very simple upload (uploadFIleLocal). If I need to upload to a different Site Collection, then I will be required to perform a cross site call (uploadFileCrossSite).

The uploadFileLocal method

To explain this, I am going to show this method in bits and explain each section.

var uploadFileLocal = function (file) {
      var digest = jQuery("#__REQUESTDIGEST").val();
      var webUrl = _spPageContextInfo.webAbsoluteUrl;
      var libraryName = "Documents";

      var reader = new FileReader();
      var arrayBuffer;

First we need to set all our variables. The digest request is important, it is a client side token to validate post backs to SharePoint to prevent attacks where the user might be tricked into posting data back to the server. It is unique to a user and a site. It is only valid for a limited time. The first line gets the current request digest from the page. This digest is required in our ajax post request when we upload the file. The other variables are pretty standard, web url, the library name to upload to, a file reader and array buffer.

        reader.onload = function (e) {
            arrayBuffer = reader.result;

            url = webUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/files/add(url=@TargetFileName,overwrite='true')?" +
               "@TargetLibrary='" + libraryName + "'" +
               "&@TargetFileName='" + file.name + "'";

            //JQuery Ajax call here

       };
       reader.readAsArrayBuffer(file);
  };

The next part of the uploadFileLocal function that continues straight after var arrayBuffer. Our reader needs to read in the file, and on the onload event, we need to prepare our REST url to past into the JQuery ajax request. (The JQuery Ajax call is shown in the next piece of code). The reader.onload function doesn’t fire until after reader.readAsArrayBuffer(file) is called. The REST url uses two parameters @TargetLibrary and @TargetFileName, the actual file data is put into arraryBuffer and used in the JQuery Ajax call.

        
jQuery.ajax({
        url: url,
       type: "POST",
       data: arrayBuffer,
    headers: {
            "Accept": "application/json; odata=verbose",
            "X-RequestDigest": digest
            },    
contentType: "application/json;odata=verbose",
processData: false,
    success: function () {
            jQuery('#lblResult').text("Successfully uploaded file locally.");
            if (currentDlg != null) {
                        currentDlg.close();
            }
         },
     error: function (arr, error) {
               jQuery('#lblResult').text("Error uploading file locally.");
               if (currentDlg != null) {
                      currentDlg.close();
               }
          }
});

Insert the above code in the previous snippet where the comment //JQuery Ajax call here is. This is a typical Ajax call using JQuery. http://api.jquery.com/jquery.ajax/ We pass in our REST url, state that it’s a POST request, and set the data to our file arrayBuffer. Our headers show that the call is a json call, and here we pass in the request digest we obtained in the very first variable of the uploadfileLocal function. The contentType and processData are standard for this type of call. Lastly I have created a success and error callbacks, with inline functions. The functions are similar, both updating the lblResult text with a success or fail message, and then close the dialog that is on show to the user. In the error function we could use the error variable to obtain the actual error message and display it to the user.

This concludes uploading a file to the same site. However if you try to use the same code to upload to a different site it will fail. The reason is all to do with the digest request. As I stated earlier, this is a client side token that is unique to the user and site. Therefore when you try to access a different site to what you are on, you will be passing an invalid digest request. You first need to obtain the digest request for the other site before you can upload/post data to it. Please note: The user will need contribute access to the site to be able to upload a file.

The uploadFileCrossSite method

var uploadFileCrossSite = function (file, webUrl) {
        url = webUrl + "/_api/contextinfo";
        jQuery.ajax({
            url: url,
            type: "POST",
            headers: {
                "Accept": "application/json; odata=verbose"
            },
            contentType: "application/json;odata=verbose",
            success: function (data) {
               var digest = data.d.GetContextWebInformation.FormDigestValue;
                var libraryName = "Documents";

                var reader = new FileReader();
                var arrayBuffer;

                reader.onload = function (e) {
                    arrayBuffer = reader.result;

                    url = webUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/files/add(url=@TargetFileName,overwrite='true')?" +
                       "@TargetLibrary='" + libraryName + "'" +
                       "&@TargetFileName='" + file.name + "'";

                    jQuery.ajax({
                        url: url,
                        type: "POST",
                        data: arrayBuffer,
                        headers: {
                            "Accept": "application/json; odata=verbose",
                            "X-RequestDigest": digest
                        },
                        contentType: "application/json;odata=verbose",
                        processData: false,
                        success: function () {
                        jQuery('#lblResult').text("Successfully uploaded file to different site.");
                          if (currentDlg != null) {
                              currentDlg.close();
                              }
                        },
                        error: function () {
                          jQuery('#lblResult').text("Error uploading file to different site.");
                          if (currentDlg != null) {
                              currentDlg.close();
                              }

                        }
                    });
                };

                reader.readAsArrayBuffer(file);

            },
       error: function () {
            jQuery('#lblResult').text("Error accessing other site.");
               if (currentDlg != null) {
                   currentDlg.close();
              }
          }
      });
 };

The uploadFileCrossSite code I have shown as whole, mainly because I have explained most of it in the uploadFileLocal function above. The lines I’ve highlighted 1-11 is a JQuery Ajax call into the target site. By requesting the context info, we can obtain the request digest of the target site in the success handler, data.d.GetContextWebInformation.FormDigestValue. We can then use this value to pass in as the digest to upload the file to the target site. The code to upload the file now is identical as before, obtaining the reader, calling the onload event, then on the success of the Ajax request we update the lblResult with a success message and close the dialog displayed.

The final error:function() call lines 53-59 highlighted at the bottom of the code is the error callback if there is an error contacting the target site for the request digest.

I can now test the form, I’ve selected my second team site, and selected a document DemoWordDoc3.docx to upload. I’ve then clicked the Upload button.

The file dialog appears in the centre of the screen so the user knows something is happening.

When the file has been uploaded, the user gets the message that it has successfully uploaded.

If I check out my Team Site 2, you can see that the DemoWordDoc3 is in the Documents library.

You can obtain my upload page from my OneDrive. To get into your SharePoint site, it is easiest just to copy it in using SharePoint Designer.

http://1drv.ms/1tcIDIQ

Upgrading Sandbox Solutions in SharePoint


In SharePoint 2013 for online solutions there is only two choices for deploying custom code now, using apps, or Sandbox solutions. Now, Microsoft has stated that Sandbox solutions have been deprecated in 2013 version of SharePoint, but this is only for assemblies running on the Sandboxed Code Service. Therefore JavaScript, WebTemplates, elements and Manifest files are still valid to use in your sandbox solutions.

For a while now I have been building Sandbox solutions in SharePoint 2013, luckily, most projects I have worked on are Day 1 implementations. Therefore, I’ve never really needed to worry about upgrades. Just deactivate, retract, remove, add, deploy, activate until I get it right. Problem is after day 1 you no longer have the luxury to remove and re-add again. There is content on the site, a list with data in. You remove it you will have some unhappy customers.

I’m going to build a sample solution and upgrade it twice. This example doesn’t actually do anything useful apart from show you the correct way to upgrade. You can download the visual studio project and the wsp’s of each version from my One Drive. (Please note I’m certain this will work without issue on SharePoint Online, however, I wrote and tested this code only on an on premise server.)

Building Version 1

In this initial project build, I’m going to be adding the following:

  • Custom Actions – Adds JQuery and a JavaScript file.
  • Column – Add a choice column
  • Content Type – Custom Content type, based on Document Library with my additional choice column.
  • Assets – Logo file, my custom JavaScript file, jQuery library

The solution looks like the following:

I will put the XML/code for each custom file below, I will not include jquery-1.11.1.min.js code.

  • CA_JQuery (Elements file) – Custom actions that add Jquery link and my own custom JavaScript.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction ScriptSrc="~sitecollection/assets/jquery-1.11.1.min.js"
                Location="ScriptLink"
                Sequence="100">
  </CustomAction>
  <CustomAction ScriptSrc="~sitecollection/assets/CFUpgrade.js"
                Location="ScriptLink"
                Sequence="101">
  </CustomAction>
</Elements>
  • CO_Classification (Elements file) –Choice column, with a default option.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Field
       ID="{815f2492-94b7-43a9-b679-2b3cbaaa35bf}"
       Name="CO_Classification"
       DisplayName="Classification"
       Type="Choice"
       Required="FALSE"
       Group="Cannonfodder">
    <Default>Standard</Default>
    <CHOICES>
      <CHOICE>Standard</CHOICE>
      <CHOICE>Important</CHOICE>
      <CHOICE>Secure</CHOICE>
    </CHOICES>
  </Field>
</Elements>
  • CT_Document (Elements file)
    Content Type based on Document Library containing Classification Column.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Document (0x0101) -->
  <ContentType ID="0x0101004A6AC4D7159D498DBDF17D3C10D609A0" Name="CannonFodder Document" Group="CannonFodder" Description="A Document type just for Cannonfodder" Inherits="TRUE">
    <FieldRefs>
      <FieldRef ID="{815f2492-94b7-43a9-b679-2b3cbaaa35bf}" DisplayName="Classification" Required="FALSE" Name="CO_Classification" />
    </FieldRefs>
  </ContentType>
</Elements>
  • CFUpgrade.js Custom Javascript file which changes the site icon to display the Cannonfodder.jpg
jQuery(document).ready(function () {
    jQuery('.ms-siteicon-img').attr("src", _spPageContextInfo.webAbsoluteUrl + "/assets/Cannonfodder.jpg");
});
  • Assets (Elements file) – Manifest file which tells SharePoint to deploy file to a folder called Assets. Ensure all files have attribute ReplaceContent=”TRUE”.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="Assets">
    <File Path="Assets\CFUpgrade.js" Url="Assets/CFUpgrade.js" ReplaceContent="TRUE" />
    <File Path="Assets\Cannonfodder.jpg" Url="Assets/Cannonfodder.jpg" ReplaceContent="TRUE" />
    <File Path="Assets\jquery-1.11.1.min.js" Url="Assets/jquery-1.11.1.min.js" ReplaceContent="TRUE" />
  </Module>
</Elements>

Add all the SharePoint Items to the feature, and ensure the feature is a scoped for Site. Publish it as a WSP. Then the solution can be upload it to SharePoint and activate. It is not recommended using Visual Studio to do your deployments on your dev environments. The main reason is because Visual Studio will retract and remove the solution if it currently exists in SharePoint, which means you are never upgrading the solution. (Not really an issue for Version 1, but best get use to it.) By right clicking the project and selecting Publish… from the context menu, you can then package up the project to a location on the hard drive.

Personally I will rename this file solutionName_v1.0.0.0.wsp. Until I know the code is right, this will always be version 1.0.0.0. Also note that the Solution number doesn’t have to correspond to the feature version, mainly because most solutions will have multiple features in them. I’m keeping them the same so that it logically make sense to you while following this blog post.

Steps to upload a Sandbox Solution

I always try and write my blogs as if I’m teaching someone for the first time especially for the parts that are related to the topic, so here are the steps to add the solutions.

  • Go to the site (I’ve created a clean Team Site, Site collection).
  • Site Actions (cog) -> Site Settings
  • Under Web Designer Galleries, Click Solutions


  • Click Upload Solution on the ribbon bar.
  • On the Add a Document dialog browse for the wsp file created, Click OK.
  • On the ribbon bar of the new dialog, Click Activate.


  • The solution has now been installed to that Site Collection. If the feature was configured to Activate On Default the column, content type, asset files will already deployed and working on the site, otherwise the feature will need activating.


How does the site currently look.


From looking at the homepage, we can see the SharePoint Icon has changed to my CannonFodder picture. Which indicates that my custom action, JQuery and custom Javascript file has loaded and working. Can also double check by looking at the Internet Explorer Dev tools (F12) and check that the scripts have loaded.


If we look at the content types Site Actions > Site Settings > Web Designer Galleries > Site content types, the Content Type CannonFodder Document is there.


And the content type contains our Classification Column.

To ensure that this content type works, I’m going to add my content type to the document library and upload a document and set the classification column.

  • Go to the document library.
  • Select Library on the ribbon bar and then Library Settings
  • In Settings, under General Settings Click Advanced settings
  • At the top of the page, set Allow management of content types to Yes. Scroll down and Click
    OK.
  • Under Content Types, click Add from existing site content types.
  • Find and Add CannonFodder Document and then Click OK.
  • Remove Document Content type, by click on Document and then Delete this content type.
  • Under Views click All Documents. Add the Classification column to the view. Click OK.
  • Add document by clicking (+) new document. Browse to find a Word Document.
  • On the Metadata dialog, there will be Name, Title and Classification available. Click Save.

Building Version 2

Now that I have deployed my code, a document has been uploaded using my content type. The site is being used. Removing the solution and installing a new version of the solution could cause damage to the site, or worse, not even install, leaving the site in a state of flux. Not quite a working version 1, not quite a working version 2.

Changes for Version 2:

  • A new custom column – hyperlink URL field called Source
  • Update Content Type to include new custom column
  • A new custom action to insert CSS into the page.
  • Update the Cannonfodder.jpg logo to a better picture.
  • A new CSS file.
  • Additional JavaScript code added to the CFUpgrade.js file.

     

The solution now looks like the following:

I will put the XML/code for each custom added or been upgraded

  • CO_Source(Elements file) –URL Column. (NEW)
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Field
       ID="{b952f5fd-8490-49f6-8b02-cdf4cfc03a99}"
       Name="CO_Source"
       DisplayName="Source"
       Type="URL"
       Format="Hyperlink"
       Required="FALSE"
       Group="Cannonfodder">
  </Field>
</Elements>
  • CT_Document (Elements file) – Adds Source column (Upgrade)
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Document (0x0101) -->
  <ContentType ID="0x0101004A6AC4D7159D498DBDF17D3C10D609A0" Name="CannonFodder Document" Group="CannonFodder" Description="A Document type just for Cannonfodder" Inherits="TRUE">
    <FieldRefs>
      <FieldRef ID="{815f2492-94b7-43a9-b679-2b3cbaaa35bf}" DisplayName="Classification" Required="FALSE" Name="CO_Classification" />
      <FieldRef ID="{b952f5fd-8490-49f6-8b02-cdf4cfc03a99}" DisplayName="Source" Required="FALSE" Name="CO_Source" Format="Hyperlink" />
    </FieldRefs>
  </ContentType>
</Elements>
  • CA_Css (Elements file) – Adds CSS to the page (New)
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="ReferCssSiteCol" Location="ScriptLink"
                ScriptBlock="document.write('&lt;link rel=&quot;stylesheet&quot; After=&quot;Corev15.css&quot;  type=&quot;text/css&quot; href=&quot;~sitecollection/assets/CFStyle.css&quot;&gt;&lt;/' + 'link&gt;');"
                Sequence="202" />
</Elements>
  • Cannonfodder.jpgreplaced with new file with same name (Upgrade)
  • CFStyle.cssA css file that won’t win any styling awards (New)
#contentBox{
    background-color: aquamarine;
}
  • CFUpgrade.js A change adding additional JavaScript (Upgrade)
jQuery(document).ready(function () {
    jQuery('.ms-siteicon-img').attr("src", _spPageContextInfo.webAbsoluteUrl + "/assets/Cannonfodder.jpg");
    jQuery('#suiteBarLeft').css("background-Color", "darkred");
});
  • Assets (Elements file) – Add the extra files (Upgrade)
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="Assets">
    <File Path="Assets\CFUpgrade.js" Url="Assets/CFUpgrade.js" ReplaceContent="TRUE" />
    <File Path="Assets\jquery-1.11.1.min.js" Url="Assets/jquery-1.11.1.min.js" ReplaceContent="TRUE" />
    <File Path="Assets\Cannonfodder.jpg" Url="Assets/Cannonfodder.jpg" ReplaceContent="TRUE" />
    <File Path="Assets\CFStyle.css" Url="Assets/CFStyle.css" ReplaceContent="True"/>
 </Module>
</Elements>

Add all the extra SharePoint Items to the site feature.

This seems like all I need to do, publish the WSP again, rename it with the correct version and upload the new version to the solution file. It’s not. However some people think that is it, and before I show you what step they are missing let me go through the upgrade process they go through.

Note:
You have to rename the file differently to how the last wsp was named. It doesn’t have to be version numbers, but it has to be different, as you cannot have two solutions with the same name in a solution library. SharePoint will know that the wsp being uploaded is an Upgrade of the previous solution because the Solution ID’s will be the same. Why this solution library couldn’t use versioning is anyone guess.

Steps not to correctly upgrade a Sandbox Solution

  • After you have published the wsp file, rename it (For this example, mine is name CFUpgradeSolution_v2.0.0.0withoutupgrade)
  • Go to the site.
  • Site Actions (cog) -> Site Settings
  • Under Web Designer Galleries, Click
    Solutions
  • Click Upload Solution on the ribbon bar.
  • On the Add a Document dialog browse for the wsp file created, Click
    OK.
  • On the ribbon bar of the new dialog, Click Upgrade. Note how SharePoint recognises that this solution is to be upgraded and not a new solution, this is due to the solution ID remaining the same in both named wsp’s


  • After the upgrade, navigate to the home page.

So what has happened? Nothing.

At this point, some people then go to the Feature that’s got the changes in them, deactivate the feature and reactivate it again. Well that does seem to work, The CSS has been applied, my logo has been replaced with a much cooler image, the suite bar is now a dark red instead of Microsoft Blue, and my new site column is there.

However, my content type hasn’t been updated.

So we can see that some upgrade worked, and some didn’t.

I’m going to reset my site (create team site, apply CFUpgrade_v1.0.0.0.wsp, and follow my instructions to apply content type to document library)

Updating the Feature Manifest steps to correctly upgrade a Sandbox Solution

The step that is missing from the previous step is updating the Feature Manifest file, and giving your feature a version number.

  • Select the feature in the solution and open up the properties window (F4)
  • At the bottom of the properties window (if properties are in alphabetically order) you will find Version. When nothing is entered into this property, SharePoint will read the version as 0.0.0.0. As this is the second version of the feature I’m putting version 2.0.0.0
    Now we need to update the features manifest file to indicate what needs to be upgraded.
  • Click on the feature to open it, then Click on the Manifest
    tab.
  • At the bottom, on the Manifest tab, Click the plus button of Edit Options.

    This area allows you to add more XML to your Feature. It is here we will add an <UpgradeActions>
    http://msdn.microsoft.com/en-us/library/office/ff595308(v=office.15).aspx. The XML to add here will be merged into the current feature manifest XML by Visual Studio. The feature manifest XML will not appear in the preview box above the edit options if the XML is malformed. The current Feature Manifest XML has all our new files within it, which is required if this solution is added for the first time to a site, however we need to apply an <UpgradeActions> with the given <VersionRange>.

    The <VersionRange> indicates to SharePoint what previous version to apply this update to. If the feature currently installed within SharePoint doesn’t fall between these version range numbers, then it will not be upgraded.

    Any <ElementManifest> files listed will be re-applied or added if it is their first time being run in the feature. This is the reason why we added all our files with the attribute ReplaceContent=”TRUE”, because it will re-apply the files over the top of the original files.

    The <AddContentTypeField> allows the addition of extra columns to existing content types, and if stated, will push down the column to any Lists that are currently using that content type.

    In the Edit Option area between <Feature xmlns=”http://schemas.microsoft.com/sharepoint/”></Feature&gt; add the following.

<UpgradeActions>
   <VersionRange BeginVersion="0.0.0.0" EndVersion="1.9.9.9">
     <ApplyElementManifests>
        <ElementManifest Location="CO_Source\Elements.xml"/>
        <ElementManifest Location="CA_Css\Elements.xml"/>
        <ElementManifest Location="Assets\Elements.xml"/>
     </ApplyElementManifests>
     <AddContentTypeField ContentTypeId="0x0101004A6AC4D7159D498DBDF17D3C10D609A0" FieldId="{b952f5fd-8490-49f6-8b02-cdf4cfc03a99}" PushDown="TRUE"/>
   </VersionRange>
 </UpgradeActions>

The above states that any feature versions starting with 0.0.0.0 (which was our first deployment) to any feature version ending with 1.9.9.9 that we should do the following.

  • Re-Apply/Add CO_Source\Elements.xml
  • Re-Apply/Add CA_CSS\Elements.xml
  • Re-Apply/Add Assets\Elements.xml
  • Add the field with the given ID, to the given content type and push down to all lists using that content type. (CO_Source column add to CannonFodder Document)

Now you can go through the steps of Publishing the Solution, renaming to v2.0.0.0 and upgrading the feature.

  • After you have published the wsp file, rename it (For this example, mine is name CFUpgradeSolution_v2.0.0.0)
  • Go to the site.
  • Site Actions (cog) -> Site Settings
  • Under Web Designer Galleries, Click
    Solutions
  • Click Upload Solution on the ribbon bar.
  • On the Add a Document dialog browse for the wsp file created, Click
    OK.
  • On the ribbon bar of the new dialog, Click Upgrade. Note how SharePoint recognises that this solution is to be upgraded and not a new solution, this is due to the solution ID remaining the same in both named wsp’s

After you have clicked Upgrade, and SharePoint has processed your solution, you should notice that the upgrade has taken place without the need to deactivate and re-activate the feature.

If you navigate to your home page, and view the properties of the document you added to the document library earlier, you will notice you now have the extra column Source.

Which means the CannonFodder Document content type has also been updated correctly.

Building Version 3

My last and final version in this demo is to show how to build a feature that will work for a brand new instance directly to version 3. That it will work and upgrade a version 1 to version 3, and that it will upgrade a version 2 to version 3.

Changes for Version 3:

  • A new List Definition that uses the CannonFodder Document content type.
  • A new web feature with a List Instance.
  • Change the JQuery file so that the Suite Bar is bright Red.

The solution now looks like the following:

The code for the CFUpgrade JavaScript file is below.

  • CFUpgrade.js A change adding additional JavaScript (Upgrade)
jQuery(document).ready(function () {
    jQuery('.ms-siteicon-img').attr("src", _spPageContextInfo.webAbsoluteUrl + "/assets/Cannonfodder.jpg");
    jQuery('#suiteBarLeft').css("background-Color", "red");
});

To create the List Definition and Instance

  • Right Click on the solution > Add > New Item
  • Select List and name it. (I’ve named my CF_Document). Click Add.
  • Give a display name, and Create a customizable list template and a list instance of it: and select Document Library

  • Click Next > , then Finish
  • On the GUI version of the List Definition, Click
    Content Types button.
  • Click where it says Click here to add a content type
    and add CannonFodder Document.
  • Highlight CannonFodder Document and then Click Set as Default button. Click OK.
  • Now the columns Classification and Source should be appearing in the Column tabs.

  • Click on the Views tab and Add Classification and Source as Selected columns to All Documents view.
  • Open the Elements.xml file of the List Definition and change the Type to 101. (I have found since doing this to document libraries, the ribbon, buttons and text reflects how Microsoft Document library works).
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <ListTemplate
        Name="CF_Document"
        Type="101"
        BaseType="1"
        OnQuickLaunch="TRUE"
        SecurityBits="11"
        Sequence="110"
        DisplayName="Cannonfodder Documents"
        Description="A list definition for Cannonfodder Document Content Types"
        Image="/_layouts/15/images/itdl.png"
        DocumentTemplate="121"/>
</Elements>
  • Open the Elements.xml file of the List Instance and change the TemplateType to 101. Also get the ID of the Site – CFUpgrade feature, and add this to the List Instance Elements file. This tells the elements file where to find the Definition file.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <ListInstance Title="Cannonfodder Document Library"
                OnQuickLaunch="TRUE"
                TemplateType="101"
                Url="Lists/CannonfodderDocuments"
                FeatureId="87a3dfff-41d2-48fc-b919-a576103b7df9"
                Description="My List Instance">
</ListInstance>
</Elements>
  • Lastly don’t forget to add the List Definition to the Site Feature.
  • Create a new Feature, by Right Clicking
    Features and Add Feature. Set this feature to Web and add the List Instance to it.

Note:The reason why I have added the list instance to another feature was mainly because I couldn’t get the List Instance to create itself if the feature was being upgraded from Version 1 to Version 3. However, if I was upgrading from Version 2 to Version 3, the list instance appeared fine.

Updating the Feature Manifest steps

  • Select the Site feature in the solution and open up the properties window (F4)
  • At the bottom of the properties window (if properties are in alphabetically order) you will find Version. As this is the third version of the feature I’m putting version 3.0.0.0

    Now we need to update the features manifest file to indicate what needs to be upgraded.
  • Double Click on the feature to open it, then Click on the Manifest tabs
  • At the bottom, on the Manifest tab, Click the plus button of Edit Options.
    Now we need to ensure that any Version 1 of the feature is upgraded to have all the new files/content types etc of Version 2. Therefore the previous UpgradeAction we will leave. An additional <VersionRange> will be added to our manifest file to perform upgrade from Version 2 to Version 3.
  • In the Edit Option area between <Feature xmlns=”http://schemas.microsoft.com/sharepoint/”></Feature&gt; but after the last </VersionRange> add the following.
    <VersionRange BeginVersion="1.9.9.9" EndVersion="2.9.9.9">
    <ApplyElementManifests>
    <ElementManifest Location="Assets\Elements.xml"/>
     <ElementManifest Location="CF_Document\Elements.xml"/>
     </ApplyElementManifests>
    </VersionRange>
    

The above states that any feature versions starting with 1.9.9.9 (which was our second deployment) to any feature version ending with 2.9.9.9 that we should do the following.

  • Re-Apply/Add Assets\Elements.xml
  • Add CF_Document\Elements.xml (our list definition)

Now you can go through the steps of Publishing the Solution, renaming to v3.0.0.0 and upgrading the feature.

  • After you have published the wsp file, rename it (For this example, mine is name CFUpgradeSolution_v3.0.0.0)
  • Go to the site.
  • Site Actions (cog) > Site Settings
  • Under Web Designer Galleries, Click
    Solutions
  • Click Upload Solution on the ribbon bar.
  • On the Add a Document dialog browse for the wsp file created, Click
    OK.
  • On the ribbon bar of the new dialog, Click Upgrade. Note how SharePoint recognises that this solution is to be upgraded and not a new solution, this is due to the solution ID remaining the same in both named wsp’s

     

  • After you have clicked Upgrade, and SharePoint has processed your solution, you should notice that the upgrade has taken place without the need to deactivate and re-activate the feature as expected. The suite bar is now a bright red.

  • Site Actions (cog) > Site Settings
  • Under Site Actions, Click Manage Site Features
  • Click Activate on CF Upgrade Web Feature

In the quick link navigation on the left hand side, you should see Cannonfodder Document Library. Click on this link.

We can upload new document files here, and our view has the two new columns ready for us.

  • Click on Site Contents
  • Click on add an app
  • There is a new app called Cannonfodder Documents, which you can create a list of our list definition as many times you like.

Jumping between versions.

With the code that has been implemented above, there would be no problem creating a new site and installing version 3. Or upgrading a site with Version 1 to Version 3. Or upgrading a site with Version 2 to Version 3. Unfortunately, in this blog with screen shots, I couldn’t prove to you that it does work. I could just show previous screen shots and say it works. However the code is available to you on my OneDrive, with each version in separate folders so that you can load each one up in turn inside Visual Studio and publish and test out this yourself.

Get the user claim token string in powershell from Windows domain/name


To grab the User Claim token equivalent of their Windows Domain/Name in powershell you just need to have the following Get-SPClaim function within your script.

Add-PSSnapin Microsoft.SharePoint.PowerShell
$m = [Microsoft.SharePoint.Administration.Claims.SPClaimProviderManager]::Local

Function Get-SPClaim{
param([string]$user)
$claim = New-SPClaimsPrincipal -identity $user -IdentityType "WindowsSamAccountName"
return $m.EncodeClaim($claim)
}

$UserClaim = Get-SPClaim("cannonfodder\administrator")
Write-Host $UserClaim

The above code will display the following on the screen.

i:0#.w|cannonfodder\administrator

The document could not be created. The required application may not be installed properly, or the template for this document library cannot be opened.


Working within my VM this morning, and just testing out basic functionality of SharePoint on my demo site. I went to do a very simple task of creating a new document. I received the following pop up.

It took me a moment, but I realised I didn’t have any Office products installed on my VM, however I did have Office Web Apps. I thought I should still be able to create a new document. It turns out that if you disable an Internet Add-On this message goes away. The Internet Add-On is called SharePoint OpenDocuments Class, (which sounds like it needs to be enabled for it to work!)

As soon as I disabled it, without any browser restart, I was able to create a new document.