// ------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ namespace Microsoft.Graph { using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; /// /// A implementation to handle json batch requests. /// public class BatchRequestContent: HttpContent { /// /// A BatchRequestSteps property. /// public IReadOnlyDictionary BatchRequestSteps { get; private set; } /// /// Gets a serializer for serializing and deserializing JSON objects. /// public ISerializer Serializer { get; private set; } /// /// Constructs a new . /// public BatchRequestContent() :this(new BatchRequestStep[] { },null) { } /// /// Constructs a new . /// /// A list of to add to the batch request content. /// A serializer for serializing and deserializing JSON objects. public BatchRequestContent(BatchRequestStep [] batchRequestSteps, ISerializer serializer = null) : this(batchRequestSteps) { this.Serializer = serializer ?? new Serializer(); } /// /// Constructs a new . /// /// A list of to add to the batch request content. public BatchRequestContent(params BatchRequestStep[] batchRequestSteps) { if (batchRequestSteps == null) throw new ClientException(new Error { Code = ErrorConstants.Codes.InvalidArgument, Message = string.Format(ErrorConstants.Messages.NullParameter, nameof(batchRequestSteps)) }); if (batchRequestSteps.Count() > CoreConstants.BatchRequest.MaxNumberOfRequests) throw new ClientException(new Error { Code = ErrorConstants.Codes.MaximumValueExceeded, Message = string.Format(ErrorConstants.Messages.MaximumValueExceeded, "Number of batch request steps", CoreConstants.BatchRequest.MaxNumberOfRequests) }); this.Headers.ContentType = new MediaTypeHeaderValue(CoreConstants.MimeTypeNames.Application.Json); BatchRequestSteps = new Dictionary(); foreach (BatchRequestStep requestStep in batchRequestSteps) { if(requestStep.DependsOn != null && !ContainsCorrespondingRequestId(requestStep.DependsOn)) { throw new ClientException(new Error { Code = ErrorConstants.Codes.InvalidArgument, Message = ErrorConstants.Messages.InvalidDependsOnRequestId }); } AddBatchRequestStep(requestStep); } this.Serializer = new Serializer(); } /// /// Adds a to batch request content if doesn't exists. /// /// A to add. /// True or false based on addition or not addition of the provided . public bool AddBatchRequestStep(BatchRequestStep batchRequestStep) { if (batchRequestStep == null || BatchRequestSteps.ContainsKey(batchRequestStep.RequestId) || BatchRequestSteps.Count >= CoreConstants.BatchRequest.MaxNumberOfRequests //we should not add any more steps ) { return false; } (BatchRequestSteps as IDictionary).Add(batchRequestStep.RequestId, batchRequestStep); return true; } /// /// Adds a to batch request content. /// /// A to use to build a to add. /// The requestId of the newly created public string AddBatchRequestStep(HttpRequestMessage httpRequestMessage) { if (BatchRequestSteps.Count >= CoreConstants.BatchRequest.MaxNumberOfRequests) throw new ClientException(new Error { Code = ErrorConstants.Codes.MaximumValueExceeded, Message = string.Format(ErrorConstants.Messages.MaximumValueExceeded, "Number of batch request steps", CoreConstants.BatchRequest.MaxNumberOfRequests) }); string requestId = Guid.NewGuid().ToString(); BatchRequestStep batchRequestStep = new BatchRequestStep(requestId, httpRequestMessage); (BatchRequestSteps as IDictionary).Add(batchRequestStep.RequestId, batchRequestStep); return requestId; } /// /// Adds a to batch request content /// /// A to use to build a to add. /// The requestId of the newly created public string AddBatchRequestStep(IBaseRequest request) { if (BatchRequestSteps.Count >= CoreConstants.BatchRequest.MaxNumberOfRequests) throw new ClientException(new Error { Code = ErrorConstants.Codes.MaximumValueExceeded, Message = string.Format(ErrorConstants.Messages.MaximumValueExceeded, "Number of batch request steps", CoreConstants.BatchRequest.MaxNumberOfRequests) }); string requestId = Guid.NewGuid().ToString(); BatchRequestStep batchRequestStep = new BatchRequestStep(requestId, request.GetHttpRequestMessage()); (BatchRequestSteps as IDictionary).Add(batchRequestStep.RequestId, batchRequestStep); return requestId; } /// /// Removes a from batch request content for the specified id. /// /// A unique batch request id to remove. /// True or false based on removal or not removal of a . public bool RemoveBatchRequestStepWithId(string requestId) { if (string.IsNullOrEmpty(requestId)) throw new ClientException( new Error { Code = ErrorConstants.Codes.InvalidArgument, Message = string.Format(ErrorConstants.Messages.NullParameter, nameof(requestId)) }); bool isRemoved = false; if (BatchRequestSteps.ContainsKey(requestId)) { (BatchRequestSteps as IDictionary).Remove(requestId); isRemoved = true; foreach (KeyValuePair batchRequestStep in BatchRequestSteps) { if (batchRequestStep.Value != null && batchRequestStep.Value.DependsOn != null) while (batchRequestStep.Value.DependsOn.Remove(requestId)) ; } } return isRemoved; } internal async Task GetBatchRequestContentAsync() { JObject batchRequest = new JObject(); JArray batchRequestItems = new JArray(); foreach (KeyValuePair batchRequestStep in BatchRequestSteps) batchRequestItems.Add(await GetBatchRequestContentFromStepAsync(batchRequestStep.Value)); batchRequest.Add(CoreConstants.BatchRequest.Requests, batchRequestItems); return batchRequest; } private bool ContainsCorrespondingRequestId(IList dependsOn) { return dependsOn.All(requestId => BatchRequestSteps.ContainsKey(requestId)); } private async Task GetBatchRequestContentFromStepAsync(BatchRequestStep batchRequestStep) { JObject jRequestContent = new JObject { { CoreConstants.BatchRequest.Id, batchRequestStep.RequestId }, { CoreConstants.BatchRequest.Url, GetRelativeUrl(batchRequestStep.Request.RequestUri) }, { CoreConstants.BatchRequest.Method, batchRequestStep.Request.Method.Method } }; if (batchRequestStep.DependsOn != null && batchRequestStep.DependsOn.Count() > 0) jRequestContent.Add(CoreConstants.BatchRequest.DependsOn, new JArray(batchRequestStep.DependsOn)); if (batchRequestStep.Request.Content?.Headers != null && batchRequestStep.Request.Content.Headers.Count() > 0) jRequestContent.Add(CoreConstants.BatchRequest.Headers, GetContentHeader(batchRequestStep.Request.Content.Headers)); if(batchRequestStep.Request != null && batchRequestStep.Request.Content != null) { jRequestContent.Add(CoreConstants.BatchRequest.Body, await GetRequestContentAsync(batchRequestStep.Request)); } return jRequestContent; } private async Task GetRequestContentAsync(HttpRequestMessage request) { try { HttpRequestMessage clonedRequest = await request.CloneAsync(); using (Stream streamContent = await clonedRequest.Content.ReadAsStreamAsync()) { return Serializer.DeserializeObject(streamContent); } } catch (Exception ex) { throw new ClientException(new Error { Code = ErrorConstants.Codes.InvalidRequest, Message = ErrorConstants.Messages.UnableToDeserializexContent }, ex); } } private JObject GetContentHeader(HttpContentHeaders headers) { JObject jHeaders = new JObject(); foreach (KeyValuePair> header in headers) { jHeaders.Add(header.Key, GetHeaderValuesAsString(header.Value)); } return jHeaders; } private string GetHeaderValuesAsString(IEnumerable headerValues) { if (headerValues == null || headerValues.Count() == 0) return string.Empty; StringBuilder builder = new StringBuilder(); foreach (string headerValue in headerValues) { builder.Append(headerValue); } return builder.ToString(); } private string GetRelativeUrl(Uri requestUri) { string version = "v1.0"; if (requestUri.AbsoluteUri.Contains("beta")) version = "beta"; return requestUri.AbsoluteUri.Substring(requestUri.AbsoluteUri.IndexOf(version) + version.ToCharArray().Count()); } /// /// Serialize the HTTP content to a stream as an asynchronous operation. /// /// The target stream. /// Information about the transport (channel binding token, for example). This parameter may be null. /// protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) { using (StreamWriter streamWritter = new StreamWriter(stream, new UTF8Encoding(), 1024, true)) using (JsonTextWriter textWritter = new JsonTextWriter(streamWritter)) { JObject batchContent = await GetBatchRequestContentAsync(); batchContent.WriteTo(textWritter); } } /// /// Determines whether the HTTP content has a valid length in bytes. /// /// The length in bytes of the HTTP content. /// protected override bool TryComputeLength(out long length) { length = -1; return false; } } }