コード例 #1
0
        public async static Task <AttachmentReference> CreateAttachmentChunkedAsync(WorkItemTrackingHttpClient client, VssConnection connection, MemoryStream uploadStream, int chunkSizeInBytes)
        {
            // it's possible for the attachment to be empty, if so we can't used the chunked upload and need
            // to fallback to the normal upload path.
            if (uploadStream.Length == 0)
            {
                return(await CreateAttachmentAsync(client, uploadStream));
            }

            var requestSettings = new VssHttpRequestSettings
            {
                SendTimeout = TimeSpan.FromMinutes(5)
            };
            var httpClient = new HttpClient(new VssHttpMessageHandler(connection.Credentials, requestSettings));

            // first create the attachment reference.
            // can't use the WorkItemTrackingHttpClient since it expects either a file or a stream.
            var attachmentReference = await RetryHelper.RetryAsync(async() =>
            {
                var request  = new HttpRequestMessage(HttpMethod.Post, $"{connection.Uri}/_apis/wit/attachments?uploadType=chunked&api-version=3.2");
                var response = await httpClient.SendAsync(request);

                if (response.IsSuccessStatusCode)
                {
                    return(await response.Content.ReadAsAsync <AttachmentReference>());
                }
                else
                {
                    var exceptionResponse = await response.Content.ReadAsAsync <ExceptionResponse>();
                    throw new Exception(exceptionResponse.Message);
                }
            }, 5);

            // now send up each chunk
            var totalNumberOfBytes = uploadStream.Length;

            // if number of chunks divides evenly, no need to add an extra chunk
            var numberOfChunks = ClientHelpers.GetBatchCount(totalNumberOfBytes, chunkSizeInBytes);

            for (var i = 0; i < numberOfChunks; i++)
            {
                var chunkBytes = new byte[chunkSizeInBytes];
                // offset is always 0 since read moves position forward
                var chunkLength = uploadStream.Read(chunkBytes, 0, chunkSizeInBytes);

                var result = await RetryHelper.RetryAsync(async() =>
                {
                    // manually create the request since the WorkItemTrackingHttpClient does not support chunking
                    var content = new ByteArrayContent(chunkBytes, 0, chunkLength);
                    content.Headers.ContentType   = new MediaTypeHeaderValue("application/octet-stream");
                    content.Headers.ContentLength = chunkLength;
                    content.Headers.ContentRange  = new ContentRangeHeaderValue(i *chunkSizeInBytes, i *chunkSizeInBytes + chunkLength - 1, totalNumberOfBytes);

                    var chunkRequest = new HttpRequestMessage(HttpMethod.Put, $"{connection.Uri}/_apis/wit/attachments/" + attachmentReference.Id + "?uploadType=chunked&api-version=3.2")
                    {
                        Content = content
                    };
                    var chunkResponse = await httpClient.SendAsync(chunkRequest);
                    if (!chunkResponse.IsSuccessStatusCode)
                    {
                        // there are two formats for the exception, so detect both.
                        var responseContentAsString = await chunkResponse.Content.ReadAsStringAsync();
                        var criticalException       = JsonConvert.DeserializeObject <CriticalExceptionResponse>(responseContentAsString);
                        var exception = JsonConvert.DeserializeObject <ExceptionResponse>(responseContentAsString);

                        throw new Exception(criticalException.Value?.Message ?? exception.Message);
                    }

                    return(chunkResponse.StatusCode);
                }, 5);
            }

            return(attachmentReference);
        }