GraphAPI delta request
Jul. 2024
24
Requesting a resource delta via the REST GraphAPI isn't as straightforward as it should be. As a matter of fact, it was easily possible with the NuGet version 4.x, but delta-request support has been removed with the major release of version 5.0. However, despite not offering any official library delta request support, it still is possible with a cumbersome workaround.
Extension methods
internal static class RequestInformationExtensions
{
public static RequestInformation EnableDelta(this RequestInformation reqInfo, string skipToken = null, string deltaToken = null)
{
StringBuilder queryBuilder = new StringBuilder();
if (!string.IsNullOrEmpty(skipToken))
queryBuilder.Append(",%24skiptoken");
if (!string.IsNullOrEmpty(deltaToken))
queryBuilder.Append(",%24deltatoken");
string query = queryBuilder.ToString();
reqInfo.UrlTemplate = reqInfo.UrlTemplate.Insert(reqInfo.UrlTemplate.Length - 1, query);
if (!string.IsNullOrEmpty(skipToken))
reqInfo.QueryParameters.Add("%24skiptoken", skipToken);
if (!string.IsNullOrEmpty(deltaToken))
reqInfo.QueryParameters.Add("%24deltatoken", deltaToken);
return reqInfo;
}
}
internal static class IRequestAdapterExtensions
{
private const string SKIP_TOKEN_IDENTIFIER = "$skiptoken=";
private const string DELTA_TOKEN_IDENTIFIER = "$deltatoken=";
public static async Task<DeltaResponse<ResponseCollection>> SendDeltaAsync<ResponseCollection>(this IRequestAdapter requestAdapter,
RequestInformation requestInformation,
ParsableFactory<ResponseCollection> parsable)
where ResponseCollection : IParsable
{
return await requestAdapter.SendAsync(requestInformation, (node) =>
{
var deltaTokenNode = node.GetChildNode("@odata.deltaLink");
var skipTokenNode = node.GetChildNode("@odata.nextLink");
string skipToken = null;
string deltaToken = null;
if (!string.IsNullOrEmpty(skipTokenNode?.GetStringValue()))
{
string tokenIdentifier = SKIP_TOKEN_IDENTIFIER;
int tokenBegin = skipTokenNode.GetStringValue().IndexOf(tokenIdentifier) + tokenIdentifier.Length;
skipToken = skipTokenNode.GetStringValue().Substring(tokenBegin);
}
if (!string.IsNullOrEmpty(deltaTokenNode?.GetStringValue()))
{
string tokenIdentifier = DELTA_TOKEN_IDENTIFIER;
int tokenBegin = deltaTokenNode.GetStringValue().IndexOf(tokenIdentifier) + tokenIdentifier.Length;
deltaToken = deltaTokenNode.GetStringValue().Substring(tokenBegin);
}
return new DeltaResponse<ResponseCollection>(skipToken, deltaToken, parsable(node));
});
}
}
Delta response
DeltaResponse
is a response wrapper, solely providing the SkipToken
and DeltaToken
for follow-up requests. The GraphResponse
contains the actual response object.
internal class DeltaResponse<ResponseCollection> : IParsable where ResponseCollection : IParsable
{
public DeltaResponse(string skipToken, string deltaToken, ResponseCollection responseCollection) =>
(GraphResponse, SkipToken, DeltaToken) = (responseCollection, skipToken, deltaToken);
public IDictionary<string, Action<IParseNode>> GetFieldDeserializers() => GraphResponse.GetFieldDeserializers();
public void Serialize(ISerializationWriter writer) => GraphResponse.Serialize(writer);
public ResponseCollection GraphResponse { get; private set; }
public string SkipToken { get; private set; }
public string DeltaToken { get; private set; }
public bool HasDeltaToken => !string.IsNullOrEmpty(DeltaToken);
}
Delta request
public async Task<Tuple<string, IEnumerable<Contact>>> GetDeltaAsync(string deltaToken = null, CancellationToken cancellationToken = default)
{
List<Contact> contacts = new List<Contact>();
async Task<DeltaResponse<ContactCollectionResponse>> BuildDeltaRequest(string skip = null, string delta = null)
{
var requestInformation = Client.Users["{mailbox}"].Contacts.Delta.ToGetRequestInformation().EnableDelta(skip, delta);
return await Client.RequestAdapter.SendDeltaAsync(requestInformation, ContactCollectionResponse.CreateFromDiscriminatorValue);
}
var response = await BuildDeltaRequest(null, deltaToken);
while (!cancellationToken.IsCancellationRequested)
{
foreach (var contact in response.GraphResponse.Value)
contacts.Add(contact);
if (response.HasDeltaToken)
{
return new Tuple<string, IEnumerable<Contact>>(response.DeltaToken, contacts);
}
else
{
response = await BuildDeltaRequest(response.SkipToken, deltaToken);
}
}
return new Tuple<string, IEnumerable<Contact>>(null, contacts);
}