From f0a195d5d0a0e077f481a153964731481468b07e Mon Sep 17 00:00:00 2001 From: Chris Hamons Date: Fri, 29 Jan 2021 16:08:39 -0600 Subject: [PATCH 1/9] Initial import from sample --- .../src/DynamicContent.cs | 68 +++++++++++++++ .../src/DynamicRequest.cs | 83 +++++++++++++++++++ .../src/DynamicResponse.cs | 26 ++++++ 3 files changed, 177 insertions(+) create mode 100644 sdk/core/Azure.Core.Experimental/src/DynamicContent.cs create mode 100644 sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs create mode 100644 sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs b/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs new file mode 100644 index 000000000000..7d7b8763955c --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs @@ -0,0 +1,68 @@ +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Core +{ + public class DynamicContent : RequestContent + { + private DynamicJson _body; + private readonly Encoding Utf8NoBom = new UTF8Encoding(false); + + internal DynamicContent(DynamicJson body) + { + _body = body; + } + + internal static RequestContent Create(DynamicJson body) + => new DynamicContent(body); + + /// + /// TBD + /// + /// + /// + /// + public override async Task WriteToAsync(Stream stream, CancellationToken cancellation) + { + // TODO: this method must be optimized, i.e. DynamicJson writes directly to stream + var json = _body.ToString(); + var utf8 = Utf8NoBom.GetBytes(json); + await stream.WriteAsync(utf8, 0, utf8.Length).ConfigureAwait(false); + } + + /// + /// TBD + /// + /// + /// + public override void WriteTo(Stream stream, CancellationToken cancellation) + { + // TODO: this method must be optimized, i.e. DynamicJson writes directly to stream + var json = _body.ToString(); + var utf8 = Utf8NoBom.GetBytes(json); + stream.Write(utf8, 0, utf8.Length); + } + + /// + /// TBD + /// + /// + /// + public override bool TryComputeLength(out long length) + { + // TODO: This is quite bad, but we need to return a value here so we don't get chunked encoding, which was breaking Anomaly Detector? + var json = _body.ToString(); + length = Utf8NoBom.GetBytes(json).Length; + return true; + } + + /// + /// TBD + /// + public override void Dispose() + { + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs new file mode 100644 index 000000000000..d26a256961a0 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs @@ -0,0 +1,83 @@ +using Azure.Core; +using Azure.Core.Pipeline; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Core +{ + public class DynamicRequest : Request + { + private Request Request { get; } + private HttpPipeline HttpPipeline { get; } + + private static readonly Encoding Utf8NoBom = new UTF8Encoding(false, true); + + public override RequestContent Content + { + get => DynamicContent.Create(Body); + + set { + MemoryStream ms = new MemoryStream(); + value.WriteTo(ms, default); + ms.Seek(0, SeekOrigin.Begin); + using (StreamReader sr = new StreamReader(ms, Utf8NoBom)) + { + Body = new DynamicJson(sr.ReadToEnd()); + } + Request.Content = value; + } + } + + // TODO(matell): How does the initialization here play into the ability to send a request with an "empty" body? + public dynamic Body { get; set; } = DynamicJson.Object(); + + // TODO(matell): In Krzysztof's prototype we also took DiagnosticScope as a parameter, do we still need that? + public DynamicRequest(Request request, HttpPipeline pipeline) + { + Request = request; + HttpPipeline = pipeline; + } + + public async Task SendAsync(CancellationToken cancellationToken = default) + { + // Since we are sending the underlying request, we need to copy the Content on to it, or we'll lose the body. + Request.Content = Content; + + Response res = await HttpPipeline.SendRequestAsync(Request, cancellationToken).ConfigureAwait(false); + DynamicJson dynamicContent = null; + + if (res.ContentStream != null) + { + using (StreamReader sr = new StreamReader(res.ContentStream, encoding: Utf8NoBom, leaveOpen: true)) + { + dynamicContent = new DynamicJson(await sr.ReadToEndAsync()); + } + } + + return new DynamicResponse(res, dynamicContent); + } + + public DynamicResponse Send(CancellationToken cancellationToken = default) => SendAsync().GetAwaiter().GetResult(); + + public override string ClientRequestId { get => Request.ClientRequestId; set => Request.ClientRequestId = value; } + + public override void Dispose() => Request.Dispose(); + + protected override void AddHeader(string name, string value) => Request.Headers.Add(name, value); + + protected override bool ContainsHeader(string name) => Request.Headers.Contains(name); + + protected override IEnumerable EnumerateHeaders() => Request.Headers; + + protected override bool RemoveHeader(string name) => Request.Headers.Remove(name); + + protected override bool TryGetHeader(string name, [NotNullWhen(true)] out string value) => Request.Headers.TryGetValue(name, out value); + + protected override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable values) => Request.Headers.TryGetValues(name, out values); + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs new file mode 100644 index 000000000000..e0a3a65a1fa4 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Azure.Core +{ + public class DynamicResponse : Response + { + private Response Response { get; } + + public override DynamicJson Value { get; } + + public dynamic Content => Value; + + public DynamicResponse(Response response, DynamicJson value) + { + Response = response; + Value = value; + } + + public override Response GetRawResponse() + { + return Response; + } + } +} From 43af06021d7615e83c940d0423e33e102041e4d3 Mon Sep 17 00:00:00 2001 From: Chris Hamons Date: Fri, 29 Jan 2021 17:03:37 -0600 Subject: [PATCH 2/9] Chris' modifications --- .../src/DynamicContent.cs | 60 ++++++-------- .../src/DynamicRequest.cs | 82 +++++++++++++++---- .../src/DynamicResponse.cs | 27 ++++-- 3 files changed, 110 insertions(+), 59 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs b/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs index 7d7b8763955c..e12a28d4b49c 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs @@ -1,68 +1,58 @@ -using System.IO; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Azure.Core { + /// + /// Represents the sent as part of the Azure.Core.Request. + /// public class DynamicContent : RequestContent { - private DynamicJson _body; - private readonly Encoding Utf8NoBom = new UTF8Encoding(false); + private readonly DynamicJson _body; internal DynamicContent(DynamicJson body) { _body = body; } - internal static RequestContent Create(DynamicJson body) - => new DynamicContent(body); + internal static RequestContent Create(DynamicJson body) => new DynamicContent(body); - /// - /// TBD - /// - /// - /// - /// + /// public override async Task WriteToAsync(Stream stream, CancellationToken cancellation) { - // TODO: this method must be optimized, i.e. DynamicJson writes directly to stream - var json = _body.ToString(); - var utf8 = Utf8NoBom.GetBytes(json); - await stream.WriteAsync(utf8, 0, utf8.Length).ConfigureAwait(false); + using Utf8JsonWriter writer = new Utf8JsonWriter(stream); + _body.WriteTo(writer); + await writer.FlushAsync(cancellation).ConfigureAwait(false); } - /// - /// TBD - /// - /// - /// + /// public override void WriteTo(Stream stream, CancellationToken cancellation) { - // TODO: this method must be optimized, i.e. DynamicJson writes directly to stream - var json = _body.ToString(); - var utf8 = Utf8NoBom.GetBytes(json); - stream.Write(utf8, 0, utf8.Length); + using Utf8JsonWriter writer = new Utf8JsonWriter(stream); + _body.WriteTo(writer); + writer.Flush(); } - /// - /// TBD - /// - /// - /// + /// public override bool TryComputeLength(out long length) { - // TODO: This is quite bad, but we need to return a value here so we don't get chunked encoding, which was breaking Anomaly Detector? - var json = _body.ToString(); - length = Utf8NoBom.GetBytes(json).Length; + using MemoryStream stream = new MemoryStream(); + WriteTo(stream, CancellationToken.None); + length = Encoding.UTF8.GetString(stream.ToArray()).Length; return true; } - /// - /// TBD - /// + /// public override void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs index d26a256961a0..c3ab079b5ee6 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs @@ -1,4 +1,7 @@ -using Azure.Core; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core; using Azure.Core.Pipeline; using System; using System.Collections.Generic; @@ -10,6 +13,9 @@ namespace Azure.Core { + /// + /// Represents an HTTP request with content. + /// public class DynamicRequest : Request { private Request Request { get; } @@ -17,14 +23,18 @@ public class DynamicRequest : Request private static readonly Encoding Utf8NoBom = new UTF8Encoding(false, true); - public override RequestContent Content - { - get => DynamicContent.Create(Body); + /// + public override RequestContent? Content + { + get => DynamicContent.Create(Body); set { MemoryStream ms = new MemoryStream(); - value.WriteTo(ms, default); - ms.Seek(0, SeekOrigin.Begin); + if (value != null) + { + value.WriteTo(ms, default); + ms.Seek(0, SeekOrigin.Begin); + } using (StreamReader sr = new StreamReader(ms, Utf8NoBom)) { Body = new DynamicJson(sr.ReadToEnd()); @@ -34,50 +44,88 @@ public override RequestContent Content } // TODO(matell): How does the initialization here play into the ability to send a request with an "empty" body? - public dynamic Body { get; set; } = DynamicJson.Object(); - - // TODO(matell): In Krzysztof's prototype we also took DiagnosticScope as a parameter, do we still need that? + /// + /// The JSON body of request. + /// + public DynamicJson Body { get; set; } = DynamicJson.Object(); + + // TODO(matell): In Krzysztof's prototype we also took DiagnosticScope as a parameter, do we still need that? + /// + /// Creates an instance of that wraps content. + /// + /// The to send. + /// The HTTP pipeline for sending and receiving REST requests and responses. public DynamicRequest(Request request, HttpPipeline pipeline) { Request = request; HttpPipeline = pipeline; } + /// + /// Send the request asynchronously. + /// + /// The cancellation token. + /// The response dynamically typed in a . public async Task SendAsync(CancellationToken cancellationToken = default) { // Since we are sending the underlying request, we need to copy the Content on to it, or we'll lose the body. Request.Content = Content; Response res = await HttpPipeline.SendRequestAsync(Request, cancellationToken).ConfigureAwait(false); - DynamicJson dynamicContent = null; + DynamicJson dynamicContent; if (res.ContentStream != null) { - using (StreamReader sr = new StreamReader(res.ContentStream, encoding: Utf8NoBom, leaveOpen: true)) + using (StreamReader sr = new StreamReader(res.ContentStream, encoding: Utf8NoBom, detectEncodingFromByteOrderMarks:true, bufferSize: 1024, leaveOpen: true)) { - dynamicContent = new DynamicJson(await sr.ReadToEndAsync()); + dynamicContent = new DynamicJson(await sr.ReadToEndAsync().ConfigureAwait(false)); } } + else + { + //TODO(chamons) - Is this correct? + dynamicContent = new DynamicJson(""); + } return new DynamicResponse(res, dynamicContent); } - public DynamicResponse Send(CancellationToken cancellationToken = default) => SendAsync().GetAwaiter().GetResult(); - + /// + /// Send the request synchronously. + /// + /// The cancellation token. + /// The response dynamically typed in a . +#pragma warning disable AZC0107 // DO NOT call public asynchronous method in synchronous scope. + //TODO(chamons) - Is this correct? + public DynamicResponse Send(CancellationToken cancellationToken = default) => SendAsync(cancellationToken).EnsureCompleted(); +#pragma warning restore AZC0107 // DO NOT call public asynchronous method in synchronous scope. + + /// public override string ClientRequestId { get => Request.ClientRequestId; set => Request.ClientRequestId = value; } - public override void Dispose() => Request.Dispose(); + /// + public override void Dispose() + { + GC.SuppressFinalize(this); + Request.Dispose(); + } + /// protected override void AddHeader(string name, string value) => Request.Headers.Add(name, value); + /// protected override bool ContainsHeader(string name) => Request.Headers.Contains(name); + /// protected override IEnumerable EnumerateHeaders() => Request.Headers; + /// protected override bool RemoveHeader(string name) => Request.Headers.Remove(name); - protected override bool TryGetHeader(string name, [NotNullWhen(true)] out string value) => Request.Headers.TryGetValue(name, out value); + /// + protected override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => Request.Headers.TryGetValue(name, out value); - protected override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable values) => Request.Headers.TryGetValues(name, out values); + /// + protected override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) => Request.Headers.TryGetValues(name, out values); } } diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs index e0a3a65a1fa4..0cc5ffe47c58 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs @@ -1,26 +1,39 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; using System.Collections.Generic; using System.Text; namespace Azure.Core { + /// + /// Represents a result of Azure operation with a response. + /// public class DynamicResponse : Response { private Response Response { get; } + /// > public override DynamicJson Value { get; } - public dynamic Content => Value; + /// + /// The value returned by the service. + /// + public DynamicJson Content => Value; + + /// + public override Response GetRawResponse() => Response; + /// + /// Represents a result of Azure operation with a response. + /// + /// The response returned by the service. + /// The value returned by the service. public DynamicResponse(Response response, DynamicJson value) { Response = response; Value = value; } - - public override Response GetRawResponse() - { - return Response; - } } } From 607bdfb95e84d77ecf0ed109e35053fb53c97ae5 Mon Sep 17 00:00:00 2001 From: Chris Hamons Date: Mon, 1 Feb 2021 14:17:15 -0600 Subject: [PATCH 3/9] Code review changes --- sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs | 6 ++---- sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs | 5 ----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs index c3ab079b5ee6..e0233cb05985 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs @@ -76,10 +76,8 @@ public async Task SendAsync(CancellationToken cancellationToken if (res.ContentStream != null) { - using (StreamReader sr = new StreamReader(res.ContentStream, encoding: Utf8NoBom, detectEncodingFromByteOrderMarks:true, bufferSize: 1024, leaveOpen: true)) - { - dynamicContent = new DynamicJson(await sr.ReadToEndAsync().ConfigureAwait(false)); - } + JsonDocument doc = await JsonDocument.ParseAsync(res.ContentStream, new JsonDocumentOptions(), cancellationToken).ConfigureAwait(false); + dynamicContent = new DynamicJson(doc.RootElement); } else { diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs index 0cc5ffe47c58..647316aa7c6c 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs @@ -17,11 +17,6 @@ public class DynamicResponse : Response /// > public override DynamicJson Value { get; } - /// - /// The value returned by the service. - /// - public DynamicJson Content => Value; - /// public override Response GetRawResponse() => Response; From 628bf03e8415b9c39d389d2159489ccc9eec6edd Mon Sep 17 00:00:00 2001 From: Chris Hamons Date: Mon, 1 Feb 2021 14:44:25 -0600 Subject: [PATCH 4/9] Add DebuggerDisplay --- sdk/core/Azure.Core.Experimental/src/DynamicContent.cs | 2 ++ sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs | 3 +++ sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs | 2 ++ 3 files changed, 7 insertions(+) diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs b/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs index e12a28d4b49c..c77fd662469a 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Diagnostics; using System.IO; using System.Text; using System.Text.Json; @@ -13,6 +14,7 @@ namespace Azure.Core /// /// Represents the sent as part of the Azure.Core.Request. /// + [DebuggerDisplay("Content: {_body}")] public class DynamicContent : RequestContent { private readonly DynamicJson _body; diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs index e0233cb05985..b774cd43b917 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs @@ -5,9 +5,11 @@ using Azure.Core.Pipeline; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -16,6 +18,7 @@ namespace Azure.Core /// /// Represents an HTTP request with content. /// + [DebuggerDisplay("Body: {Body}")] public class DynamicRequest : Request { private Request Request { get; } diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs index 647316aa7c6c..194b17d9323a 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; namespace Azure.Core @@ -10,6 +11,7 @@ namespace Azure.Core /// /// Represents a result of Azure operation with a response. /// + [DebuggerDisplay("Status: {Response.Status}, Value: {Value}")] public class DynamicResponse : Response { private Response Response { get; } From 52a385670032175103aa69bf78875a7777b2667b Mon Sep 17 00:00:00 2001 From: Chris Hamons Date: Mon, 1 Feb 2021 15:20:03 -0600 Subject: [PATCH 5/9] Export API --- .../Azure.Core.Experimental.netstandard2.0.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs index 2094cc3c51cc..a0b583fbd898 100644 --- a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs @@ -1,5 +1,14 @@ namespace Azure.Core { + [System.Diagnostics.DebuggerDisplayAttribute("Content: {_body}")] + public partial class DynamicContent : Azure.Core.RequestContent + { + internal DynamicContent() { } + public override void Dispose() { } + public override bool TryComputeLength(out long length) { throw null; } + public override void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellation) { } + public override System.Threading.Tasks.Task WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellation) { throw null; } + } public partial class DynamicJson : System.Dynamic.IDynamicMetaObjectProvider { public DynamicJson(string json) { } @@ -56,6 +65,30 @@ public DynamicJson(System.Text.Json.JsonElement element) { } public override string ToString() { throw null; } public void WriteTo(System.Text.Json.Utf8JsonWriter writer) { } } + [System.Diagnostics.DebuggerDisplayAttribute("Body: {Body}")] + public partial class DynamicRequest : Azure.Core.Request + { + public DynamicRequest(Azure.Core.Request request, Azure.Core.Pipeline.HttpPipeline pipeline) { } + public Azure.Core.DynamicJson Body { get { throw null; } set { } } + public override string ClientRequestId { get { throw null; } set { } } + public override Azure.Core.RequestContent? Content { get { throw null; } set { } } + protected override void AddHeader(string name, string value) { } + protected override bool ContainsHeader(string name) { throw null; } + public override void Dispose() { } + protected override System.Collections.Generic.IEnumerable EnumerateHeaders() { throw null; } + protected override bool RemoveHeader(string name) { throw null; } + public Azure.Core.DynamicResponse Send(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.Task SendAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + protected override bool TryGetHeader(string name, out string? value) { throw null; } + protected override bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values) { throw null; } + } + [System.Diagnostics.DebuggerDisplayAttribute("Status: {Response.Status}, Value: {Value}")] + public partial class DynamicResponse : Azure.Response + { + public DynamicResponse(Azure.Response response, Azure.Core.DynamicJson value) { } + public override Azure.Core.DynamicJson Value { get { throw null; } } + public override Azure.Response GetRawResponse() { throw null; } + } } namespace Azure.Core.GeoJson { From 3e6a43b5bae8afbdb3148d957f263f85e3d885b7 Mon Sep 17 00:00:00 2001 From: Chris Hamons Date: Mon, 1 Feb 2021 16:32:03 -0600 Subject: [PATCH 6/9] More code review changes --- .../src/DynamicRequest.cs | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs index b774cd43b917..94dd371c966a 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs @@ -23,6 +23,7 @@ public class DynamicRequest : Request { private Request Request { get; } private HttpPipeline HttpPipeline { get; } + private bool _disposed; private static readonly Encoding Utf8NoBom = new UTF8Encoding(false, true); @@ -84,8 +85,7 @@ public async Task SendAsync(CancellationToken cancellationToken } else { - //TODO(chamons) - Is this correct? - dynamicContent = new DynamicJson(""); + dynamicContent = new DynamicJson(JsonDocument.Parse("null").RootElement); } return new DynamicResponse(res, dynamicContent); @@ -96,10 +96,26 @@ public async Task SendAsync(CancellationToken cancellationToken /// /// The cancellation token. /// The response dynamically typed in a . -#pragma warning disable AZC0107 // DO NOT call public asynchronous method in synchronous scope. - //TODO(chamons) - Is this correct? - public DynamicResponse Send(CancellationToken cancellationToken = default) => SendAsync(cancellationToken).EnsureCompleted(); -#pragma warning restore AZC0107 // DO NOT call public asynchronous method in synchronous scope. + public DynamicResponse Send(CancellationToken cancellationToken = default) + { + // Since we are sending the underlying request, we need to copy the Content on to it, or we'll lose the body. + Request.Content = Content; + + Response res = HttpPipeline.SendRequest(Request, cancellationToken); + DynamicJson dynamicContent; + + if (res.ContentStream != null) + { + JsonDocument doc = JsonDocument.Parse(res.ContentStream); + dynamicContent = new DynamicJson(doc.RootElement); + } + else + { + dynamicContent = new DynamicJson(JsonDocument.Parse("null").RootElement); + } + + return new DynamicResponse(res, dynamicContent); + } /// public override string ClientRequestId { get => Request.ClientRequestId; set => Request.ClientRequestId = value; } @@ -107,8 +123,22 @@ public async Task SendAsync(CancellationToken cancellationToken /// public override void Dispose() { + Dispose(true); GC.SuppressFinalize(this); - Request.Dispose(); + } + + /// + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + if (disposing) + { + Request.Dispose(); + } + _disposed = true; } /// From 720d564c9e74a26eaa16c4700249f44e20db3211 Mon Sep 17 00:00:00 2001 From: Chris Hamons Date: Mon, 1 Feb 2021 16:37:49 -0600 Subject: [PATCH 7/9] Export API and fix doccomments --- .../api/Azure.Core.Experimental.netstandard2.0.cs | 1 + sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs index a0b583fbd898..84c9098ae4a5 100644 --- a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs @@ -75,6 +75,7 @@ public DynamicRequest(Azure.Core.Request request, Azure.Core.Pipeline.HttpPipeli protected override void AddHeader(string name, string value) { } protected override bool ContainsHeader(string name) { throw null; } public override void Dispose() { } + protected virtual void Dispose(bool disposing) { } protected override System.Collections.Generic.IEnumerable EnumerateHeaders() { throw null; } protected override bool RemoveHeader(string name) { throw null; } public Azure.Core.DynamicResponse Send(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs index 94dd371c966a..72905d91938f 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs @@ -120,14 +120,19 @@ public DynamicResponse Send(CancellationToken cancellationToken = default) /// public override string ClientRequestId { get => Request.ClientRequestId; set => Request.ClientRequestId = value; } - /// + /// + /// Frees resources held by the object. + /// public override void Dispose() { Dispose(true); GC.SuppressFinalize(this); } - /// + /// + /// Frees resources held by the object. + /// + /// true if we should dispose, otherwise false protected virtual void Dispose(bool disposing) { if (_disposed) From 076b06dc856b686512b835a4d1584b9fecd40a84 Mon Sep 17 00:00:00 2001 From: Chris Hamons Date: Wed, 3 Feb 2021 12:17:23 -0600 Subject: [PATCH 8/9] Fix nullability issue by being Response not Response --- .../src/DynamicRequest.cs | 12 +--- .../src/DynamicResponse.cs | 60 ++++++++++++++++--- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs index 72905d91938f..a68fda4b0186 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs @@ -76,17 +76,13 @@ public async Task SendAsync(CancellationToken cancellationToken Request.Content = Content; Response res = await HttpPipeline.SendRequestAsync(Request, cancellationToken).ConfigureAwait(false); - DynamicJson dynamicContent; + DynamicJson? dynamicContent = null; if (res.ContentStream != null) { JsonDocument doc = await JsonDocument.ParseAsync(res.ContentStream, new JsonDocumentOptions(), cancellationToken).ConfigureAwait(false); dynamicContent = new DynamicJson(doc.RootElement); } - else - { - dynamicContent = new DynamicJson(JsonDocument.Parse("null").RootElement); - } return new DynamicResponse(res, dynamicContent); } @@ -102,17 +98,13 @@ public DynamicResponse Send(CancellationToken cancellationToken = default) Request.Content = Content; Response res = HttpPipeline.SendRequest(Request, cancellationToken); - DynamicJson dynamicContent; + DynamicJson? dynamicContent = null; if (res.ContentStream != null) { JsonDocument doc = JsonDocument.Parse(res.ContentStream); dynamicContent = new DynamicJson(doc.RootElement); } - else - { - dynamicContent = new DynamicJson(JsonDocument.Parse("null").RootElement); - } return new DynamicResponse(res, dynamicContent); } diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs index 194b17d9323a..e1cfeadd8c93 100644 --- a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs +++ b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Text; namespace Azure.Core @@ -12,25 +14,69 @@ namespace Azure.Core /// Represents a result of Azure operation with a response. /// [DebuggerDisplay("Status: {Response.Status}, Value: {Value}")] - public class DynamicResponse : Response + public class DynamicResponse : Response { + private bool _disposed; + private Response Response { get; } - /// > - public override DynamicJson Value { get; } + /// + /// The JSON body of the response. + /// + public DynamicJson? Body { get; } /// - public override Response GetRawResponse() => Response; + public override int Status => Response.Status; + /// + public override string ReasonPhrase => Response.ReasonPhrase; + /// + public override Stream? ContentStream { get => Response.ContentStream; set => Response.ContentStream = value; } + /// + public override string ClientRequestId { get => Response.ClientRequestId; set => Response.ClientRequestId = value; } + /// + protected override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => Response.Headers.TryGetValue(name, out value); + /// + protected override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) => Response.Headers.TryGetValues(name, out values); + /// + protected override bool ContainsHeader(string name) => Response.Headers.Contains(name); + /// + protected override IEnumerable EnumerateHeaders() => Response.Headers; /// /// Represents a result of Azure operation with a response. /// /// The response returned by the service. - /// The value returned by the service. - public DynamicResponse(Response response, DynamicJson value) + /// The body returned by the service. + public DynamicResponse(Response response, DynamicJson? body) { Response = response; - Value = value; + Body = body; + } + + /// + /// Frees resources held by the object. + /// + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Frees resources held by the object. + /// + /// true if we should dispose, otherwise false + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + if (disposing) + { + Response.Dispose(); + } + _disposed = true; } } } From 98738077d190ca3347914997e41638cda8902810 Mon Sep 17 00:00:00 2001 From: Chris Hamons Date: Wed, 3 Feb 2021 14:59:24 -0600 Subject: [PATCH 9/9] Export API --- .../Azure.Core.Experimental.netstandard2.0.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs index 84c9098ae4a5..b844ac2d1a80 100644 --- a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs @@ -84,11 +84,20 @@ protected virtual void Dispose(bool disposing) { } protected override bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values) { throw null; } } [System.Diagnostics.DebuggerDisplayAttribute("Status: {Response.Status}, Value: {Value}")] - public partial class DynamicResponse : Azure.Response + public partial class DynamicResponse : Azure.Response { - public DynamicResponse(Azure.Response response, Azure.Core.DynamicJson value) { } - public override Azure.Core.DynamicJson Value { get { throw null; } } - public override Azure.Response GetRawResponse() { throw null; } + public DynamicResponse(Azure.Response response, Azure.Core.DynamicJson? body) { } + public Azure.Core.DynamicJson? Body { get { throw null; } } + public override string ClientRequestId { get { throw null; } set { } } + public override System.IO.Stream? ContentStream { get { throw null; } set { } } + public override string ReasonPhrase { get { throw null; } } + public override int Status { get { throw null; } } + protected override bool ContainsHeader(string name) { throw null; } + public override void Dispose() { } + protected virtual void Dispose(bool disposing) { } + protected override System.Collections.Generic.IEnumerable EnumerateHeaders() { throw null; } + protected override bool TryGetHeader(string name, out string? value) { throw null; } + protected override bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values) { throw null; } } } namespace Azure.Core.GeoJson