// ------------------------------------------------------------------------------
// 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;
}
}
}