Sometimes as a developer usual channels don't work. My colleague and I tried for several hours to extract a base64 file from a document library using "/_vti_bin/Lists.asmx" in a Nintex Web Service component. We could see the correct SOAP output after using the internal run command. But alas, after compiling, the workflow, the web service returned nothing.
Â
Being a SharePoint Developer for over eight years, it was time to write a web service to talk to Nintex. Since it had been awhile since I had written one, I turned to Google for help.
Â
To the rescue came Rob Windsor's Video and Blog on the Subject of creating a SharePoint Web Service:
Â
- Creating a Custom ASP NET (ASMX) Web Service in SharePoint - YouTube
- Walkthrough: Creating a Custom ASP.NET (ASMX) Web Service in SharePoint 2010 – Rob Windsor's Weblog
Â
The Weblog is for SharePoint 2010 in the video, Rob explains the changes needed for 2013.
Â
For those unfamiliar with Rob, he is a gifted instructor with some heavy-duty SharePoint courses on Pluralsight.com.
Â
I, especially, appreciate his no-nonsense, step-by-step guide and sample code. Having developed some web services that resided in SharePoint's ISAPI directory, the simpler method of building the web service for the SharePoint Layouts folder appealed to me.
Web services built for the ISAPI directory have a complicated discovery wrapper. This makes them much harder to create. As long as my colleague and I know the address of the web service ".asmx" file, web discoverability is irrelevant.
Â
The basic process of creating the Web Service harness is basically a recipe. Getting the right logic in the Web Service Class is a little more difficult and depends on what you are trying to accomplish.
Â
Just to summarize the formulaic steps using either Visual Studio 2013 or 2015:
Â
- Choose to deploy as a Farm Solution.
- Add the SharePoint Layout's folder as a mapping to the project.
- Under the Project Name beneath the layout's folder, add a new text file. Name it using the class name that you will be creating. Also, change the extension to ".asmx" from ".txt".
- Add a class file with the base name used for the ".asmx" file.
- Add a Reference to the project for System.Webservices.
- Save the project.
- Choose the Build Command by right-clicking the Project Name in Explorer View.
The project is just a shell at this point in time, but the build command will create the Assembly. The three-part Assembly Name is needed for the .asmx file. - To actually obtain the Assembly three-part name, the author points to an article link for creating a PowerShell tool to attach to the Visual Studio Tools Menu for Strong Name Generation. The tool will generate the three-part Assembly Name and output to a window in Visual Studio.  Follow the instructions--then simply run the command . The output for my example code is as follows:
WorkflowHelperWS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d9e9ca5ad43f9694 - The .asmx file would read as:
<%@ WebService class="{PROJECT}.{CLASS}, {THREE-PART SHAREPOINT ASSEMBLY NAME}" %>
In my example, my CopyWebSvcs.asmx file looks as follows:
<%@ WebService class="WorkflowHelperWS.CopyWebSvcs, WorkflowHelperWS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d9e9ca5ad43f9694" %>
This line is the ENTIRE Contents of my CopyWebSvcs.asmx File. - Finally, write the class code.
Â
Rob has good sample code that I called from Nintex.
Rob's Code:
Â
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Services;
using Microsoft.SharePoint;
namespace WebServiceDemo
{
    public class MyCustomWebService : WebService
    {
         rWebMethod]
         public string GetSiteListCount()
         {
              var web = SPContext.Current.Web;
              return (web.Title + " contains " +
                web.Lists.Count.ToString() + " lists.");
         }
    }
}
Â
Note that this code requires No Parameters and returns a string.
Â
Another interesting thing to note about Rob's code is that he makes use of the SPContext object.Â
This object assumes you are in the SPWeb where you want to work. Unfortunately, it doesn't always work out that way.
Â
Rob explains in his videos that he directly accesses the web service .asmx file through the URL of his current Site Collection "http://win7virtualbox/sites/demo/_layouts/15/WebServiceDemo/MyCustomWebService.asmx". However, after he executes the web service from the web page, he is redirected to the Root Site Collection and the URL becomes:
"http://win7virtualbox/_layouts/15/WebServiceDemo/MyCustomWebService.asmx".
Â
This means the SPContext object is referencing the Root Site Collection/Root Web--not the site from which the URL was invoked.
Â
Rob goes on to explain when you access the web service from code, as in his test harness console app--also in the video, the Proxy URLÂ is used to set the site for the SPContext object. This allows the executing site to be used instead of the Root Site Collection/Root Site.
Â
Unfortunately, this doesn't seem to happen in Nintex. You will notice in my code below that I specifically open the site and web from a URL parameter. I tried at first to use the SPContext object; however, it always pointed to the URL of my Root Site Collection/Root Web.
Â
Since my web service was being used from Nintex, I did not bother with the console apps.
Â
Â
Once you have created your class, you can create multiple routines or webmethods within the class file. You can even use the same method with different overload signatures (different parameters).
Â
At first, I just wanted to duplicate what I had been trying to get from SharePoint "Lists.asmx" web service, namely, a Base64 string from a document in a document library. But using this web service still required me to couple it with some other steps such as parsing the returned XML and another call to "/_vti_bin/Lists.asmx" to the document string as an attachment to a specific list item.
Â
What I really wanted was a single web service to copy a document from a document library and add it to a specific list item as an attachment. I wanted to do the entire operation in one call: hence, my second webmethod.
Â
The way I created the second webmethod was to find and morph a PowerShell Script.
Â
Stephan's Code:
Â
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Web.Services;
using Microsoft.SharePoint;
Â
namespace WorkflowHelperWS
{
   class CopyWebSvcs
   {
fWebMethod]
       public string DoctoBase64toStringByWebUrl(string WebUrl, string ListName, int docID)
       {
          using (SPSite site = new SPSite(WebUrl))
           {
               using (SPWeb web = site.OpenWeb())
               {
                        byteÂ] binFile;
                         try
                          {
                              var list = web.ListsÂListName];
                              SPListItem item = list.GetItemById(docID);
                              SPFile file = item.File;
                               binFile = file.OpenBinary();
                                   }
                                   catch (Exception ex)
                                        {
                                             return (ex.Message);
                                         }
                              return (Convert.ToBase64String(binFile));
                              }
                }
       }
Â
/WebMethod]
       public string CopyDocumenttoList(string WebUrl, string docLibName, string docName, string ListName, int ListID )
       {
                    using (SPSite site = new SPSite(WebUrl))
                     {
                              using (SPWeb web = site.OpenWeb())
                              {
                                   try
                                        {
                                             var list = web.ListseListName];
                                             var docLib = web.ListsddocLibName];
                                             SPListItem listitem = list.GetItemById(ListID);
                                              var filePath = WebUrl + docLibName + "/" + docName;
                                              var file = web.GetFile(filePath).OpenBinary();
                                              listitem.Attachments.Add(filePath, file);
                                              listitem.Update();
                                        }
                                   catch (Exception ex)
                                   {
                                        return (ex.Message);
                                  }
Â
                              return ("Document Successfully Copied to List");
                         }
                }
            }
Â
   }
}
Â
Â
Â
Note also the error handling with the use of the "try" statement. I chose to have an error message returned on error. This would allow the workflow to complete with a normal termination. This error message can be written in the output by including the variable in the Common Section of the Web Services component.
Â
By having the workflow terminate normally, the user does not have to physically click the error status and choose to terminate the workflow to start another.
Â
Workflow with an Error:
Â
Â
The WSP plus the entire project are attached for your experimentation with at your own risk. Use it as a starting template and at the very least, you'll learn some SharePoint and Nintex.