From 7713bc4c32f99db5353d54491bb74ca7722889ee Mon Sep 17 00:00:00 2001 From: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 25 Sep 2023 14:57:48 +0300 Subject: [PATCH 1/2] Fix warnings and optimize constructor calls --- src/Passwordless/IPasswordlessClient.cs | 6 +- src/Passwordless/Models/RegisterOptions.cs | 4 +- .../Models/RegisterTokenResponse.cs | 4 +- src/Passwordless/Passwordless.csproj | 5 - src/Passwordless/PasswordlessApiException.cs | 2 - src/Passwordless/PasswordlessClient.cs | 108 ++++--- .../PasswordlessDelegatingHandler.cs | 1 - .../PasswordlessHttpRequestExtensions.cs | 2 - src/Passwordless/Pollyfill.cs | 269 +++++++++--------- .../ServiceCollectionExtensions.cs | 7 +- .../PasswordlessClientTests.cs | 1 - 11 files changed, 211 insertions(+), 198 deletions(-) diff --git a/src/Passwordless/IPasswordlessClient.cs b/src/Passwordless/IPasswordlessClient.cs index 5e21326..4ea635a 100644 --- a/src/Passwordless/IPasswordlessClient.cs +++ b/src/Passwordless/IPasswordlessClient.cs @@ -12,7 +12,6 @@ public interface IPasswordlessClient /// /// /// - /// Task AddAliasAsync(AddAliasRequest request, CancellationToken cancellationToken = default); /// @@ -43,6 +42,11 @@ public interface IPasswordlessClient /// An exception containing details abaout the reason for failure. Task DeleteCredentialAsync(byte[] id, CancellationToken cancellationToken = default); + /// + /// Gets the users count. + /// + Task GetUsersCountAsync(CancellationToken cancellationToken = default); + /// /// List all the for a given user. /// diff --git a/src/Passwordless/Models/RegisterOptions.cs b/src/Passwordless/Models/RegisterOptions.cs index f7aecb1..06f1203 100644 --- a/src/Passwordless/Models/RegisterOptions.cs +++ b/src/Passwordless/Models/RegisterOptions.cs @@ -1,6 +1,4 @@ -using System.Text.Json.Serialization; - -namespace Passwordless; +namespace Passwordless; /// /// diff --git a/src/Passwordless/Models/RegisterTokenResponse.cs b/src/Passwordless/Models/RegisterTokenResponse.cs index 41c2e9a..19989a2 100644 --- a/src/Passwordless/Models/RegisterTokenResponse.cs +++ b/src/Passwordless/Models/RegisterTokenResponse.cs @@ -1,6 +1,4 @@ -using System.Text.Json.Serialization; - -namespace Passwordless.Models; +namespace Passwordless.Models; public class RegisterTokenResponse { diff --git a/src/Passwordless/Passwordless.csproj b/src/Passwordless/Passwordless.csproj index f187606..24a033c 100644 --- a/src/Passwordless/Passwordless.csproj +++ b/src/Passwordless/Passwordless.csproj @@ -23,11 +23,6 @@ embedded - - - $(DefineConstants);INCLUDE_DYNAMICALLY_ACCESSED_MEMBERS - - diff --git a/src/Passwordless/PasswordlessApiException.cs b/src/Passwordless/PasswordlessApiException.cs index 2fb6858..91c1294 100644 --- a/src/Passwordless/PasswordlessApiException.cs +++ b/src/Passwordless/PasswordlessApiException.cs @@ -1,5 +1,3 @@ -using System.Diagnostics; -using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/src/Passwordless/PasswordlessClient.cs b/src/Passwordless/PasswordlessClient.cs index d5c9b01..fea7a9b 100644 --- a/src/Passwordless/PasswordlessClient.cs +++ b/src/Passwordless/PasswordlessClient.cs @@ -7,140 +7,157 @@ namespace Passwordless; -/// -/// TODO: FILL IN -/// +/// [DebuggerDisplay("{DebuggerToString(),nq}")] public class PasswordlessClient : IPasswordlessClient, IDisposable { - private readonly bool _needsDisposing; private readonly HttpClient _client; + private readonly bool _disposeClient; - public static PasswordlessClient Create(PasswordlessOptions options, IHttpClientFactory factory) + /// + /// Initializes an instance of using the provided + /// and . + /// + public static PasswordlessClient Create(PasswordlessOptions options, IHttpClientFactory factory) => + new(factory.CreateClient(), options, false); + + private PasswordlessClient(HttpClient client, bool disposeClient) + { + _client = client; + _disposeClient = disposeClient; + } + + private PasswordlessClient(HttpClient client, PasswordlessOptions options, bool disposeClient) + : this(client, disposeClient) { - var client = factory.CreateClient(); client.BaseAddress = new Uri(options.ApiUrl); client.DefaultRequestHeaders.Add("ApiSecret", options.ApiSecret); - return new PasswordlessClient(client); } - public PasswordlessClient(PasswordlessOptions passwordlessOptions) + /// + /// Initializes an instance of . + /// + public PasswordlessClient(HttpClient client) + : this(client, false) { - _needsDisposing = true; - _client = new HttpClient - { - BaseAddress = new Uri(passwordlessOptions.ApiUrl), - }; - _client.DefaultRequestHeaders.Add("ApiSecret", passwordlessOptions.ApiSecret); } - public PasswordlessClient(HttpClient client) + /// + /// Initializes an instance of . + /// + public PasswordlessClient(PasswordlessOptions options) + : this(new HttpClient(), options, true) { - _needsDisposing = false; - _client = client; } - /// + /// public async Task AddAliasAsync(AddAliasRequest request, CancellationToken cancellationToken) { - var res = await _client.PostAsJsonAsync("alias", + using var response = await _client.PostAsJsonAsync("alias", request, PasswordlessSerializerContext.Default.AddAliasRequest, cancellationToken); - res.EnsureSuccessStatusCode(); + + response.EnsureSuccessStatusCode(); } - /// + /// public async Task CreateRegisterTokenAsync(RegisterOptions registerOptions, CancellationToken cancellationToken = default) { - var res = await _client.PostAsJsonAsync("register/token", + using var response = await _client.PostAsJsonAsync("register/token", registerOptions, PasswordlessSerializerContext.Default.RegisterOptions, cancellationToken); - res.EnsureSuccessStatusCode(); - return (await res.Content.ReadFromJsonAsync( + + response.EnsureSuccessStatusCode(); + + return (await response.Content.ReadFromJsonAsync( PasswordlessSerializerContext.Default.RegisterTokenResponse, cancellationToken))!; } - /// + /// public async Task VerifyTokenAsync(string verifyToken, CancellationToken cancellationToken = default) { - var request = new HttpRequestMessage(HttpMethod.Post, "signin/verify") - { - // TODO: No JsonTypeInfo overload yet? - Content = JsonContent.Create(new VerifyTokenRequest(verifyToken)), - }; + using var request = new HttpRequestMessage(HttpMethod.Post, "signin/verify"); + + // TODO: No JsonTypeInfo overload yet? + request.Content = JsonContent.Create(new VerifyTokenRequest(verifyToken)); // We just want to return null if there is a problem. request.SkipErrorHandling(); - var response = await _client.SendAsync(request, cancellationToken); + using var response = await _client.SendAsync(request, cancellationToken); if (response.IsSuccessStatusCode) { var res = await response.Content.ReadFromJsonAsync( PasswordlessSerializerContext.Default.VerifiedUser, cancellationToken); + return res; } return null; } - /// + /// public async Task DeleteUserAsync(string userId, CancellationToken cancellationToken = default) { - await _client.PostAsJsonAsync("users/delete", + using var response = await _client.PostAsJsonAsync("users/delete", new DeleteUserRequest(userId), PasswordlessSerializerContext.Default.DeleteUserRequest, cancellationToken); } - /// + /// public async Task> ListUsersAsync(CancellationToken cancellationToken = default) { var response = await _client.GetFromJsonAsync( "users/list", PasswordlessSerializerContext.Default.ListResponsePasswordlessUserSummary, cancellationToken); + return response!.Values; } - /// + /// public async Task> ListAliasesAsync(string userId, CancellationToken cancellationToken = default) { var response = await _client.GetFromJsonAsync( $"alias/list?userid={userId}", PasswordlessSerializerContext.Default.ListResponseAliasPointer, cancellationToken); + return response!.Values; } - /// + /// public async Task> ListCredentialsAsync(string userId, CancellationToken cancellationToken = default) { var response = await _client.GetFromJsonAsync( $"credentials/list?userid={userId}", PasswordlessSerializerContext.Default.ListResponseCredential, cancellationToken); + return response!.Values; } - /// + /// public async Task DeleteCredentialAsync(string id, CancellationToken cancellationToken = default) { - await _client.PostAsJsonAsync("credentials/delete", + using var response = await _client.PostAsJsonAsync("credentials/delete", new DeleteCredentialRequest(id), PasswordlessSerializerContext.Default.DeleteCredentialRequest, cancellationToken); } - /// + /// public async Task DeleteCredentialAsync(byte[] id, CancellationToken cancellationToken = default) { await DeleteCredentialAsync(Base64Url.Encode(id), cancellationToken); } + /// public async Task GetUsersCountAsync(CancellationToken cancellationToken = default) { return (await _client.GetFromJsonAsync( @@ -174,18 +191,19 @@ private string DebuggerToString() return sb.ToString(); } + /// public void Dispose() { - if (_needsDisposing) - { - Dispose(true); - GC.SuppressFinalize(this); - } + Dispose(true); + GC.SuppressFinalize(this); } + /// + /// Releases unmanaged resources. + /// protected virtual void Dispose(bool disposing) { - if (disposing) + if (disposing && _disposeClient) { _client.Dispose(); } diff --git a/src/Passwordless/PasswordlessDelegatingHandler.cs b/src/Passwordless/PasswordlessDelegatingHandler.cs index f467ed1..bdc0387 100644 --- a/src/Passwordless/PasswordlessDelegatingHandler.cs +++ b/src/Passwordless/PasswordlessDelegatingHandler.cs @@ -1,4 +1,3 @@ -using System.Net.Http; using System.Net.Http.Json; using Passwordless.Helpers; diff --git a/src/Passwordless/PasswordlessHttpRequestExtensions.cs b/src/Passwordless/PasswordlessHttpRequestExtensions.cs index 0347c6a..a3aaaa7 100644 --- a/src/Passwordless/PasswordlessHttpRequestExtensions.cs +++ b/src/Passwordless/PasswordlessHttpRequestExtensions.cs @@ -1,5 +1,3 @@ -using System.Net.Http; - namespace Passwordless; internal static class PasswordlessHttpRequestExtensions diff --git a/src/Passwordless/Pollyfill.cs b/src/Passwordless/Pollyfill.cs index 3b9f508..559ed10 100644 --- a/src/Passwordless/Pollyfill.cs +++ b/src/Passwordless/Pollyfill.cs @@ -1,143 +1,144 @@ +#if !NET6_0_OR_GREATER + // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace System.Diagnostics.CodeAnalysis +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Indicates that certain members on a specified are accessed dynamically, +/// for example through . +/// +/// +/// This allows tools to understand which members are being accessed during the execution +/// of a program. +/// +/// This attribute is valid on members whose type is or . +/// +/// When this attribute is applied to a location of type , the assumption is +/// that the string represents a fully qualified type name. +/// +/// When this attribute is applied to a class, interface, or struct, the members specified +/// can be accessed dynamically on instances returned from calling +/// on instances of that class, interface, or struct. +/// +/// If the attribute is applied to a method it's treated as a special case and it implies +/// the attribute should be applied to the "this" parameter of the method. As such the attribute +/// should only be used on instance methods of types assignable to System.Type (or string, but no methods +/// will use it there). +/// +[AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] +internal sealed class DynamicallyAccessedMembersAttribute : Attribute { -#if INCLUDE_DYNAMICALLY_ACCESSED_MEMBERS - /// - /// Indicates that certain members on a specified are accessed dynamically, - /// for example through . - /// - /// - /// This allows tools to understand which members are being accessed during the execution - /// of a program. - /// - /// This attribute is valid on members whose type is or . - /// - /// When this attribute is applied to a location of type , the assumption is - /// that the string represents a fully qualified type name. - /// - /// When this attribute is applied to a class, interface, or struct, the members specified - /// can be accessed dynamically on instances returned from calling - /// on instances of that class, interface, or struct. - /// - /// If the attribute is applied to a method it's treated as a special case and it implies - /// the attribute should be applied to the "this" parameter of the method. As such the attribute - /// should only be used on instance methods of types assignable to System.Type (or string, but no methods - /// will use it there). - /// - [AttributeUsage( - AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | - AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, - Inherited = false)] - internal sealed class DynamicallyAccessedMembersAttribute : Attribute + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) { - /// - /// Initializes a new instance of the class - /// with the specified member types. - /// - /// The types of members dynamically accessed. - public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) - { - MemberTypes = memberTypes; - } - - /// - /// Gets the which specifies the type - /// of members dynamically accessed. - /// - public DynamicallyAccessedMemberTypes MemberTypes { get; } + MemberTypes = memberTypes; } /// - /// Specifies the types of members that are dynamically accessed. - /// - /// This enumeration has a attribute that allows a - /// bitwise combination of its member values. + /// Gets the which specifies the type + /// of members dynamically accessed. /// - [Flags] - internal enum DynamicallyAccessedMemberTypes - { - /// - /// Specifies no members. - /// - None = 0, - - /// - /// Specifies the default, parameterless public constructor. - /// - PublicParameterlessConstructor = 0x0001, - - /// - /// Specifies all public constructors. - /// - PublicConstructors = 0x0002 | PublicParameterlessConstructor, - - /// - /// Specifies all non-public constructors. - /// - NonPublicConstructors = 0x0004, - - /// - /// Specifies all public methods. - /// - PublicMethods = 0x0008, - - /// - /// Specifies all non-public methods. - /// - NonPublicMethods = 0x0010, - - /// - /// Specifies all public fields. - /// - PublicFields = 0x0020, - - /// - /// Specifies all non-public fields. - /// - NonPublicFields = 0x0040, - - /// - /// Specifies all public nested types. - /// - PublicNestedTypes = 0x0080, - - /// - /// Specifies all non-public nested types. - /// - NonPublicNestedTypes = 0x0100, - - /// - /// Specifies all public properties. - /// - PublicProperties = 0x0200, - - /// - /// Specifies all non-public properties. - /// - NonPublicProperties = 0x0400, - - /// - /// Specifies all public events. - /// - PublicEvents = 0x0800, - - /// - /// Specifies all non-public events. - /// - NonPublicEvents = 0x1000, - - /// - /// Specifies all interfaces implemented by the type. - /// - Interfaces = 0x2000, - - /// - /// Specifies all members. - /// - All = ~None - } -#endif -} \ No newline at end of file + public DynamicallyAccessedMemberTypes MemberTypes { get; } +} + +/// +/// Specifies the types of members that are dynamically accessed. +/// +/// This enumeration has a attribute that allows a +/// bitwise combination of its member values. +/// +[Flags] +internal enum DynamicallyAccessedMemberTypes +{ + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all members. + /// + All = ~None +} + +#endif \ No newline at end of file diff --git a/src/Passwordless/ServiceCollectionExtensions.cs b/src/Passwordless/ServiceCollectionExtensions.cs index 8febcb3..b228445 100644 --- a/src/Passwordless/ServiceCollectionExtensions.cs +++ b/src/Passwordless/ServiceCollectionExtensions.cs @@ -1,13 +1,18 @@ using System.Diagnostics.CodeAnalysis; -using System.Net.Http; using Microsoft.Extensions.Options; using Passwordless; // This is a trick to always show up in a class when people are registering services namespace Microsoft.Extensions.DependencyInjection; +/// +/// Service registration extensions for Passwordless. +/// public static class ServiceCollectionExtensions { + /// + /// Adds and configures Passwordless-related services. + /// public static IServiceCollection AddPasswordlessSdk(this IServiceCollection services, Action configureOptions) { services.AddOptions() diff --git a/tests/Passwordless.Tests/PasswordlessClientTests.cs b/tests/Passwordless.Tests/PasswordlessClientTests.cs index 032f20f..1fa3990 100644 --- a/tests/Passwordless.Tests/PasswordlessClientTests.cs +++ b/tests/Passwordless.Tests/PasswordlessClientTests.cs @@ -1,4 +1,3 @@ -using System.Net.Http; using System.Text.Json; using Microsoft.Extensions.DependencyInjection; From 91a9cc28121dbbf3c0d393edcd5f04d786422472 Mon Sep 17 00:00:00 2001 From: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 25 Sep 2023 20:19:23 +0300 Subject: [PATCH 2/2] Revert ctor changes --- src/Passwordless/PasswordlessClient.cs | 31 ++++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/Passwordless/PasswordlessClient.cs b/src/Passwordless/PasswordlessClient.cs index fea7a9b..dae2d34 100644 --- a/src/Passwordless/PasswordlessClient.cs +++ b/src/Passwordless/PasswordlessClient.cs @@ -15,39 +15,36 @@ public class PasswordlessClient : IPasswordlessClient, IDisposable private readonly bool _disposeClient; /// - /// Initializes an instance of using the provided - /// and . + /// Initializes an instance of . /// - public static PasswordlessClient Create(PasswordlessOptions options, IHttpClientFactory factory) => - new(factory.CreateClient(), options, false); - - private PasswordlessClient(HttpClient client, bool disposeClient) - { - _client = client; - _disposeClient = disposeClient; - } - - private PasswordlessClient(HttpClient client, PasswordlessOptions options, bool disposeClient) - : this(client, disposeClient) + public static PasswordlessClient Create(PasswordlessOptions options, IHttpClientFactory factory) { + var client = factory.CreateClient(); client.BaseAddress = new Uri(options.ApiUrl); client.DefaultRequestHeaders.Add("ApiSecret", options.ApiSecret); + return new PasswordlessClient(client); } /// /// Initializes an instance of . /// - public PasswordlessClient(HttpClient client) - : this(client, false) + public PasswordlessClient(PasswordlessOptions passwordlessOptions) { + _client = new HttpClient + { + BaseAddress = new Uri(passwordlessOptions.ApiUrl), + }; + _client.DefaultRequestHeaders.Add("ApiSecret", passwordlessOptions.ApiSecret); + _disposeClient = true; } /// /// Initializes an instance of . /// - public PasswordlessClient(PasswordlessOptions options) - : this(new HttpClient(), options, true) + public PasswordlessClient(HttpClient client) { + _client = client; + _disposeClient = false; } ///