Wednesday, December 3, 2014

Adding a new version in version collection of Document set using SharePoint 2013 CSOM & JSOM

Hi,
I had requirement to add new version to document set using SharePoint 2013  CSOM (Client side object Model) & JSOM (Javacript side object Model). CSOM & JSOM doesn't have any such API to add a version to Document Set directly. Also, I have to achieve this functionaliy in Sharepoint hosted App (for Sharepoint Online).
Below is the server side Code that I have to achieve using CSOM & JSOM.
web.AllowUnsafeUpdates = true;
listItem.Update();
DocumentSet documentSet = DocumentSet.GetDocumentSet(listItem.Folder);
documentSet.VersionCollection.Add(true, currentUser.Name);
web.AllowUnsafeUpdates = false;

Solution :- 
With absence of any proper APIs in CSOM & JSOM, a possible workaround was that monitor the POST request using Fiddler when clicking the "Capture Version" button in the ribbon on Document Set welcome page, then Create a custom POST request to imitate the Original "Capture Version" action.
Therefore, I have achieved this functionality by creating custom POST request to imitate the Original "Capture Version" Action. On click of "Capture Version" button in the ribbon it creates a Post Request on "/_layouts/15/CreateDocSetVersion.aspx" Page along with ListId & ItemId (i.e. Document Set Item of whom version need to be captured) in Query String Parameter "List={listguidid}&ID=docsetitemid".
 Also, It passes "CreateComments={docsetversioncomments}" in the post request body to set document set version comments.
Reference :- https://social.msdn.microsoft.com/Forums/office/en-US/2b3d11d0-05ee-4e53-91bc-66362a0751ef/adding-a-new-version-in-version-collection-of-document-set-using-sharepoint-2013-jsom?forum=sharepointdevelopment
CSOM
Using CSOM API by creating custom POST request to Capture Document Set Version. 
var CreateDocSetVersionUrl = SiteURL + "/_layouts/15/CreateDocSetVersion.aspx?List={" + spListID + "}&ID=" + spListItemID;

System.Net.CookieContainer cookieContainer = new System.Net.CookieContainer();
// create Web Request using client context
HttpWebRequest request = clientContext.WebRequestExecutorFactory.CreateWebRequestExecutor(clientContext, CreateDocSetVersionUrl).WebRequest;

if (clientContext.Credentials != null)
{
 // Get Authentication Cookie
 SecureString passWord = new SecureString();
 foreach (char c in UserPassword.ToCharArray()) passWord.AppendChar(c);

 var credentials = new Microsoft.SharePoint.Client.SharePointOnlineCredentials(UserName, passWord);
 var authCookieValue = credentials.GetAuthenticationCookie(new Uri(CreateDocSetVersionUrl));

 // Create fed auth Cookie
 System.Net.Cookie fedAuth = new Cookie();
 fedAuth.Name = "FedAuth";
 fedAuth.Value = authCookieValue.TrimStart(new char[] { 'S', 'P', 'O', 'I', 'D', 'C', 'R', 'L', '=' });
 fedAuth.Path = "/";
 fedAuth.Secure = true;
 fedAuth.HttpOnly = true;
 fedAuth.Domain = new Uri(clientContext.Url).Host;

 // Hookup authentication cookie to request
 cookieContainer.Add(fedAuth);

 request.CookieContainer = cookieContainer;
}
else
{
 // No specific authentication required
 request.UseDefaultCredentials = true;
}

request.ContentLength = 0;
WebResponse response = request.GetResponse();


// decode response
string strResponse;
Stream stream = response.GetResponseStream();
if (!string.IsNullOrEmpty(response.Headers["Content-Encoding"]))
{
 if (response.Headers["Content-Encoding"].ToLower().Contains("gzip"))
 {
  stream = new System.IO.Compression.GZipStream(stream, System.IO.Compression.CompressionMode.Decompress);
 }
 else if (response.Headers["Content-Encoding"].ToLower().Contains("deflate"))
 {
  stream = new System.IO.Compression.DeflateStream(stream, System.IO.Compression.CompressionMode.Decompress);
 }
}

// get response string
System.IO.StreamReader sr = new System.IO.StreamReader(stream);

strResponse = sr.ReadToEnd();

sr.Close();
sr.Dispose();

stream.Close();

// Look for inputs and add them to the dictionary for postback values
var inputs = new List<KeyValuePair<string, string>>();

string patInput = @"<input.+?\/??>";
string patName = @"name=\""(.+?)\""";
string patValue = @"value=\""(.+?)\""";

// Instantiate the regular expression object.
Regex r = new Regex(patInput, RegexOptions.IgnoreCase);
Regex rName = new Regex(patName, RegexOptions.IgnoreCase);
Regex rValue = new Regex(patValue, RegexOptions.IgnoreCase);

// Match the regular expression pattern against a text string.
Match m = r.Match(strResponse);
while (m.Success)
{
 string name = string.Empty;
 string value = string.Empty;
 if (rName.IsMatch(m.Value))
 {
  name = rName.Match(m.Value).Groups[1].Value;
 }

 if (rValue.IsMatch(m.Value))
 {
  value = rValue.Match(m.Value).Groups[1].Value;
 }

 if (string.IsNullOrEmpty(name))
 {
  m = m.NextMatch();
  continue;
 }

 var dict = new KeyValuePair<string, string>(name, value);
 inputs.Add(dict);

 m = m.NextMatch();
}


response.Close();
response.Dispose();

// Format inputs as postback data string
string strPost = "";
string strComments = "Doc set version comments";
bool IsCommentsExist = false;
foreach (KeyValuePair<string, string> inputKey in inputs)
{
 if (!string.IsNullOrEmpty(inputKey.Key) && inputKey.Key.EndsWith("CreateComments"))
 {
  IsCommentsExist = true;
  strPost += System.Uri.EscapeDataString(inputKey.Key) + "=" + System.Uri.EscapeDataString(strComments) + "&";
 }
 else if (!string.IsNullOrEmpty(inputKey.Key))
 {
  strPost += System.Uri.EscapeDataString(inputKey.Key) + "=" + System.Uri.EscapeDataString(inputKey.Value) + "&";
 }
}

if (!IsCommentsExist)
{
 // Set Document Set Version Comments
 strPost += System.Uri.EscapeDataString("CreateComments") + "=" + System.Uri.EscapeDataString(strComments) + "&";
}

strPost = strPost.TrimEnd(new char[] { '&' });

byte[] postData = System.Text.Encoding.UTF8.GetBytes(strPost);

// Build postback request
HttpWebRequest activateRequest = clientContext.WebRequestExecutorFactory.CreateWebRequestExecutor(clientContext, CreateDocSetVersionUrl).WebRequest;
activateRequest.Method = "POST";
activateRequest.Accept = "text/html, application/xhtml+xml, */*";
if (clientContext.Credentials != null)
{
 activateRequest.CookieContainer = cookieContainer;
}
else
{
 // No specific authentication required
 activateRequest.UseDefaultCredentials = true;
}
activateRequest.ContentType = "application/x-www-form-urlencoded";
activateRequest.ContentLength = postData.Length;
activateRequest.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
activateRequest.Headers["Cache-Control"] = "no-cache";
activateRequest.Headers["Accept-Encoding"] = "gzip, deflate";
activateRequest.Headers["Accept-Language"] = "en-US";

// Add postback data to the request stream
stream = activateRequest.GetRequestStream();
stream.Write(postData, 0, postData.Length);
stream.Close();
stream.Dispose();

// Perform the postback
response = activateRequest.GetResponse();
response.Close();
response.Dispose();

Reference :- https://github.com/janikvonrotz/PowerShell-PowerUp/blob/master/functions/SharePoint%20Online/Switch-SPOEnableDisableSolution.ps1

JSOM
In JSOM, I have to achieve this on App Web Page of Sharepoint Hosted App. I have created one page "DocSet.aspx" to create custom post request to add document set version. This page takes "SPHostUrl=hosturl&SPAppWebUrl=appweburl&ItemID=itemid&ListID=listid&Comments=docsetversioncomments" following parameter from query string & create custom POST request to add Document set version.
DocSet.aspx
<%-- The following 4 lines are ASP.NET directives needed when using SharePoint components --%>
<%@ Page Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" MasterPageFile="~masterurl/default.master" Language="C#" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>


<%-- The markup and script in the following Content element will be placed in the <head> of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
 <script type="text/javascript" src="../JS/jquery.js" ></script>
    <script type="text/javascript" src="/_layouts/15/MicrosoftAjax.js"></script>
    <SharePoint:ScriptLink ID="ScriptLink5" name="sp.js" runat="server" LoadAfterUI="true" Localizable="false" />
    <SharePoint:ScriptLink ID="ScriptLink6" name="sp.runtime.js" runat="server" LoadAfterUI="true" Localizable="false" />
    <SharePoint:ScriptLink ID="ScriptLink7" name="sp.core.js" runat="server" LoadAfterUI="true" Localizable="false" />
    <SharePoint:ScriptLink ID="ScriptLink8" name="SP.RequestExecutor.js" runat="server" LoadAfterUI="true" Localizable="false" />
</asp:Content>

<%-- The markup in the following Content element will be placed in the TitleArea of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
 Page Title
</asp:Content>

<%-- The markup and script in the following Content element will be placed in the <body> of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
<WebPartPages:AllowFraming runat="server" />

 <table class="ms-formtable" border="0" cellspacing="0" width="100%">
        <tr>
            <td>
                &nbsp;
            </td>
        </tr>
    </table>
 <span id="errorMessage"></span>
 
 <script type="text/javascript">
  
        var errorMessage = '';
        function LoggerLog(logMessage) {
            errorMessage += '<br/>' + logMessage;
            $('#errorMessage').html(errorMessage);
        }
  var hostUrl;
  var appWebUrl;               
        var context;
        var spListID;
  var spListItemID;
  var Comments;

        function getParameterByName(name) {
            name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
            var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
                results = regex.exec(location.search);
            return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
        }
  
        LoadProperties();        
  
  String.prototype.endsWith = function(suffix) {
   return this.indexOf(suffix, this.length - suffix.length) !== -1;
  };
  
   if (this.length == 0)
   return this;
   c = c ? c : ' ';
   var i = 0;
   var val = 0;
   for (; this.charAt(i) == c && i < this.length; i++);
   return this.substring(i);
  }
  String.prototype.trimEnd=function(c)
  {
   c = c?c:' ';
   var i=this.length-1;
   for(;i>=0 && this.charAt(i)==c;i--);
   return this.substring(0,i+1);
  }
   
        function LoadProperties() {
            try {
                //debugger;
                hostUrl = getParameterByName('SPHostUrl');
                appWebUrl = getParameterByName('SPAppWebUrl');
                spListItemID = getParameterByName('ItemID');
                spListID = getParameterByName('ListID');
                Comments = getParameterByName('Comments');

                //debugger;
                if (spListItemID != '') {
                    context = new SP.ClientContext.get_current(); 
     
     // Add Document Set Version
     AddDocSetVersion();
    }
   }
            catch (ex) {
    //debugger;
                console.log(ex.toString());
                LoggerLog(ex.toString());
            }
        }
  
  function AddDocSetVersion()
        {
            console.log("UpdateLeadInfo Method  started..");

            try
            {
                //debugger;                
    var redirectUrl = hostUrl + '/_layouts/15/CreateDocSetVersion.aspx?List={' + spListID + '}&ID=' + spListItemID +'&IsDlg=1';

    var value = new SP.ClientRequest(context);
    var webRequest = value.get_webRequest();
    
    // Set the request verb.
    webRequest.set_httpVerb("GET");
    // Set the request Url.  
    webRequest.set_url(redirectUrl);  
    webRequest.get_headers()['Content-Length'] = 0;
    
    // Set the web request completed event handler, for processing return data.
    webRequest.add_completed(OnWebGetRequestCompleted);        
    
    // Execute the request.
    webRequest.invoke();  
        
                console.log("AddDocSetVersion Method  completed..");
            }
   catch (ex) {
    //debugger;
                console.log("Error occured in AddDocSetVersion : " + ex.toString());
                LoggerLog("Error occured in AddDocSetVersion : " + ex.toString());
            }
        }
  
  function OnWebGetRequestCompleted(executor, eventArgs)
  {
         //debugger;
    if(executor.get_responseAvailable()) 
       {
         var statusCode=executor.get_statusCode();  // STATUS CODE 204 MEANS ok, BUT NO DATA TO RETUIRN. ie CHANGES THIS TO 1223 AND DROPS ALL THE HEADERES
         var statusText=executor.get_statusText(); 
         var responseData=executor.get_responseData();
       var newHeaders=executor.getAllResponseHeaders();
     
     // Look for inputs and add them to the dictionary for postback values
     var inputs = [];

     var patInput = /<input.+?\/??>/ig;
     var patName = /name=\"(.+?)\"/i;
     var patValue = /value=\"(.+?)\"/i;
   
     while(res = patInput.exec(responseData)) {
      var name1 = '';
      var value1 = '';
      var InputTag = res[0];
      
      var resName = patName.exec(InputTag);
      if(resName !== null) {
       name1 = resName[1];
      }
      
      var resValue = patValue.exec(InputTag);
      if(resValue !== null) {
       value1 = resValue[1];
      }

      if (name1 == '')
      {
       continue;
      }
      
      inputs.push({key:name1,value:value1}); 
     }
     
     // Format inputs as postback data string, but ignore the one that ends with iidIOGoBack
     var strPost = "";
     var strComments = Comments;
     var IsCommentsExist = false;
     for(i=0;i<inputs.length;i++){     
      if (inputs[i].key != '' && inputs[i].key.endsWith("CreateComments"))
      {
       IsCommentsExist = true;
       strPost += encodeURIComponent(inputs[i].key) + "=" + encodeURIComponent(strComments) + "&";
      }
      else if (inputs[i].key != '')
      {
       strPost += encodeURIComponent(inputs[i].key) + "=" + encodeURIComponent(inputs[i].value) + "&";
      }      
     }
     
     
     if (!IsCommentsExist)
     {
      // Set Document Set Version Comments
      strPost += encodeURIComponent("CreateComments") + "=" + encodeURIComponent(strComments) + "&";
     }

     strPost = strPost.trimEnd('&');
     
     var postData = strPost;

     // Build postback request          
     var redirectUrl = hostUrl + '/_layouts/15/CreateDocSetVersion.aspx?List={' + spListID + '}&ID=' + spListItemID +'&IsDlg=1';

     var value = new SP.ClientRequest(context);
     var webRequest = value.get_webRequest();
     
     // Set the request verb.
     webRequest.set_httpVerb("POST");
     // Set the request Url.  
     webRequest.set_url(redirectUrl);  
     webRequest.set_body(postData);
     webRequest.get_headers()['Accept'] = "text/html, application/xhtml+xml, */*";
     webRequest.get_headers()['Content-Type'] = "application/x-www-form-urlencoded";
     webRequest.get_headers()['Content-Length'] = postData.length;
     webRequest.get_headers()['User-Agent'] = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
     webRequest.get_headers()['Cache-Control'] = "no-cache";
     webRequest.get_headers()['Accept-Encoding'] = "gzip, deflate";
     webRequest.get_headers()['Accept-Language'] = "en-US";
     
     // Set the web request completed event handler, for processing return data.
     webRequest.add_completed(OnWebPostRequestCompleted);        
     
     // Execute the request.
     webRequest.invoke();  
       }
  }
   
  function OnWebPostRequestCompleted(executor, eventArgs)
  {
         //debugger;
   if(executor.get_responseAvailable()) 
   {
    var statusCode=executor.get_statusCode();  // STATUS CODE 204 MEANS ok, BUT NO DATA TO RETUIRN. ie CHANGES THIS TO 1223 AND DROPS ALL THE HEADERES
    var statusText=executor.get_statusText(); 
    var responseData=executor.get_responseData();
    var newHeaders=executor.getAllResponseHeaders();
   }
  }
         
 </script>

</asp:Content>
then I have uploaded that page in "StyleLibrary" of Sharepoint online site.
After on App Web page, I have created "SPAppIframe" control to load above created "DocSet.aspx" page along with the query string to add Document Set Version for an document set item.
<SharePoint:SPAppIFrame ID="SPDocSetVersionCreation" Style="display: none !important;" runat="server" Src="" Width="100%" Height="100%"></SharePoint:SPAppIFrame>
Code to load "DocSet.aspx" page on App Web page dynamically using jquery.
<script type="text/javascript">
// Update Document Set Version
var Iframeurl = '';
Iframeurl += '&SPHostUrl=' + HostUrl + '&SPAppWebUrl=' + AppWebUrl + '&ItemID=' + ItemId + '&ListID=' + spListID + '&Comments=' + encodeURIComponent('comments');
$('#SPDocSetVersionCreation').attr('src', Iframeurl);
$('#SPDocSetVersionCreation').load( function () {
alert('Document set version added suceesfully !');
});
 </script>