msgraph-sdk-dotnet icon indicating copy to clipboard operation
msgraph-sdk-dotnet copied to clipboard

Long Running Operation Handler Work

Open joerneu opened this issue 6 years ago • 5 comments

After about 10-50 copy operations I get

Error = {Code: generalException Message: An error occurred in the data store.

I assume that there is some contention because the copy operation is async.

I would like to perform the copy operations sequentially but I cannot monitor the copy operation as described in the API's documentation.

[x] Expected behavior [x] Actual behavior [x] Steps to reproduce the behavior

Expected behavior

Return an interface to monitor the progress of the copy operation.

See documentation:

Copy a DriveItem Working with APIs that make take a long time to complete

Actual behavior

The copy operation returns Task<DriveItem> and the Task's result is always NULL.

There was an issue regarding the copy operation and this behavior:

Issue #73

Steps to reproduce the behavior

var copy = await _graphClient.Drive.Items[item.Id].Copy(newName, new ItemReference { DriveId = folder.ParentReference.DriveId, Id = folder.Id }).Request().PostAsync();

copy is always NULL. There is no way to know when the copy operation actually finishes.

AB#7341

joerneu avatar Jan 04 '18 11:01 joerneu

I need to check whether the Graph returns the location header so that a subsequent request can be made to monitor the copy status. Still, if it is returned, we'll need to expose the response headers. That is planned work.

2121286

MIchaelMainer avatar Jan 11 '18 21:01 MIchaelMainer

Workaround for sending :

var implReq = ((DriveItemCopyRequest) req);
implReq.Method = "POST";
using(var response =
    await implReq.SendRequestAsync(implReq.RequestBody, CancellationToken.None,
        System.Net.Http.HttpCompletionOption.ResponseContentRead).ConfigureAwait(false)) {

    if (response.IsSuccessStatusCode) {
        //… do polling with response.Header.Location status
    }
}

Please also note that AsyncMonitor does not work with current graph API. You should do your own polling and wait for "completed" result to grab copied item id.

pfresnay avatar May 09 '18 12:05 pfresnay

To complete, my forked polling method:

private async Task<AsyncOperationStatus> pollForOperationCompletionAsync(string monitorUrl, IProgress<AsyncOperationStatus> progress, CancellationToken cancellationToken) {
    AsyncOperationStatus asyncOperationStatus = null;

    while (!cancellationToken.IsCancellationRequested) {
        using(var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, monitorUrl)) {
            using(var responseMessage = await graphClient.HttpProvider.SendAsync(httpRequestMessage).ConfigureAwait(false)) {
                using(var responseStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false)) {
                    asyncOperationStatus = graphClient.HttpProvider.Serializer.DeserializeObject<AsyncOperationStatus>(responseStream);

                    if (asyncOperationStatus == null) {
                        throw new ServiceException(
                            new Error {
                                Code = "generalException",
                                    Message = "Error retrieving monitor status."
                            });
                    }

                    if (string.Equals(asyncOperationStatus.Status, "cancelled", StringComparison.OrdinalIgnoreCase)) {
                        return asyncOperationStatus;
                    }

                    if (string.Equals(asyncOperationStatus.Status, "failed", StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(asyncOperationStatus.Status, "deleteFailed", StringComparison.OrdinalIgnoreCase)) {
                        object message = null;
                        if (asyncOperationStatus.AdditionalData != null) {
                            asyncOperationStatus.AdditionalData.TryGetValue("message", out message);
                        }

                        throw new ServiceException(
                            new Error {
                                Code = "generalException",
                                    Message = message as string
                            });
                    }

                    if (progress != null) {
                        progress.Report(asyncOperationStatus);
                    }

                    if (string.Equals(asyncOperationStatus.Status, "completed", StringComparison.OrdinalIgnoreCase))
                        return asyncOperationStatus;
                }
            }
        }
        await Task.Delay(CoreConstants.PollingIntervalInMs, cancellationToken).ConfigureAwait(false);
    }
    return asyncOperationStatus;
}

pfresnay avatar May 09 '18 12:05 pfresnay

Long running operation support needs to be implemented as a piece of middleware. This code above is a good starting point.

darrelmiller avatar Jan 26 '19 04:01 darrelmiller

Hello,

I tried pfresnay's workarounds for sending and polling. I got a monitor uri that works in the browser. Unfortunately in polling method, the call of _graphClient.HttpProvider.SendAsync method throws a NullReferenceException at Microsoft.Graph.HttpProvider.<SendAsync>d__18.MoveNext(), System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw().

I read something nebulous about missing credentials. Do I have to add some scopes to the azure enterprise application? This HttpRequest does not call the Graph Rest Api but in my case the Sharepoint Rest Api. Scopes "User.Read", "Sites.Manage.All" are set.

Thank you.

Kalle4242 avatar Aug 07 '20 16:08 Kalle4242