Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for .NET 5 #2169

Merged
merged 18 commits into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
bdc69c7
Merge pull request #1851 from Azure/abmisr/preview_net5_FI
abhipsaMisra Mar 23, 2021
22d11cc
feat(all): Add support for .NET 5.0
abhipsaMisra Mar 18, 2021
e84af40
refactor(all): Update APIs to follow .NET 5.0 patterns
abhipsaMisra Mar 18, 2021
04d77f9
refactor(e2e-tests): Update E2E tests to follow .NET 5.0 patterns
abhipsaMisra Mar 18, 2021
3a5f736
refactor(all): Simplify disposal of IDisposable X509Certificate
abhipsaMisra Mar 18, 2021
f4fb416
fix(vsts): Update the location for log analytics workspace resource
abhipsaMisra Mar 22, 2021
35e4091
Merge remote-tracking branch 'origin' into abmisr/previewNet5FI
abhipsaMisra Mar 26, 2021
2a23367
Merge pull request #1863 from Azure/abmisr/previewNet5FI
abhipsaMisra Mar 27, 2021
49e4f4f
Merge branch 'master' into abmisr/net5FI2
abhipsaMisra Apr 19, 2021
8bc9702
refactor(e2e-tests): Dispose IDisposable types
abhipsaMisra Apr 19, 2021
204759b
Merge pull request #1891 from Azure/abmisr/net5FI2
abhipsaMisra Apr 19, 2021
aab69e5
Merge branch 'master' into abmisr/net5FI
abhipsaMisra Apr 20, 2021
49b0d78
Merge pull request #1898 from Azure/abmisr/net5FI
abhipsaMisra Apr 20, 2021
a388326
Merge branch 'master' into abmisr/previewNet5FI
abhipsaMisra May 17, 2021
4964f3a
fix(e2e-tests): Dispose IDisposable types created - hmac
abhipsaMisra May 18, 2021
e420d1d
Merge pull request #1964 from Azure/abmisr/previewNet5FI
abhipsaMisra May 18, 2021
6ba6889
Merge branch 'master' into abmist/net5UpdateFromMaster
abhipsaMisra Sep 15, 2021
bb06d18
Bring updates from master to net5 feature branch
abhipsaMisra Sep 15, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions common/src/HttpContentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Azure.Devices.Shared
{
/// <summary>
/// Extensions added to simplify the usage of <see cref="HttpContent"/> APIs based on the .NET implementation used.
/// </summary>
internal static class HttpContentExtensions
{
internal static async Task CopyToStreamAsync(this HttpContent content, Stream stream, CancellationToken cancellationToken)
{
#if NET5_0
await content.CopyToAsync(stream, cancellationToken).ConfigureAwait(false);
#else
// .NET implementations < .NET 5.0 do not support CancellationTokens for HttpContent APIs, so we will discard it.
_ = cancellationToken;

await content.CopyToAsync(stream).ConfigureAwait(false);
#endif
}

internal static Task<Stream> ReadHttpContentAsStream(this HttpContent httpContent, CancellationToken cancellationToken)
{
#if NET5_0
return httpContent.ReadAsStreamAsync(cancellationToken);
#else
// .NET implementations < .NET 5.0 do not support CancellationTokens for HttpContent APIs, so we will discard it.
_ = cancellationToken;

return httpContent.ReadAsStreamAsync();
#endif
}

internal static Task<byte[]> ReadHttpContentAsByteArrayAsync(this HttpContent content, CancellationToken cancellationToken)
{
#if NET5_0
return content.ReadAsByteArrayAsync(cancellationToken);
#else
// .NET implementations < .NET 5.0 do not support CancellationTokens for HttpContent APIs, so we will discard it.
_ = cancellationToken;

return content.ReadAsByteArrayAsync();
#endif
}

internal static Task<string> ReadHttpContentAsStringAsync(this HttpContent content, CancellationToken cancellationToken)
{
#if NET5_0
return content.ReadAsStringAsync(cancellationToken);
#else
// .NET implementations < .NET 5.0 do not support CancellationTokens for HttpContent APIs, so we will discard it.
_ = cancellationToken;

return content.ReadAsStringAsync();
#endif
}

}
}
35 changes: 35 additions & 0 deletions common/src/HttpMessageHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Net.Http;
using System.Text;
using Newtonsoft.Json;

#if NET451
using System.Net.Http.Formatting;
#endif

namespace Microsoft.Azure.Devices.Shared
{
/// <summary>
/// A helper class to simplify operations with Http messages based on the .NET implementation used.
/// </summary>
internal static class HttpMessageHelper
{
#if NET451
private static readonly JsonMediaTypeFormatter s_jsonFormatter = new JsonMediaTypeFormatter();
#else
private const string ApplicationJson = "application/json";
#endif

internal static void SetHttpRequestMessageContent<T>(HttpRequestMessage requestMessage, T entity)
{
#if NET451
requestMessage.Content = new ObjectContent<T>(entity, s_jsonFormatter);
#else
string str = JsonConvert.SerializeObject(entity);
requestMessage.Content = new StringContent(str, Encoding.UTF8, ApplicationJson);
#endif
}
}
}
24 changes: 24 additions & 0 deletions common/src/StreamExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Azure.Devices.Shared
{
/// <summary>
/// Extensions added to simplify the usage of <see cref="Stream"/> APIs based on the .NET implementation used.
/// </summary>
internal static class StreamExtensions
{
internal static async Task WriteToStreamAsync(this Stream stream, byte[] requestBytes, CancellationToken cancellationToken)
{
#if NET451 || NET472 || NETSTANDARD2_0
await stream.WriteAsync(requestBytes, 0, requestBytes.Length, cancellationToken).ConfigureAwait(false);
#else
await stream.WriteAsync(requestBytes, cancellationToken).ConfigureAwait(false);
#endif
}
}
}
27 changes: 27 additions & 0 deletions common/src/TaskCompletionSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Threading.Tasks;

namespace Microsoft.Azure.Devices.Shared
{
/// <summary>
/// A <see cref="TaskCompletionSource{boolean}"/> implementation that returns a <see cref="Task"/> when completed.
/// </summary>
/// <remarks>
/// Represents the producer side of a <see cref="Task"/> unbound to a delegate, providing access to the consumer side through the <see cref="Task"/> property.
/// This is used for .NET implementations lower than .NET 5.0, which lack a native implementation of the non-generic TaskCompletionSource.
/// </remarks>
internal sealed class TaskCompletionSource : TaskCompletionSource<bool>
{
public TaskCompletionSource()
{
}

public bool TrySetResult() => TrySetResult(true);

public void SetResult() => SetResult(true);

public override string ToString() => $"TaskCompletionSource[status: {Task.Status}]";
}
}
59 changes: 14 additions & 45 deletions common/src/service/HttpClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Common;
Expand All @@ -19,19 +18,13 @@
using Microsoft.Azure.Devices.Shared;
using Newtonsoft.Json;

#if NET451
using System.Net.Http.Formatting;
#endif
using static Microsoft.Azure.Devices.Shared.HttpMessageHelper;

namespace Microsoft.Azure.Devices
{
internal sealed class HttpClientHelper : IHttpClientHelper
{
private const string ApplicationJson = "application/json";

#if NET451
static readonly JsonMediaTypeFormatter JsonFormatter = new JsonMediaTypeFormatter();
#endif
private readonly Uri _baseAddress;
private readonly IAuthorizationHeaderProvider _authenticationHeaderProvider;
private readonly IReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> _defaultErrorMapping;
Expand Down Expand Up @@ -179,12 +172,8 @@ await ExecuteAsync(
(requestMsg, token) =>
{
InsertEtag(requestMsg, entity, operationType);
#if NET451
requestMsg.Content = new ObjectContent<T>(entity, JsonFormatter);
#else
string str = JsonConvert.SerializeObject(entity);
requestMsg.Content = new StringContent(str, Encoding.UTF8, ApplicationJson);
#endif
SetHttpRequestMessageContent(requestMsg, entity);

return Task.FromResult(0);
},
async (httpClient, token) => result = await ReadResponseMessageAsync<T>(httpClient, token).ConfigureAwait(false),
Expand All @@ -208,12 +197,7 @@ await ExecuteAsync(
new Uri(_baseAddress, requestUri),
(requestMsg, token) =>
{
#if NET451
requestMsg.Content = new ObjectContent<T>(entity, JsonFormatter);
#else
string str = JsonConvert.SerializeObject(entity);
requestMsg.Content = new StringContent(str, System.Text.Encoding.UTF8, ApplicationJson);
#endif
SetHttpRequestMessageContent<T>(requestMsg, entity);
return Task.FromResult(0);
},
async (httpClient, token) => result = await ReadResponseMessageAsync<T2>(httpClient, token).ConfigureAwait(false),
Expand All @@ -237,12 +221,8 @@ await ExecuteAsync(
(requestMsg, token) =>
{
InsertEtag(requestMsg, etag, operationType);
#if NET451
requestMsg.Content = new ObjectContent<T>(entity, JsonFormatter);
#else
string str = Newtonsoft.Json.JsonConvert.SerializeObject(entity);
requestMsg.Content = new StringContent(str, System.Text.Encoding.UTF8, ApplicationJson);
#endif
SetHttpRequestMessageContent<T>(requestMsg, entity);

return Task.FromResult(0);
},
null,
Expand All @@ -267,12 +247,8 @@ await ExecuteAsync(
{
// TODO: skintali: Use string etag when service side changes are ready
InsertEtag(requestMsg, etag, operationType);
#if NET451
requestMsg.Content = new ObjectContent<T>(entity, JsonFormatter);
#else
string str = JsonConvert.SerializeObject(entity);
requestMsg.Content = new StringContent(str, Encoding.UTF8, ApplicationJson);
#endif
SetHttpRequestMessageContent<T>(requestMsg, entity);

return Task.FromResult(0);
},
async (httpClient, token) => result = await ReadResponseMessageAsync<T2>(httpClient, token).ConfigureAwait(false),
Expand All @@ -291,12 +267,8 @@ await ExecuteAsync(
(requestMsg, token) =>
{
InsertEtag(requestMsg, etag, PutOperationType.UpdateEntity);
#if NET451
requestMsg.Content = new ObjectContent<T>(entity, JsonFormatter);
#else
string str = JsonConvert.SerializeObject(entity);
requestMsg.Content = new StringContent(str, Encoding.UTF8, ApplicationJson);
#endif
SetHttpRequestMessageContent<T>(requestMsg, entity);

return Task.FromResult(0);
},
null,
Expand All @@ -317,12 +289,8 @@ await ExecuteAsync(
(requestMsg, token) =>
{
InsertEtag(requestMsg, etag, putOperationType);
#if NET451
requestMsg.Content = new ObjectContent<T>(entity, JsonFormatter);
#else
string str = JsonConvert.SerializeObject(entity);
requestMsg.Content = new StringContent(str, System.Text.Encoding.UTF8, ApplicationJson);
#endif
SetHttpRequestMessageContent<T>(requestMsg, entity);

return Task.FromResult(0);
},
async (httpClient, token) => result = await ReadResponseMessageAsync<T2>(httpClient, token).ConfigureAwait(false),
Expand All @@ -342,9 +310,10 @@ private static async Task<T> ReadResponseMessageAsync<T>(HttpResponseMessage mes
#if NET451
T entity = await message.Content.ReadAsAsync<T>(token).ConfigureAwait(false);
#else
string str = await message.Content.ReadAsStringAsync().ConfigureAwait(false);
string str = await message.Content.ReadHttpContentAsStringAsync(token).ConfigureAwait(false);
T entity = JsonConvert.DeserializeObject<T>(str);
#endif

// Etag in the header is considered authoritative
var eTagHolder = entity as IETagHolder;
if (eTagHolder != null)
Expand Down
4 changes: 0 additions & 4 deletions e2e/test/E2EMsTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down
4 changes: 2 additions & 2 deletions e2e/test/E2ETests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1.18;net472;net451</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netcoreapp3.1;netcoreapp2.1.18</TargetFrameworks>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1.18;net472;net451</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">net5.0;netcoreapp3.1;netcoreapp2.1.18</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
Expand Down
2 changes: 1 addition & 1 deletion e2e/test/config/TestConfiguration.IoTHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private static string GenerateSasToken(string resourceUri, string sharedAccessKe

string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry;

HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(sharedAccessKey));
using var hmac = new HMACSHA256(Convert.FromBase64String(sharedAccessKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

// SharedAccessSignature sr=ENCODED(dh://myiothub.azure-devices.net/a/b/c?myvalue1=a)&sig=<Signature>&se=<ExpiresOnValue>[&skn=<KeyName>]
Expand Down
2 changes: 1 addition & 1 deletion e2e/test/helpers/CustomWebProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Helpers
public class CustomWebProxy : IWebProxy
{
private readonly MsTestLogger _logger;
private long _counter = 0;
private long _counter;

public CustomWebProxy(MsTestLogger logger)
{
Expand Down
2 changes: 1 addition & 1 deletion e2e/test/helpers/StorageContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Helpers
/// </summary>
public class StorageContainer : IDisposable
{
private bool _disposed = false;
private bool _disposed;

public string ContainerName { get; }
public Uri Uri { get; private set; }
Expand Down
Loading