Once Web Reference is setup authentication and web service wsdl methods handler can be created. My VersionsHandler.cs looks like:
using System;
using System.Net;
using System.Security;
using System.Xml;
using Core.ListItemFieldVersions.ListsASMX;
using Microsoft.SharePoint.Client;
namespace Core.ListItemFieldVersions
{
public class VersionsHandler
{
private const string ListsServiceUrl = "/_vti_bin/Lists.asmx";
private Lists lists = null;
public string TenantUrl{get;set;}
public String User { get; set; }
public String Password { get; set; }
public string Domain { get; set; }
public string MySiteHost { get; set; }
private Lists _lists
{
get
{
if (lists == null)
{
if (!String.IsNullOrEmpty(TenantUrl))
{
this.lists = new Lists();
lists.Url = TenantUrl + ListsServiceUrl;
lists.UseDefaultCredentials = false;
lists.CookieContainer = new CookieContainer();
lists.CookieContainer.Add(GetFedAuthCookie(CreateSharePointOnlineCredentials()));
return lists;
}
else if (this.User.Length > 0 && this.Password.Length > 0 && this.Domain.Length > 0 && this.MySiteHost.Length > 0)
{
this.lists = new Lists();
lists.Url = this.MySiteHost + ListsServiceUrl;
NetworkCredential credential = new NetworkCredential(this.User, this.Password, this.Domain);
CredentialCache credentialCache = new CredentialCache();
credentialCache.Add(new Uri(this.MySiteHost), "NTLM", credential);
lists.Credentials = credentialCache;
return lists;
}
else
{
throw new Exception("Please specify an authentication provider or specify domain credentials");
}
}
else
{
return this.lists;
}
}
}
public XmlNode GetVersionCollection(string listId, string itemId, string fieldName)
{
return _lists.GetVersionCollection(listId, itemId, fieldName);
}
private SharePointOnlineCredentials CreateSharePointOnlineCredentials()
{
var spoPassword = new SecureString();
foreach (char c in Password)
{
spoPassword.AppendChar(c);
}
return new SharePointOnlineCredentials(User, spoPassword);
}
private Cookie GetFedAuthCookie(SharePointOnlineCredentials credentials)
{
string authCookie = credentials.GetAuthenticationCookie(new Uri(this.TenantUrl));
if (authCookie.Length > 0)
{
return new Cookie("SPOIDCRL", authCookie.TrimStart("SPOIDCRL=".ToCharArray()), String.Empty, new Uri(this.TenantUrl).Authority);
}
else
{
return null;
}
}
}
}
Once we have the handler class, the program can send authentication cookie and access the methods of the Lists.asmx. Username and password are required in order to do this and maybe this is one of the downsides. I have added my credentials in the app.config file in plain text, but in production the password should be at least encrypted.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" <br>type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="Core.ListItemFieldVersions.Properties.Settings" <br>type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"<br> requirePermission="false" />
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<applicationSettings>
<Core.ListItemFieldVersions.Properties.Settings>
<setting name="Core_ListItemFieldVersions_ListsASMX_Lists" serializeAs="String">
<value>https://yoursite.sharepoint.com/_vti_bin/Lists.asmx</value><br> </setting>
</Core.ListItemFieldVersions.Properties.Settings>
</applicationSettings>
<appSettings>
<add key="TenantUrl" value="https://yoursite.sharepoint.com/sites/rootsite" /><br> <add key="AdminUser" value="yoursite@yourcompany.onmicrosoft.com" />
<add key="Password" value="your_password" />
</appSettings>
</configuration><br><br>Now get to the goal... getting
using System;
using System.Configuration;
using System.Security;
using System.Xml;
using Microsoft.SharePoint.Client;
namespace Core.ListItemFieldVersions
{
class Program
{
private static Web _web;
private static ClientContext _context;
static void Main(string[] args)
{
_context = new ClientContext(ConfigurationManager.AppSettings["TenantUrl"]);
string userName = ConfigurationManager.AppSettings["AdminUser"];
var passWord = new SecureString();
foreach (char c in ConfigurationManager.AppSettings["Password"].ToCharArray())
{
passWord.AppendChar(c);
}
_context.Credentials = new SharePointOnlineCredentials(userName, passWord);
_web = _context.Web;
var list = _context.Web.Lists.GetByTitle("Retention Rules");
var query = new CamlQuery();
query.ViewXml =
@"<View Scope='RecursiveAll'>
<Query>
<Where><Eq><FieldRef Name='FSObjType' /><Value Type='Integer'>0</Value></Eq></Where>
</Query>
<RowLimit>5000</RowLimit>
</View>";
var listItems = list.GetItems(query);
_context.Load(_web);
_context.Load(list);
_context.Load(listItems);
_context.ExecuteQuery();
var versionsHandler = new VersionsHandler();
versionsHandler.User = userName;
versionsHandler.Password = ConfigurationManager.AppSettings["Password"];
versionsHandler.TenantUrl = ConfigurationManager.AppSettings["TenantUrl"];
if(listItems.Count == 0) throw new ArgumentException("No list items");
var listId = list.Id.ToString();
var itemId = listItems[0].Id.ToString();
var versionNodes = versionsHandler.GetVersionCollection(listId, itemId, "Title");
foreach (XmlNode node in versionNodes.ChildNodes)
{
if (node.Attributes != null)
{
var title = node.Attributes["Title"].Value;
var modified = node.Attributes["Modified"].Value;
Console.WriteLine("Modified: {0}, Title: {1}", modified, title);
}
}
Console.WriteLine("Hit any key to end.");
Console.ReadKey();
}
}
}
The interesting moment is where we invoke the handler that would invoke the web service and retrieve data in xml format:
var versionNodes = versionsHandler.GetVersionCollection(listId, itemId, "Title");
This is how you can get version history data for a list item field. It is also useful when you have "AppenOnly" note field and you have to get all the comments. Another downside is that it is very slow if you have big set of fields or big set of list items, but at least is a way to get the data.