From 8e31601d7577a265d9cece3c2c5e2806b0900e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=BCller?= Date: Sun, 25 Feb 2024 09:49:43 +0100 Subject: [PATCH] Added Collection Extensions, Updated DateTimeExtensions --- .../ApplicationBuilderExtensions.cs | 30 ++-- AMWD.Common/AMWD.Common.csproj | 6 - .../Extensions/CollectionExtensions.cs | 55 +++++++ AMWD.Common/Extensions/DateTimeExtensions.cs | 23 ++- AMWD.Common/InternalsVisibleTo.cs | 3 + CHANGELOG.md | 8 +- README.md | 1 + .../Extensions/CollectionExtensionsTests.cs | 143 ++++++++++++++++++ 8 files changed, 242 insertions(+), 27 deletions(-) create mode 100644 AMWD.Common/Extensions/CollectionExtensions.cs create mode 100644 AMWD.Common/InternalsVisibleTo.cs create mode 100644 UnitTests/Common/Extensions/CollectionExtensionsTests.cs diff --git a/AMWD.Common.AspNetCore/Extensions/ApplicationBuilderExtensions.cs b/AMWD.Common.AspNetCore/Extensions/ApplicationBuilderExtensions.cs index 108bfb1..293d7b1 100644 --- a/AMWD.Common.AspNetCore/Extensions/ApplicationBuilderExtensions.cs +++ b/AMWD.Common.AspNetCore/Extensions/ApplicationBuilderExtensions.cs @@ -16,26 +16,32 @@ namespace Microsoft.AspNetCore.Builder /// Adds settings to run behind a reverse proxy (e.g. NginX). /// /// - /// A base path (e.g. running in a sub-directory /app) for the application is defined via ASPNETCORE_APPL_PATH environment variable. + /// A base path (e.g. running in a sub-directory /app) for the application can be defined via ASPNETCORE_APPL_PATH environment variable. ///
///
/// Additionally you can specify the proxy server by using or a when there are multiple proxy servers. ///
- /// When no oder is set, the default subnets are configured:
- /// - 127.0.0.0/8
- /// - 10.0.0.0/8
- /// - 172.16.0.0/12
- /// - 192.168.0.0/16
+ /// When neither nor is set, the default subnets are configured: + /// + /// 127.0.0.0/8 + /// ::1/128 /// - /// - ::1/128
- /// - fd00::/8 + /// 10.0.0.0/8 + /// 172.16.0.0/12 + /// 192.168.0.0/16 + /// fd00::/8 + ///
///
/// The application builder. /// The where proxy requests are received from (optional). /// The where proxy requests are received from (optional). - public static IApplicationBuilder UseProxyHosting(this IApplicationBuilder app, IPNetwork network = null, IPAddress address = null) + /// A custom base path (optional, ASPNETCORE_APPL_PATH is prefererred). + public static IApplicationBuilder UseProxyHosting(this IApplicationBuilder app, IPNetwork network = null, IPAddress address = null, string basePath = null) { string path = Environment.GetEnvironmentVariable("ASPNETCORE_APPL_PATH"); + if (string.IsNullOrWhiteSpace(path)) + path = basePath; + if (!string.IsNullOrWhiteSpace(path)) app.UsePathBase(new PathString(path)); @@ -46,15 +52,17 @@ namespace Microsoft.AspNetCore.Builder if (network == null && address == null) { // localhost - options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("127.0.0.0"), 8)); - options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("::1"), 128)); + options.KnownNetworks.Add(new IPNetwork(IPAddress.Loopback, 8)); + options.KnownNetworks.Add(new IPNetwork(IPAddress.IPv6Loopback, 128)); // private IPv4 networks + // see https://en.wikipedia.org/wiki/Private_network#Private_IPv4_addresses options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8)); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12)); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16)); // private IPv6 networks + // see https://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fd00::"), 8)); } diff --git a/AMWD.Common/AMWD.Common.csproj b/AMWD.Common/AMWD.Common.csproj index d28ffb4..332ec50 100644 --- a/AMWD.Common/AMWD.Common.csproj +++ b/AMWD.Common/AMWD.Common.csproj @@ -34,10 +34,4 @@ - - - <_Parameter1>UnitTests - - - diff --git a/AMWD.Common/Extensions/CollectionExtensions.cs b/AMWD.Common/Extensions/CollectionExtensions.cs new file mode 100644 index 0000000..2d0fb46 --- /dev/null +++ b/AMWD.Common/Extensions/CollectionExtensions.cs @@ -0,0 +1,55 @@ +namespace System.Collections.Generic +{ + /// + /// Provides extension methods for generic implementations. + /// + public static class CollectionExtensions + { + /// + /// Add the to the if it is not . + /// + /// The type of the elements in the collection + /// The collection. + /// The item to add if not . + public static void AddIfNotNull(this ICollection collection, T item) + { +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(collection); +#else + if (collection == null) + throw new ArgumentNullException(nameof(collection)); +#endif + + if (item == null) + return; + + collection.Add(item); + } + + /// + /// Adds a functionallity to . + /// + /// The type of the elements in the collection. + /// The collection. + /// The items to add. + public static void AddRange(this ICollection collection, IEnumerable items) + { +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(collection); + ArgumentNullException.ThrowIfNull(items); +#else + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + if (items == null) + throw new ArgumentNullException(nameof(items)); +#endif + + if (collection == items) + return; + + foreach (var item in items) + collection.Add(item); + } + } +} diff --git a/AMWD.Common/Extensions/DateTimeExtensions.cs b/AMWD.Common/Extensions/DateTimeExtensions.cs index becc9f1..29338bc 100644 --- a/AMWD.Common/Extensions/DateTimeExtensions.cs +++ b/AMWD.Common/Extensions/DateTimeExtensions.cs @@ -61,20 +61,27 @@ namespace System public static TimeSpan GetAlignedIntervalLocal(this TimeSpan timeSpan, TimeSpan offset = default) => timeSpan.GetAlignedInterval(DateTime.Now, offset); - internal static TimeSpan GetAlignedInterval(this TimeSpan timeSpan, DateTime now, TimeSpan offset = default) + /// + /// Aligns the to the specified time. + /// + /// The timespan to align. + /// A timestamp to align with. + /// A specific offset to the timespan. + /// The timespan until the aligned time. + public static TimeSpan GetAlignedInterval(this TimeSpan timeSpan, DateTime now, TimeSpan offset = default) { - var nowWithOffset = new DateTimeOffset(now); + var dtOffsetNow = new DateTimeOffset(now); - var nextTime = new DateTime(nowWithOffset.Ticks / timeSpan.Ticks * timeSpan.Ticks, now.Kind).Add(offset); - var nextTimeWithOffset = new DateTimeOffset(nextTime); + var nextTime = new DateTime(dtOffsetNow.Ticks / timeSpan.Ticks * timeSpan.Ticks, now.Kind).Add(offset); + var dtOffsetNext = new DateTimeOffset(nextTime); - if (nextTimeWithOffset <= nowWithOffset) - nextTimeWithOffset = nextTimeWithOffset.Add(timeSpan); + if (dtOffsetNext <= dtOffsetNow) + dtOffsetNext = dtOffsetNext.Add(timeSpan); if (now.Kind == DateTimeKind.Local) - return nextTimeWithOffset.LocalDateTime - nowWithOffset.LocalDateTime; + return dtOffsetNext.LocalDateTime - dtOffsetNow.LocalDateTime; - return nextTimeWithOffset - nowWithOffset; + return dtOffsetNext - dtOffsetNow; } #endregion Aligned Interval diff --git a/AMWD.Common/InternalsVisibleTo.cs b/AMWD.Common/InternalsVisibleTo.cs new file mode 100644 index 0000000..cd080bc --- /dev/null +++ b/AMWD.Common/InternalsVisibleTo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("UnitTests")] diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6784f..1b43cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,21 +12,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [AMWD.Common](https://git.am-wd.de/AM.WD/common/compare/v2.0.1...HEAD) - [AMWD.Common.AspNetCore](https://git.am-wd.de/AM.WD/common/compare/asp/v3.0.0...HEAD) - [AMWD.Common.EntityFrameworkCore](https://git.am-wd.de/AM.WD/common/compare/efc/v3.0.0...HEAD) +- [AMWD.Common.MessagePack](https://git.am-wd.de/AM.WD/common/compare/main...HEAD) - [AMWD.Common.Test](https://git.am-wd.de/AM.WD/common/compare/test/v2.1.1...HEAD) ### Added - `ArReader` and `ArWriter` for Unix archives - `TarReader` and `TarWriter` for TAR archives +- `.AddRange()` for collections +- `.AddIfNotNull()` for collections - `VersionStringComparer` to compare version strings (SemVer) ### Changed - Optimized for C# 12 -- `IPNetwork` is used from (as `Microsoft.AspNetCore.HttpOverrides` is taged as "out of support"): +- `IPNetwork` is used from (as `Microsoft.AspNetCore.HttpOverrides` is tagged as "out of support"): - .NET Standard 2.0 and .NET 6.0: `Microsoft.AspNetCore.HttpOverrides.IPNetwork` - .NET 8.0: `System.Net.IPNetwork` -- Moved `MessagePack` extensions formatter extensions to own package `AMWD.Common.MessagePack` +- Moved `MessagePack` formatter extensions to own package `AMWD.Common.MessagePack` +- `GetAlignedInterval()` (without Local/Utc) is now public accessible ### Removed diff --git a/README.md b/README.md index a82d58e..b8c7d1f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ To save time, they are all packed to NuGet packages. | AMWD.Common | ![https://nuget.am-wd.de/packages/amwd.common/](https://services.am-wd.de/nuget.php?package=AMWD.Common) | | AMWD.Common.AspNetCore | ![https://nuget.am-wd.de/packages/amwd.common.aspnetcore/](https://services.am-wd.de/nuget.php?package=AMWD.Common.AspNetCore) | | AMWD.Common.EntityFrameworkCore | ![https://nuget.am-wd.de/packages/amwd.common.entityframeworkcore/](https://services.am-wd.de/nuget.php?package=AMWD.Common.EntityFrameworkCore) | +| AMWD.Common.MessagePack | ![https://nuget.am-wd.de/packages/amwd.common.messagepack/](https://services.am-wd.de/nuget.php?package=AMWD.Common.MessagePack) | | AMWD.Common.Test | ![https://nuget.am-wd.de/packages/amwd.common.test/](https://services.am-wd.de/nuget.php?package=AMWD.Common.Test) | | CI / CD | ![https://git.am-wd.de/AM.WD/common/-/pipelines](https://git.am-wd.de/AM.WD/common/badges/main/pipeline.svg?style=flat-square) ![https://git.am-wd.de/AM.WD/common/-/tree/main](https://git.am-wd.de/AM.WD/common/badges/main/coverage.svg?style=flat-square) | diff --git a/UnitTests/Common/Extensions/CollectionExtensionsTests.cs b/UnitTests/Common/Extensions/CollectionExtensionsTests.cs new file mode 100644 index 0000000..19762c3 --- /dev/null +++ b/UnitTests/Common/Extensions/CollectionExtensionsTests.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.Common.Extensions +{ + [TestClass] + public class CollectionExtensionsTests + { + [TestMethod] + public void ShouldAddItem() + { + // Arrange + var item = new TestItem { Number = 10, Text = "Ten" }; + ICollection list = new List + { + new() { + Number = 1, + Text = "One" + } + }; + + // Act + list.AddIfNotNull(item); + + // Assert + Assert.AreEqual(2, list.Count); + } + + [TestMethod] + public void ShouldNotAddItem() + { + // Arrange + TestItem item = null; + ICollection list = new List + { + new() { + Number = 1, + Text = "One" + } + }; + + // Act + list.AddIfNotNull(item); + + // Assert + Assert.AreEqual(1, list.Count); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void ShouldThrowArgumentNullExceptionForNullList() + { + // Arrange + var item = new TestItem { Number = 10, Text = "Ten" }; + ICollection list = null; + + // Act + list.AddIfNotNull(item); + + // Assert - ArgumentNullException + } + + [TestMethod] + public void ShouldAddRange() + { + // Arrange + ICollection items = new List + { + new() { Number = 10, Text = "Ten" }, + new() { Number = 11, Text = "Eleven" }, + }; + ICollection list = new List + { + new() { Number = 1, Text = "One" }, + }; + + // Act + list.AddRange(items); + + // Assert + Assert.AreEqual(3, list.Count); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void ShouldThrowArgumentNullExceptionForList() + { + // Arrange + ICollection items = new List + { + new() { Number = 10, Text = "Ten" }, + new() { Number = 11, Text = "Eleven" }, + }; + ICollection list = null; + + // Act + list.AddRange(items); + + // Assert - ArgumentNullException + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void ShouldThrowArgumentNullExceptionForItems() + { + // Arrange + ICollection items = null; + ICollection list = new List + { + new() { Number = 1, Text = "One" }, + }; + + // Act + list.AddRange(items); + + // Assert - ArgumentNullException + } + + [TestMethod] + public void ShouldNotAddRange() + { + // Arrange + ICollection list = new List + { + new() { Number = 1, Text = "One" }, + }; + + // Act + list.AddRange(list); + + // Assert + Assert.AreEqual(1, list.Count); + } + + private class TestItem + { + public int Number { get; set; } + + public string Text { get; set; } + } + } +}