From b552dc7fa5996f9bbc618838e862a9833a65b5f7 Mon Sep 17 00:00:00 2001 From: "kampute.com" <49691331+kampute@users.noreply.github.com> Date: Mon, 6 May 2024 15:32:36 +0800 Subject: [PATCH] Improve HttpContentDeserializerCollection and XML comments (#8) Co-authored-by: Kambiz Khojasteh --- README.md | 6 +- .../Kampute.HttpClient.DataContract.csproj | 2 +- .../Kampute.HttpClient.Json.csproj | 4 +- .../Kampute.HttpClient.NewtonsoftJson.csproj | 4 +- .../Kampute.HttpClient.Xml.csproj | 2 +- .../HttpContentDeserializerCollection.cs | 218 ++++++++++++------ .../HttpContentException.cs | 2 +- src/Kampute.HttpClient/HttpRestClient.cs | 43 ++-- .../Kampute.HttpClient.csproj | 2 +- .../MediaTypeHeaderValueStore.cs | 4 +- src/Kampute.HttpClient/README.md | 6 +- .../Utilities/FlyweightCache.cs | 4 +- .../Utilities/SharedHttpClient.cs | 9 +- ...ampute.HttpClient.DataContract.Test.csproj | 4 +- .../Kampute.HttpClient.Json.Test.csproj | 4 +- ...pute.HttpClient.NewtonsoftJson.Test.csproj | 4 +- .../HttpContentDeserializerCollectionTests.cs | 2 +- .../HttpRestClientTests.cs | 16 ++ .../Kampute.HttpClient.Test.csproj | 4 +- .../Utilities/FlyweightCacheTests.cs | 12 +- .../Kampute.HttpClient.Xml.Test.csproj | 4 +- 21 files changed, 223 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index 70e464c..462bf68 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ to address the complexities of web service consumption. mechanisms to fit specific application needs. - **Dynamic Request Customization:** - Offers the capability to define headers and properties scoped to specific request blocks, allowing for temporary changes that do not affect the global configuration. - Scoped headers and properties ensure that modifications are contextually isolated, enhancing maintainability and reducing the risk of configuration errors during - runtime. + Offers the capability to define request headers and properties scoped to specific request blocks, allowing for temporary changes that do not affect the global + configuration. Scoped headers and properties ensure that modifications are contextually isolated, enhancing maintainability and reducing the risk of configuration + errors during runtime. - **Custom Error Handling and Exception Management:** Converts HTTP response errors into detailed, meaningful exceptions, streamlining the process of interpreting API-specific errors with the aid of a customizable diff --git a/src/Kampute.HttpClient.DataContract/Kampute.HttpClient.DataContract.csproj b/src/Kampute.HttpClient.DataContract/Kampute.HttpClient.DataContract.csproj index 57e57f0..02d98b7 100644 --- a/src/Kampute.HttpClient.DataContract/Kampute.HttpClient.DataContract.csproj +++ b/src/Kampute.HttpClient.DataContract/Kampute.HttpClient.DataContract.csproj @@ -5,7 +5,7 @@ Kampute.HttpClient.DataContract This package is an extension package for Kampute.HttpClient, enhancing it to manage application/xml content types, using DataContractSerializer for serialization and deserialization of XML responses and payloads. Kambiz Khojasteh - 2.0.0 + 2.1.0 Kampute Copyright (c) 2024 Kampute latest diff --git a/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj b/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj index 21388e9..f7482fa 100644 --- a/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj +++ b/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj @@ -5,7 +5,7 @@ Kampute.HttpClient.Json This package is an extension package for Kampute.HttpClient, enhancing it to manage application/json content types, using System.Text.Json library for serialization and deserialization of JSON responses and payloads. Kambiz Khojasteh - 2.0.0 + 2.1.0 Kampute Copyright (c) 2024 Kampute latest @@ -36,7 +36,7 @@ - + diff --git a/src/Kampute.HttpClient.NewtonsoftJson/Kampute.HttpClient.NewtonsoftJson.csproj b/src/Kampute.HttpClient.NewtonsoftJson/Kampute.HttpClient.NewtonsoftJson.csproj index 7538406..dfa4dcc 100644 --- a/src/Kampute.HttpClient.NewtonsoftJson/Kampute.HttpClient.NewtonsoftJson.csproj +++ b/src/Kampute.HttpClient.NewtonsoftJson/Kampute.HttpClient.NewtonsoftJson.csproj @@ -5,7 +5,7 @@ Kampute.HttpClient.NewtonsoftJson This package is an extension package for Kampute.HttpClient, enhancing it to manage application/json content types, using Newtonsoft.Json library for serialization and deserialization of JSON responses and payloads. Kambiz Khojasteh - 2.0.0 + 2.1.0 Kampute Copyright (c) 2024 Kampute latest @@ -36,7 +36,7 @@ - + diff --git a/src/Kampute.HttpClient.Xml/Kampute.HttpClient.Xml.csproj b/src/Kampute.HttpClient.Xml/Kampute.HttpClient.Xml.csproj index 9706223..06c0510 100644 --- a/src/Kampute.HttpClient.Xml/Kampute.HttpClient.Xml.csproj +++ b/src/Kampute.HttpClient.Xml/Kampute.HttpClient.Xml.csproj @@ -5,7 +5,7 @@ Kampute.HttpClient.Xml This package is an extension package for Kampute.HttpClient, enhancing it to manage application/xml content types, using XmlSerializer for serialization and deserialization of XML responses and payloads. Kambiz Khojasteh - 2.0.0 + 2.1.0 Kampute Copyright (c) 2024 Kampute latest diff --git a/src/Kampute.HttpClient/HttpContentDeserializerCollection.cs b/src/Kampute.HttpClient/HttpContentDeserializerCollection.cs index 3a86cdb..8ff9651 100644 --- a/src/Kampute.HttpClient/HttpContentDeserializerCollection.cs +++ b/src/Kampute.HttpClient/HttpContentDeserializerCollection.cs @@ -6,41 +6,39 @@ namespace Kampute.HttpClient { using Kampute.HttpClient.Interfaces; + using Kampute.HttpClient.Utilities; using System; using System.Collections; - using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; + using System.Threading; /// /// Represents a specialized collection of instances. /// /// - /// /// This collection provides capabilities for managing instances, including adding, removing, /// and selecting deserializers based on media types and model types. It leverages internal caches to optimize performance for frequently /// accessed deserializers, significantly enhancing efficiency in scenarios where media types and model types are repeatedly queried. - /// - /// - /// Caches within the collection are automatically invalidated and updated upon modification of the deserializer inventory, ensuring that access - /// patterns remain efficient and that overhead associated with dynamic updates is minimized. - /// - /// - /// The collection is designed to be flexible and adaptable, accommodating a wide range of models, media types, and server behaviors without - /// prior knowledge of specific implementations. It supports assigning quality factors to media types, prioritizing those that can deserialize - /// both model and error types (q=1.0) over those that solely support error types (q=0.9). This nuanced handling of media types facilitates - /// sophisticated content negotiation strategies, ensuring clients can effectively communicate preferences for both successful responses and - /// error scenarios. - /// /// - public sealed class HttpContentDeserializerCollection : ICollection + public sealed class HttpContentDeserializerCollection : ICollection, IReadOnlyCollection { private static readonly string[] AllMediaTypes = ["*/*"]; - private readonly List _collection = []; - private readonly ConcurrentDictionary> _mediaTypes1Cache = new(); - private readonly ConcurrentDictionary<(Type, Type), IReadOnlyCollection> _mediaTypes2Cache = new(); + private readonly List _collection; + private readonly Lazy _acceptCache; + private readonly FlyweightCache<(string, Type), IHttpContentDeserializer?> _deserializerCache; + + /// + /// Initializes a new instance of the class. + /// + public HttpContentDeserializerCollection() + { + _collection = []; + _deserializerCache = new(key => FindDeserializer(key.Item1, key.Item2)); + _acceptCache = new(() => new(this), LazyThreadSafetyMode.PublicationOnly); + } /// /// Gets the number of instances contained in the collection. @@ -66,11 +64,7 @@ public sealed class HttpContentDeserializerCollection : ICollectionAn instance of that can deserialize the specified media type and model type, or null if none is found. public IHttpContentDeserializer? GetDeserializerFor(string mediaType, Type modelType) { - foreach (var deserializer in _collection) - if (deserializer.CanDeserialize(mediaType, modelType)) - return deserializer; - - return null; + return _deserializerCache.Get((mediaType, modelType)); } /// @@ -98,7 +92,7 @@ public IEnumerable GetAcceptableMediaTypes(Type? modelType) { 0 => [], 1 => _collection[0].GetSupportedMediaTypes(modelType), - _ => _mediaTypes1Cache.GetOrAdd(modelType, CollectSupportedMediaTypes) + _ => _acceptCache.Value.GetSupportedMediaTypes(modelType) }; } @@ -135,7 +129,7 @@ public IEnumerable GetAcceptableMediaTypes(Type? modelType, Type? errorT if (modelType is null) return GetAcceptableMediaTypes(errorType).Concat(AllMediaTypes); - return _mediaTypes2Cache.GetOrAdd((modelType, errorType), CollectSupportedMediaTypes); + return _acceptCache.Value.GetSupportedMediaTypes(modelType, errorType); } /// @@ -230,75 +224,149 @@ IEnumerator IEnumerable.GetEnumerator() } /// - /// Resets the caches. + /// Locates the first instances in the collection that support deserializing a specific media type and model type. /// - private void InvalidateCaches() + /// The media type to deserialize. + /// The type of the model to deserialize. + /// An instance of that can deserialize the specified media type and model type, or null if none is found. + private IHttpContentDeserializer? FindDeserializer(string mediaType, Type modelType) { - _mediaTypes1Cache.Clear(); - _mediaTypes2Cache.Clear(); + foreach (var deserializer in _collection) + if (deserializer.CanDeserialize(mediaType, modelType)) + return deserializer; + + return null; } /// - /// Retrieves all supported media types for a specified model type from the collection of deserializers. + /// Resets the caches. /// - /// The type of the model for which to retrieve supported media type header values. - /// A read-only collection of strings that represent the media types supported for deserializing the specified model type. - private IReadOnlyCollection CollectSupportedMediaTypes(Type modelType) + private void InvalidateCaches() { - var uniqueMediaTypes = new HashSet(); - var orderedMediaTypes = new List(); - foreach (var deserializer in _collection) - { - foreach (var mediaType in deserializer.GetSupportedMediaTypes(modelType)) - { - if (uniqueMediaTypes.Add(mediaType)) - orderedMediaTypes.Add(mediaType); - } - } - orderedMediaTypes.TrimExcess(); - return orderedMediaTypes; + _deserializerCache.Clear(); + if (_acceptCache.IsValueCreated) + _acceptCache.Value.Clear(); } + #region Helper Types + /// - /// Retrieves all supported media types for a specified pair of model and error types from the collection of deserializers. + /// Provides cache of supported media types for .NET object types. /// - /// - /// A tuple containing two types used to collect and aggregate media types that can deserialize objects of these types from HTTP content: - /// - /// - /// Item1 - /// The type of the model for which to retrieve supported media types. - /// - /// - /// Item2 - /// The type of the error for which to retrieve supported media types. - /// - /// - /// - /// - /// A read-only collection of strings that represent the media types supported for deserializing the specified types. - /// - private IReadOnlyCollection CollectSupportedMediaTypes((Type, Type) types) + private sealed class AcceptableMediaTypeCache { - var uniqueMediaTypes = new HashSet(); - var orderedMediaTypes = new List(); + private readonly IReadOnlyCollection _deserializers; + private readonly FlyweightCache> _singles; + private readonly FlyweightCache<(Type, Type), IReadOnlyCollection> _duals; - // Add media type header values supporting model - foreach (var mediaType in GetAcceptableMediaTypes(types.Item1)) + public AcceptableMediaTypeCache(IReadOnlyCollection deserializers) { - if (uniqueMediaTypes.Add(mediaType)) - orderedMediaTypes.Add(mediaType); + _deserializers = deserializers; + _singles = new(CollectSupportedMediaTypes); + _duals = new(CollectSupportedMediaTypes); } - // Add media type header values supporting error - foreach (var mediaType in GetAcceptableMediaTypes(types.Item2)) + /// + /// Retrieves all supported media types for a specified model type from the collection of deserializers. + /// + /// The type of the model for which to retrieve supported media types. + /// A read-only collection of strings that represent the media types supported for deserializing the specified model type. + public IReadOnlyCollection GetSupportedMediaTypes(Type modelType) { - if (uniqueMediaTypes.Add(mediaType)) - orderedMediaTypes.Add(mediaType); + return _singles.Get(modelType); + } + + /// + /// Retrieves all supported media types for a specified model type and error type from the collection of deserializers. + /// + /// The type of the model for which to retrieve supported media types. + /// The type of the error for which to retrieve supported media types. + /// A read-only collection of strings representing the supported media types for the specified types. + public IReadOnlyCollection GetSupportedMediaTypes(Type modelType, Type errorType) + { + return _duals.Get((modelType, errorType)); + } + + /// + /// Clears the chace. + /// + public void Clear() + { + _singles.Clear(); + _duals.Clear(); + } + + /// + /// Retrieves all supported media types for a specified model type from the collection of deserializers. + /// + /// The type of the model for which to retrieve supported media type header values. + /// A read-only collection of strings that represent the media types supported for deserializing the specified model type. + private IReadOnlyCollection CollectSupportedMediaTypes(Type modelType) + { + var uniqueMediaTypes = new HashSet(); + var orderedMediaTypes = new List(); + + foreach (var deserializer in _deserializers) + { + foreach (var mediaType in deserializer.GetSupportedMediaTypes(modelType)) + { + if (uniqueMediaTypes.Add(mediaType)) + orderedMediaTypes.Add(mediaType); + } + } + + orderedMediaTypes.TrimExcess(); + return orderedMediaTypes; } - orderedMediaTypes.TrimExcess(); - return orderedMediaTypes; + /// + /// Retrieves all supported media types for a specified pair of model and error types from the collection of deserializers. + /// + /// + /// A tuple containing two types used to collect and aggregate media types that can deserialize objects of these types from HTTP content: + /// + /// + /// Item1 + /// The type of the model for which to retrieve supported media types. + /// + /// + /// Item2 + /// The type of the error for which to retrieve supported media types. + /// + /// + /// + /// + /// A read-only collection of strings that represent the media types supported for deserializing the specified types. + /// + private IReadOnlyCollection CollectSupportedMediaTypes((Type, Type) types) + { + var (modelType, errorType) = types; + var uniqueMediaTypes = new HashSet(); + var orderedMediaTypes = new List(); + + foreach (var deserializer in _deserializers) + { + foreach (var mediaType in deserializer.GetSupportedMediaTypes(modelType)) + { + if (uniqueMediaTypes.Add(mediaType)) + orderedMediaTypes.Add(mediaType); + } + } + + foreach (var deserializer in _deserializers) + { + foreach (var mediaType in deserializer.GetSupportedMediaTypes(errorType)) + { + if (uniqueMediaTypes.Add(mediaType)) + orderedMediaTypes.Add(mediaType); + } + } + + orderedMediaTypes.TrimExcess(); + return orderedMediaTypes; + } } + + #endregion } } diff --git a/src/Kampute.HttpClient/HttpContentException.cs b/src/Kampute.HttpClient/HttpContentException.cs index 18e6177..1b0e42d 100644 --- a/src/Kampute.HttpClient/HttpContentException.cs +++ b/src/Kampute.HttpClient/HttpContentException.cs @@ -12,7 +12,7 @@ namespace Kampute.HttpClient /// /// The exception that is thrown when an invalid or unsupported content is encountered in an HTTP response. /// - public class HttpContentException : ApplicationException + public class HttpContentException : Exception { /// /// Initializes a new instance of the class. diff --git a/src/Kampute.HttpClient/HttpRestClient.cs b/src/Kampute.HttpClient/HttpRestClient.cs index f35dfe0..736be10 100644 --- a/src/Kampute.HttpClient/HttpRestClient.cs +++ b/src/Kampute.HttpClient/HttpRestClient.cs @@ -20,33 +20,30 @@ namespace Kampute.HttpClient /// /// /// - /// simplifies interactions with RESTful APIs by abstracting the complexities of . It supports the - /// sharing of a single instance across multiple instances, optimizing resource use and connection - /// management. This shared approach enhances performance, especially when concurrently accessing various services or API endpoints, by reusing HTTP - /// connections. + /// abstracts the complexities of , supporting the sharing of a single + /// instance across multiple instances. This optimizes resource use and connection management, enhancing performance by + /// reusing HTTP connections, especially during concurrent access to various services or API endpoints. /// /// - /// It features a collection for automatic HTTP response content deserialization into .NET objects. The client selects - /// the appropriate deserializer for the response's Content-Type, easing the integration with API responses. + /// The client allows for scoped request headers and properties, providing temporary configurations that do not alter global settings. This ensures + /// that changes remain isolated to specific contexts, increasing maintainability and reducing configuration errors during runtime. /// /// - /// Backoff strategies are implemented through the property, providing a mechanism for handling transient failures and network - /// interruptions. These strategies dictate the logic for retrying requests after failures, including how long to wait between retries. By employing a - /// proper backoff strategy, the client can avoid overwhelming the server or network, improving the chances of successful communication without compromising - /// resource utilization. + /// It includes a collection that automatically deserializes HTTP response content into .NET objects based on the + /// response's Content-Type. If the Accept header is not predefined, the client dynamically adjusts it based on the configured response + /// deserializers and the expected .NET object type. /// /// - /// Extensible error handling is achieved through the collection. Custom implementations can - /// be employed to address specific HTTP errors, enabling tailored retry strategies and failure responses. + /// Transient failures and network interruptions are managed via the property, which outlines retry logic and wait times + /// between retries. This strategic approach helps avoid server overloads and improves communication success without excessive resource use. /// /// - /// The client enriches HTTP request and response handling with life-cycle events such as and . - /// These events allow for request modification, response inspection, and logging, facilitating a high degree of interaction customization. + /// Extensible error handling is enabled through the collection, allowing custom implementations + /// to handle specific HTTP errors with tailored strategies. /// /// - /// Through its design, aims to offer a balance between ease of use and flexibility, making it a suitable choice for developers - /// looking to interact with RESTful APIs in a .NET environment. Whether for simple API consumption or complex, high-load scenarios, it provides the tools - /// necessary to create efficient, reliable, and customizable HTTP communication solutions. + /// Lifecycle events like and enhance request and response handling by enabling + /// modifications, inspections, and logging, allowing for a highly customizable interaction. /// /// public class HttpRestClient : IDisposable @@ -70,8 +67,7 @@ private static HttpRequestHeaders CreateRequestHeaders() /// Initializes a new instance of the class. /// /// - /// This constructor initializes the with a shared instance provided - /// by class. + /// This constructor initializes the using a shared instance acquired from the static class. /// public HttpRestClient() : this(SharedHttpClient.AcquireReference()) @@ -79,10 +75,13 @@ public HttpRestClient() } /// - /// Initializes a new instance of the class using a shared . + /// Initializes a new instance of the class with the specified shared reference. /// - /// A to the shared instance. - /// Thrown if is null. + /// A reference to a shared instance, managed as . + /// Thrown if is null>. + /// + /// This constructor takes ownership of the shared reference and ensures it is properly released when the is disposed. + /// public HttpRestClient(SharedDisposable.Reference httpClientReference) { if (httpClientReference is null) diff --git a/src/Kampute.HttpClient/Kampute.HttpClient.csproj b/src/Kampute.HttpClient/Kampute.HttpClient.csproj index 7fbb336..e7dce9e 100644 --- a/src/Kampute.HttpClient/Kampute.HttpClient.csproj +++ b/src/Kampute.HttpClient/Kampute.HttpClient.csproj @@ -5,7 +5,7 @@ Kampute.HttpClient Kampute.HttpClient is a versatile and lightweight .NET library that simplifies RESTful API communication. Its core HttpRestClient class provides a streamlined approach to HTTP interactions, offering advanced features such as flexible serialization/deserialization, robust error handling, configurable backoff strategies, and detailed request-response processing. Striking a balance between simplicity and extensibility, Kampute.HttpClient empowers developers with a powerful yet easy-to-use client for seamless API integration across a wide range of .NET applications. Kambiz Khojasteh - 2.0.0 + 2.1.0 Kampute Copyright (c) 2024 Kampute latest diff --git a/src/Kampute.HttpClient/MediaTypeHeaderValueStore.cs b/src/Kampute.HttpClient/MediaTypeHeaderValueStore.cs index 8a74cde..1687923 100644 --- a/src/Kampute.HttpClient/MediaTypeHeaderValueStore.cs +++ b/src/Kampute.HttpClient/MediaTypeHeaderValueStore.cs @@ -16,7 +16,7 @@ public static class MediaTypeHeaderValueStore /// /// The media type as a string. /// A corresponding to the specified media type. - public static MediaTypeWithQualityHeaderValue Get(string mediaType) => WithoutQuality.Store[mediaType]; + public static MediaTypeWithQualityHeaderValue Get(string mediaType) => WithoutQuality.Store.Get(mediaType); /// /// Retrieves a from the cache or creates a new one if it does not exist. @@ -24,7 +24,7 @@ public static class MediaTypeHeaderValueStore /// The media type as a string. /// The quality factor associated with this media type, expressed as a value between 0 and 1. /// A corresponding to the specified media type and quality factor. - public static MediaTypeWithQualityHeaderValue Get(string mediaType, float quality) => WithQuality.Store[(mediaType, quality)]; + public static MediaTypeWithQualityHeaderValue Get(string mediaType, float quality) => WithQuality.Store.Get((mediaType, quality)); /// /// Manages the caching of instances without quality factor. diff --git a/src/Kampute.HttpClient/README.md b/src/Kampute.HttpClient/README.md index cce00cc..bb75bbe 100644 --- a/src/Kampute.HttpClient/README.md +++ b/src/Kampute.HttpClient/README.md @@ -15,9 +15,9 @@ array of functionalities to address the complexities of web service consumption. mechanisms to fit specific application needs. - **Dynamic Request Customization:** - Offers the capability to define headers and properties scoped to specific request blocks, allowing for temporary changes that do not affect the global configuration. - Scoped headers and properties ensure that modifications are contextually isolated, enhancing maintainability and reducing the risk of configuration errors during - runtime. + Offers the capability to define request headers and properties scoped to specific request blocks, allowing for temporary changes that do not affect the global + configuration. Scoped headers and properties ensure that modifications are contextually isolated, enhancing maintainability and reducing the risk of configuration + errors during runtime. - **Custom Error Handling and Exception Management:** Converts HTTP response errors into detailed, meaningful exceptions, streamlining the process of interpreting API-specific errors with the aid of a customizable diff --git a/src/Kampute.HttpClient/Utilities/FlyweightCache.cs b/src/Kampute.HttpClient/Utilities/FlyweightCache.cs index c150b59..0873697 100644 --- a/src/Kampute.HttpClient/Utilities/FlyweightCache.cs +++ b/src/Kampute.HttpClient/Utilities/FlyweightCache.cs @@ -51,8 +51,8 @@ public FlyweightCache(Func valueFactory, IEqualityComparer? /// Retrieves a value for the specified key. /// /// The key whose value to retrieve. - /// The value associated with the specified key. - public TValue this[TKey key] => _store.GetOrAdd(key, _valueFactory); + /// The value associated with the specified key. + public TValue Get(TKey key) => _store.GetOrAdd(key, _valueFactory); /// /// Checks if the cache contains a value associated with the specified key. diff --git a/src/Kampute.HttpClient/Utilities/SharedHttpClient.cs b/src/Kampute.HttpClient/Utilities/SharedHttpClient.cs index c1b68d2..d995f6c 100644 --- a/src/Kampute.HttpClient/Utilities/SharedHttpClient.cs +++ b/src/Kampute.HttpClient/Utilities/SharedHttpClient.cs @@ -4,7 +4,7 @@ using System.Net.Http; /// - /// Provides a singleton-like access to a shared instance across the application using . + /// Provides a singleton-like access to a shared instance across the application. /// /// /// This static class manages the lifecycle of a single instance. It ensures efficient resource usage by allowing @@ -34,6 +34,13 @@ public static SharedDisposable.Reference AcquireReference() return _instance.AcquireReference(); } + + /// + /// Gets the current number of active references to the shared instance. + /// + /// The number of active references. + public static int ReferenceCount => _instance is not null ? _instance.ReferenceCount : 0; + /// /// Gets or sets the factory method used to create the instance. /// diff --git a/tests/Kampute.HttpClient.DataContract.Test/Kampute.HttpClient.DataContract.Test.csproj b/tests/Kampute.HttpClient.DataContract.Test/Kampute.HttpClient.DataContract.Test.csproj index a70d985..9480d0f 100644 --- a/tests/Kampute.HttpClient.DataContract.Test/Kampute.HttpClient.DataContract.Test.csproj +++ b/tests/Kampute.HttpClient.DataContract.Test/Kampute.HttpClient.DataContract.Test.csproj @@ -14,11 +14,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Kampute.HttpClient.Json.Test/Kampute.HttpClient.Json.Test.csproj b/tests/Kampute.HttpClient.Json.Test/Kampute.HttpClient.Json.Test.csproj index cacfcf4..59df174 100644 --- a/tests/Kampute.HttpClient.Json.Test/Kampute.HttpClient.Json.Test.csproj +++ b/tests/Kampute.HttpClient.Json.Test/Kampute.HttpClient.Json.Test.csproj @@ -14,11 +14,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Kampute.HttpClient.NewtonsoftJson.Test/Kampute.HttpClient.NewtonsoftJson.Test.csproj b/tests/Kampute.HttpClient.NewtonsoftJson.Test/Kampute.HttpClient.NewtonsoftJson.Test.csproj index 32ec5d4..2ce7912 100644 --- a/tests/Kampute.HttpClient.NewtonsoftJson.Test/Kampute.HttpClient.NewtonsoftJson.Test.csproj +++ b/tests/Kampute.HttpClient.NewtonsoftJson.Test/Kampute.HttpClient.NewtonsoftJson.Test.csproj @@ -14,11 +14,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Kampute.HttpClient.Test/HttpContentDeserializerCollectionTests.cs b/tests/Kampute.HttpClient.Test/HttpContentDeserializerCollectionTests.cs index e463b51..c2e98cd 100644 --- a/tests/Kampute.HttpClient.Test/HttpContentDeserializerCollectionTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpContentDeserializerCollectionTests.cs @@ -65,7 +65,7 @@ public void GetAcceptableMediaTypes_ModelTypeAndErrorType_ReturnsCorrectMediaTyp var result = collection.GetAcceptableMediaTypes(modelType, errorType); - Assert.That(result, Is.EquivalentTo(expectedMediaTypes)); + Assert.That(result, Is.EqualTo(expectedMediaTypes)); } [Test] diff --git a/tests/Kampute.HttpClient.Test/HttpRestClientTests.cs b/tests/Kampute.HttpClient.Test/HttpRestClientTests.cs index bc146a1..1378ae4 100644 --- a/tests/Kampute.HttpClient.Test/HttpRestClientTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpRestClientTests.cs @@ -3,6 +3,7 @@ using Kampute.HttpClient; using Kampute.HttpClient.Interfaces; using Kampute.HttpClient.Test.TestHelpers; + using Kampute.HttpClient.Utilities; using Moq; using NUnit.Framework; using System; @@ -45,19 +46,34 @@ public void Cleanup() _client.Dispose(); } + [Test] + public void DefaultConstractor_UsesSharedHttpClient() + { + var client = new HttpRestClient(); + Assert.That(SharedHttpClient.ReferenceCount, Is.EqualTo(1)); + + client.Dispose(); + Assert.That(SharedHttpClient.ReferenceCount, Is.Zero); + } + [Test] public async Task DefaultRequestHeaders_AreCopiedToRequestHeaders() { var testerAgent = new ProductInfoHeaderValue("Tester", "1.0"); _client.DefaultRequestHeaders.UserAgent.Add(testerAgent); + var sent = false; _mockMessageHandler.MockHttpResponse(request => { Assert.That(request.Headers.UserAgent, Contains.Item(testerAgent)); + + sent = true; return new HttpResponseMessage(HttpStatusCode.NoContent); }); await _client.SendAsync(TestHttpMethod, "/resource"); + + Assert.That(sent, Is.True); } [Test] diff --git a/tests/Kampute.HttpClient.Test/Kampute.HttpClient.Test.csproj b/tests/Kampute.HttpClient.Test/Kampute.HttpClient.Test.csproj index 4077218..1a2a219 100644 --- a/tests/Kampute.HttpClient.Test/Kampute.HttpClient.Test.csproj +++ b/tests/Kampute.HttpClient.Test/Kampute.HttpClient.Test.csproj @@ -14,11 +14,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Kampute.HttpClient.Test/Utilities/FlyweightCacheTests.cs b/tests/Kampute.HttpClient.Test/Utilities/FlyweightCacheTests.cs index 68028d3..4417fe2 100644 --- a/tests/Kampute.HttpClient.Test/Utilities/FlyweightCacheTests.cs +++ b/tests/Kampute.HttpClient.Test/Utilities/FlyweightCacheTests.cs @@ -13,7 +13,7 @@ public void Get_WhenKeyDoesNotExist_AddsValue() { var cache = new FlyweightCache(key => $"Value {key}"); - var result = cache[1]; + var result = cache.Get(1); Assert.That(result, Is.EqualTo("Value 1")); } @@ -23,8 +23,8 @@ public void Get_WhenKeyExists_ReturnsExistingValue() { var cache = new FlyweightCache(key => $"Value {key}"); - var result1 = cache[1]; - var result2 = cache[1]; + var result1 = cache.Get(1); + var result2 = cache.Get(1); Assert.Multiple(() => { @@ -38,7 +38,7 @@ public async Task Get_IsThreadSafe() { var cache = new FlyweightCache(key => $"Value {key}"); - var tasks = Enumerable.Range(0, 100).Select(i => Task.Factory.StartNew(() => cache[i % 2])); + var tasks = Enumerable.Range(0, 100).Select(i => Task.Factory.StartNew(() => cache.Get(i % 2))); var results = await Task.WhenAll(tasks); Assert.That(cache.Count, Is.EqualTo(2)); @@ -61,7 +61,7 @@ public void Contains_WhenKeyDoesNotExist_ReturnsFalse() public void Contains_WhenKeyExists_ReturnsTrue() { var cache = new FlyweightCache(key => $"Value {key}"); - var _ = cache[1]; + var _ = cache.Get(1); var result = cache.Contains(1); @@ -72,7 +72,7 @@ public void Contains_WhenKeyExists_ReturnsTrue() public void Clear_RemovesAllEntries() { var cache = new FlyweightCache(key => $"Value {key}"); - var _ = cache[1]; + var _ = cache.Get(1); cache.Clear(); diff --git a/tests/Kampute.HttpClient.Xml.Test/Kampute.HttpClient.Xml.Test.csproj b/tests/Kampute.HttpClient.Xml.Test/Kampute.HttpClient.Xml.Test.csproj index 548e71e..0b27f8e 100644 --- a/tests/Kampute.HttpClient.Xml.Test/Kampute.HttpClient.Xml.Test.csproj +++ b/tests/Kampute.HttpClient.Xml.Test/Kampute.HttpClient.Xml.Test.csproj @@ -14,11 +14,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive