Compare commits

...

60 Commits

Author SHA1 Message Date
amacocian 2c918cf4a4 Blittable strings generator implementation
Improvements to fixed array generator
2026-01-27 14:15:41 +01:00
amacocian dbdf5cfbe2 Remove deprecated packages 2026-01-12 22:24:26 +01:00
amacocian 124ae942b5 Remove Slim dependency and adhere to Microsoft.Extensions standards 2026-01-11 01:02:42 +01:00
amacocian 5e5cdb3b47 Improve logging 2026-01-11 00:53:44 +01:00
amacocian a4d0d5cab7 IClientWebSocket builder 2026-01-09 14:26:36 +01:00
amacocian 3b47d0bd0c Deprecate logging and config abstractions, in favor of standard MS provided ones 2026-01-09 13:20:18 +01:00
amacocian 4033f9d401 Remove gitlab mirror 2025-09-13 13:39:13 +02:00
amacocian c9f6541904 Migrate to slnx 2025-09-12 11:39:38 +02:00
amacocian 18cd59d414 Mark notifypropertychanged with nullable 2025-06-29 12:25:13 +02:00
amacocian c63fd45e38 Fix from managed to fixed array 2025-05-20 18:41:39 +02:00
amacocian 7ec573098b Allow multiple on fixed array generator 2025-05-20 18:15:51 +02:00
amacocian af1dfd6751 Expose SemaphoreSlim.Acquire with CancellationToken 2025-05-20 12:25:36 +02:00
amacocian bae28b2552 Implement FixedArrayGenerator 2025-05-17 17:50:29 +02:00
amacocian 818240fcea Fix nullability of parameter 2024-10-16 14:29:51 +02:00
amacocian 25eae4061b Fix ThrowIfNull 2024-10-16 14:09:13 +02:00
amacocian 33584e8a14 Update dependency injection package 2024-09-27 17:10:49 +02:00
amacocian 7d06595307 Implement value caches 2024-09-26 10:32:38 +02:00
amacocian d4ef0dc34e Base32 conversions 2024-09-08 18:37:38 +02:00
amacocian 0fc24b8307 Fix build issues 2024-09-08 17:13:48 +02:00
amacocian def0258695 More hashing services and HMAC services 2024-09-08 17:08:22 +02:00
amacocian 535a2cedc0 Argument to dispose inner http client 2024-09-06 23:44:27 +02:00
amacocian 2b23665145 Downgrade compiler version to support MAUI 2024-09-02 20:47:41 +02:00
amacocian 3b942c2102 Adjust generator namespace 2024-09-02 20:29:13 +02:00
amacocian 681bec01f2 Fix generators project 2024-08-28 15:33:32 +02:00
amacocian 552b72b1b9 Introduce source generators package
Update dependencies
2024-08-28 15:24:18 +02:00
amacocian 00459fc59b SemaphoreSlim context 2024-08-23 22:42:25 +02:00
amacocian 84a9f8e8d6 Clean csproj 2024-08-22 13:28:45 +02:00
amacocian a8de6cd15d ScopedLogger with implicit member name
Include NetStandard in NetCore
2024-08-22 13:21:45 +02:00
amacocian ec343797b4 Additional extension to scoped logger 2024-08-21 11:25:57 +02:00
amacocian 8a58a6e9a3 Improve HttpClient with client constructor 2024-08-16 12:51:20 +02:00
amacocian 3131558017 Change HttpClientBuilder to work on IServiceCollection 2024-06-04 12:37:09 +02:00
amacocian 6a541e07a3 Update dependencies 2024-04-18 17:53:42 +02:00
amacocian 791387fb29 Circular buffer implementation 2024-04-18 17:45:00 +02:00
amacocian b7244b2bdb Replace Moq with NSubstitute (#51) 2023-08-09 16:11:11 +02:00
amacocian fe049d4c00 Add extension to recycle websockets (#50)
Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2023-05-15 09:21:29 +00:00
amacocian 0de73a967b Client Web Socket fix (#49)
* Fix for clientwebsocket

* Updated dependencies
2023-05-14 23:13:33 +00:00
amacocian 8d7676bedf Fix for ClientWebSocketResolver (#48) 2023-05-14 22:15:35 +00:00
amacocian 2ed3de439e Client web socket resolver (#47) 2023-05-14 21:19:32 +00:00
amacocian de08c0e3b0 Client Web Socket implementation (#46) 2023-05-14 20:46:38 +00:00
dependabot[bot] a25f2e5acb Bump FluentAssertions from 6.7.0 to 6.9.0 (#41)
Bumps [FluentAssertions](https://github.com/fluentassertions/fluentassertions) from 6.7.0 to 6.9.0.
- [Release notes](https://github.com/fluentassertions/fluentassertions/releases)
- [Changelog](https://github.com/fluentassertions/fluentassertions/blob/develop/AcceptApiChanges.ps1)
- [Commits](https://github.com/fluentassertions/fluentassertions/compare/6.7.0...6.9.0)

---
updated-dependencies:
- dependency-name: FluentAssertions
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 21:37:09 +01:00
dependabot[bot] 8e64309c2e Bump actions/setup-dotnet from 2 to 3 (#32)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 2 to 3.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-11 12:16:48 +02:00
dependabot[bot] 24c3e37b5e Bump Microsoft.NET.Test.Sdk from 17.3.1 to 17.3.2 (#31)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.1 to 17.3.2.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Commits](https://github.com/microsoft/vstest/compare/v17.3.1...v17.3.2)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-27 11:44:21 +02:00
amacocian f6435b24ab Increment dependency injection version (#29) 2022-09-12 19:28:10 +02:00
amacocian 9118c864bf Make httpclient use a factory to create handlers (#28) 2022-09-12 17:22:55 +00:00
amacocian fb84036215 Bump slim to 1.9.2 (#27)
Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>

Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2022-09-12 09:59:05 +00:00
amacocian f6fd9ec616 Update Slim (#26) 2022-09-09 15:05:48 +00:00
dependabot[bot] 6a10fe91e3 Bump actions/checkout from 2 to 3 (#22)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-06 14:20:27 +00:00
dependabot[bot] e890b21bb6 Bump actions/setup-dotnet from 1 to 2 (#21)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 1 to 2.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v1...v2)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-06 14:12:59 +00:00
dependabot[bot] e36e6605ff Bump Microsoft.NET.Test.Sdk from 17.2.0 to 17.3.1 (#23)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.2.0 to 17.3.1.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Commits](https://github.com/microsoft/vstest/compare/v17.2.0...v17.3.1)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-06 16:06:51 +02:00
dependabot[bot] 5e8e772d36 Bump Moq from 4.18.1 to 4.18.2 (#24)
Bumps [Moq](https://github.com/moq/moq4) from 4.18.1 to 4.18.2.
- [Release notes](https://github.com/moq/moq4/releases)
- [Changelog](https://github.com/moq/moq4/blob/main/CHANGELOG.md)
- [Commits](https://github.com/moq/moq4/compare/v4.18.1...v4.18.2)

---
updated-dependencies:
- dependency-name: Moq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-06 15:58:34 +02:00
amacocian e6a06915cd Improve IHttpClient factories (#20)
Codestyle fixes
Setup CODEOWNERS
Setup dependabot
2022-09-06 15:49:07 +02:00
amacocian 3c741ab969 Make AesEncrypter disposable (#19) 2022-06-07 12:49:39 +02:00
amacocian a46fde652d Expose Aes object in AesEncrypter constructor (#18) 2022-06-07 12:26:08 +02:00
amacocian ecc65a4302 Update dependencies for NetStandard.DependencyInjection (#17) 2022-06-02 11:35:08 +02:00
amacocian 7222528eed Update dependencies (#16)
* Update dependencies
Implement AsyncLazy
Fix namespaces
Fix CD pipeline

* Fix code issues
Fix Int64BitStruct test
2022-06-02 10:42:34 +02:00
amacocian 41b1b48f98 Remove null check for flow id in scoped loggers (#15) 2022-01-23 21:13:37 +01:00
amacocian 721b444dda Scoped logger (#14)
Logging extensions
2022-01-16 15:14:23 +01:00
amacocian 1c7d3a2be2 Adjust project descriptions (#12)
* Adjust package description
2022-01-02 16:54:15 +02:00
amacocian 0a30872781 Introduce .netcore extensions (#11)
* Introduce .netcore extensions
Implement ArgumentNullException for .net6 and c#10

* Update pipelines to install .netcore 6

* Update test projects to .net 6.0
2022-01-02 00:10:49 +02:00
Alexandru Macocian 7eed1ca466 Unify namespace naming 2021-11-15 22:21:46 +01:00
151 changed files with 8711 additions and 7907 deletions
+129
View File
@@ -0,0 +1,129 @@
[*.{cs,vb}]
# IDE0003: Remove qualification
dotnet_diagnostic.IDE0003.severity = error
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error
dotnet_style_prefer_auto_properties = true:error
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
[*.cs]
csharp_indent_labels = one_less_than_current
csharp_space_around_binary_operators = before_and_after
csharp_using_directive_placement = outside_namespace:error
csharp_prefer_simple_using_statement = true:error
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = file_scoped:error
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_throw_expression = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:error
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_deconstructed_variable_declaration = true:error
csharp_style_unused_value_assignment_preference = discard_variable:error
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_prefer_static_local_function = true:suggestion
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_prefer_parameter_null_checking = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_readonly_field = true:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion
dotnet_code_quality_unused_parameters = all:error
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
dotnet_style_qualification_for_field = true:error
dotnet_style_qualification_for_property = true:error
dotnet_style_qualification_for_method = true:error
dotnet_style_qualification_for_event = true:error
+1
View File
@@ -0,0 +1 @@
* amacocian@yahoo.com
+16
View File
@@ -0,0 +1,16 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "nuget"
directory: "/"
schedule:
interval: "daily"
+26 -19
View File
@@ -17,22 +17,23 @@ jobs:
env:
Configuration: Release
Solution_Path: SystemExtensions.sln
Solution_Path: SystemExtensions.slnx
Test_Project_Path: SystemExtensions.Tests\SystemExtensions.NetStandard.Tests.csproj
DI_Test_Project_Path: SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj
Source_Project_Path: SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
Core_Project_Path: SystemExtensions.NetCore\SystemExtensions.NetCore.csproj
Actions_Allow_Unsecure_Commands: true
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: '5.0.202'
dotnet-version: '10.0.x'
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.0.1
@@ -45,26 +46,32 @@ jobs:
- name: Build SystemExtensions.NetStandard project
run: dotnet build SystemExtensions.NetStandard -c $env:Configuration
- name: Build SystemExtensions.NetCore project
run: dotnet build SystemExtensions.NetCore -c $env:Configuration
- name: Build SystemExtensions.NetStandard.DependencyInjection project
run: dotnet build SystemExtensions.NetStandard.DependencyInjection -c $env:Configuration
- name: Build SystemExtensions.NetStandard.Security project
run: dotnet build SystemExtensions.NetStandard.Security -c $env:Configuration
- name: Push SystemExtensions.NetStandard nuget package
uses: brandedoutcast/publish-nuget@v2.5.5
with:
PROJECT_FILE_PATH: SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
- name: Build SystemExtensions.NetStandard.Generators project
run: dotnet build SystemExtensions.NetStandard.Generators -c $env:Configuration
- name: Push SystemExtensions.NetStandard.DependencyInjection nuget package
uses: brandedoutcast/publish-nuget@v2.5.5
with:
PROJECT_FILE_PATH: SystemExtensions.NetStandard.DependencyInjection\SystemExtensions.NetStandard.DependencyInjection.csproj
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
- name: Package SystemExtensions.NetStandard
run: dotnet pack -c Release -o . SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
- name: Push SystemExtensions.NetStandard.Security nuget package
uses: brandedoutcast/publish-nuget@v2.5.5
with:
PROJECT_FILE_PATH: SystemExtensions.NetStandard.Security\SystemExtensions.NetStandard.Security.csproj
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
- name: Package SystemExtensions.NetCore
run: dotnet pack -c Release -o . SystemExtensions.NetCore\SystemExtensions.NetCore.csproj
- name: Package SystemExtensions.NetStandard.DependencyInjection
run: dotnet pack -c Release -o . SystemExtensions.NetStandard.DependencyInjection\SystemExtensions.NetStandard.DependencyInjection.csproj
- name: Package SystemExtensions.NetStandard.Security
run: dotnet pack -c Release -o . SystemExtensions.NetStandard.Security\SystemExtensions.NetStandard.Security.csproj
- name: Package SystemExtensions.NetStandard.Generators
run: dotnet pack -c Release -o . SystemExtensions.NetStandard.Generators\SystemExtensions.NetStandard.Generators.csproj
- name: Publish
run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
+4 -4
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: windows-latest
env:
Solution_Path: SystemExtensions.sln
Solution_Path: SystemExtensions.slnx
Test_Project_Path: SystemExtensions.Tests\SystemExtensions.NetStandard.Tests.csproj
DI_Test_Project_Path: SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj
Sec_Test_Project_Path: SystemExtensions.NetStandard.Security.Tests\SystemExtensions.NetStandard.Security.Tests.csproj
@@ -28,14 +28,14 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: '5.0.202'
dotnet-version: '10.0.x'
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.0.1
@@ -1,26 +0,0 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Configuration;
namespace SystemExtensions.DependencyInjection.Tests.Configuration
{
[TestClass]
public class DefaultOptionsManagerTests
{
private readonly DefaultOptionsManager optionsManager = new();
[TestMethod]
public void GetOptions_ReturnsDefault()
{
var options = this.optionsManager.GetOptions<string>();
options.Should().BeNull();
}
[TestMethod]
public void UpdateOptions_Succeeds()
{
this.optionsManager.UpdateOptions(string.Empty);
}
}
}
@@ -1,6 +0,0 @@
namespace SystemExtensions.DependencyInjection.Tests.Configuration
{
public sealed class DummyOptions
{
}
}
@@ -1,69 +0,0 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Configuration;
namespace SystemExtensions.DependencyInjection.Tests.Configuration
{
[TestClass]
public class LiveOptionsResolverTests
{
private readonly LiveOptionsResolver liveOptionsResolver = new();
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
private readonly Mock<IOptionsManager> optionsManagerMock = new();
[TestMethod]
public void CanResolve_ILiveOptions_ReturnsTrue()
{
var type = typeof(ILiveOptions<string>);
var canResolve = this.liveOptionsResolver.CanResolve(type);
canResolve.Should().BeTrue();
}
[TestMethod]
public void CanResolve_AnythingElse_ReturnsFalse()
{
var types = new Type[] { typeof(object), typeof(string), typeof(LiveOptionsResolverTests), typeof(int) };
foreach (var type in types)
{
var canResolve = this.liveOptionsResolver.CanResolve(type);
canResolve.Should().BeFalse();
}
}
[TestMethod]
public void Resolve_ILiveOptions_ReturnsILiveOptions()
{
this.SetupServiceProvider();
var liveOptions = this.liveOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(ILiveOptions<string>));
liveOptions.Should().BeAssignableTo<ILiveOptions<string>>();
}
[TestMethod]
public void Resolve_AnythingElse_Throws()
{
this.SetupServiceProvider();
Action action = new(() =>
{
this.liveOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
});
action.Should().Throw<Exception>();
}
private void SetupServiceProvider()
{
this.serviceProviderMock
.Setup(u => u.GetService<IOptionsManager>())
.Returns(this.optionsManagerMock.Object);
}
}
}
@@ -1,69 +0,0 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Configuration;
namespace SystemExtensions.DependencyInjection.Tests.Configuration
{
[TestClass]
public class LiveUpdateableOptionsResolverTests
{
private readonly LiveUpdateableOptionsResolver liveUpdateableOptionsResolver = new();
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
private readonly Mock<IOptionsManager> optionsManagerMock = new();
[TestMethod]
public void CanResolve_ILiveUpdateableOptions_ReturnsTrue()
{
var type = typeof(ILiveUpdateableOptions<string>);
var canResolve = this.liveUpdateableOptionsResolver.CanResolve(type);
canResolve.Should().BeTrue();
}
[TestMethod]
public void CanResolve_AnythingElse_ReturnsFalse()
{
var types = new Type[] { typeof(object), typeof(string), typeof(LiveOptionsResolverTests), typeof(int) };
foreach (var type in types)
{
var canResolve = this.liveUpdateableOptionsResolver.CanResolve(type);
canResolve.Should().BeFalse();
}
}
[TestMethod]
public void Resolve_ILiveUpdateableOptions_ReturnsILiveUpdateableOptions()
{
this.SetupServiceProvider();
var liveOptions = this.liveUpdateableOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(ILiveUpdateableOptions<string>));
liveOptions.Should().BeAssignableTo<ILiveUpdateableOptions<string>>();
}
[TestMethod]
public void Resolve_AnythingElse_Throws()
{
this.SetupServiceProvider();
Action action = new(() =>
{
this.liveUpdateableOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
});
action.Should().Throw<Exception>();
}
private void SetupServiceProvider()
{
this.serviceProviderMock
.Setup(u => u.GetService<IOptionsManager>())
.Returns(this.optionsManagerMock.Object);
}
}
}
@@ -1,44 +0,0 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Configuration;
namespace SystemExtensions.DependencyInjection.Tests.Configuration
{
[TestClass]
public class LiveUpdateableOptionsWrapperTests
{
private LiveUpdateableOptionsWrapper<string> optionsWrapper;
private readonly Mock<IOptionsManager> optionsManagerMock = new();
[TestInitialize]
public void TestInitialize()
{
this.optionsWrapper = new LiveUpdateableOptionsWrapper<string>(this.optionsManagerMock.Object);
}
[TestMethod]
public void GetValue_ReturnsValue()
{
this.optionsManagerMock
.Setup(u => u.GetOptions<string>())
.Returns("hello");
var value = this.optionsWrapper.Value;
value.Should().Be("hello");
}
[TestMethod]
public void UpdateOption_CallsOptionsManager()
{
this.optionsManagerMock
.Setup(u => u.UpdateOptions<string>(It.IsAny<string>()))
.Verifiable();
this.optionsWrapper.UpdateOption();
this.optionsManagerMock.Verify();
}
}
}
@@ -1,72 +0,0 @@
using FluentAssertions;
using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Configuration;
namespace SystemExtensions.DependencyInjection.Tests.Configuration
{
[TestClass]
public class OptionsResolverTests
{
private readonly OptionsResolver optionsResolver = new();
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
private readonly Mock<IOptionsManager> optionsManagerMock = new();
public Mock<IOptionsManager> OptionsManagerMock => optionsManagerMock;
[TestMethod]
public void CanResolve_ILiveOptions_ReturnsTrue()
{
var type = typeof(IOptions<string>);
var canResolve = this.optionsResolver.CanResolve(type);
canResolve.Should().BeTrue();
}
[TestMethod]
public void CanResolve_AnythingElse_ReturnsFalse()
{
var types = new Type[] { typeof(object), typeof(string), typeof(OptionsResolverTests), typeof(int) };
foreach (var type in types)
{
var canResolve = this.optionsResolver.CanResolve(type);
canResolve.Should().BeFalse();
}
}
[TestMethod]
public void Resolve_IOptions_ReturnsIOptions()
{
this.SetupServiceProvider();
var liveOptions = this.optionsResolver.Resolve(this.serviceProviderMock.Object, typeof(IOptions<string>));
liveOptions.Should().BeAssignableTo<IOptions<string>>();
}
[TestMethod]
public void Resolve_AnythingElse_Throws()
{
this.SetupServiceProvider();
Action action = new(() =>
{
this.optionsResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
});
action.Should().Throw<Exception>();
}
private void SetupServiceProvider()
{
this.serviceProviderMock
.Setup(u => u.GetService<IOptionsManager>())
.Returns(this.OptionsManagerMock.Object);
}
}
}
@@ -1,69 +0,0 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Configuration;
namespace SystemExtensions.DependencyInjection.Tests.Configuration
{
[TestClass]
public class UpdateableOptionsResolverTests
{
private readonly UpdateableOptionsResolver updateableOptionsResolver = new();
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
private readonly Mock<IOptionsManager> optionsManagerMock = new();
[TestMethod]
public void CanResolve_ILiveOptions_ReturnsTrue()
{
var type = typeof(IUpdateableOptions<string>);
var canResolve = this.updateableOptionsResolver.CanResolve(type);
canResolve.Should().BeTrue();
}
[TestMethod]
public void CanResolve_AnythingElse_ReturnsFalse()
{
var types = new Type[] { typeof(object), typeof(string), typeof(UpdateableOptionsResolverTests), typeof(int) };
foreach (var type in types)
{
var canResolve = this.updateableOptionsResolver.CanResolve(type);
canResolve.Should().BeFalse();
}
}
[TestMethod]
public void Resolve_IUpdateableOptions_ReturnsIUpdateableOptions()
{
this.SetupServiceProvider();
var liveOptions = this.updateableOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(IUpdateableOptions<string>));
liveOptions.Should().BeAssignableTo<IUpdateableOptions<string>>();
}
[TestMethod]
public void Resolve_AnythingElse_Throws()
{
this.SetupServiceProvider();
Action action = new(() =>
{
this.updateableOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
});
action.Should().Throw<Exception>();
}
private void SetupServiceProvider()
{
this.serviceProviderMock
.Setup(u => u.GetService<IOptionsManager>())
.Returns(this.optionsManagerMock.Object);
}
}
}
@@ -1,48 +0,0 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Configuration;
using System.Extensions.Configuration;
namespace SystemExtensions.DependencyInjection.Tests.Configuration
{
[TestClass]
public class UpdateableOptionsWrapperTests
{
private const string Value = "hello";
private UpdateableOptionsWrapper<string> optionsWrapper;
private readonly Mock<IOptionsManager> optionsManagerMock = new();
[TestInitialize]
public void TestInitialize()
{
this.optionsWrapper = new UpdateableOptionsWrapper<string>(optionsManagerMock.Object, Value);
}
[TestMethod]
public void GetValue_ReturnsValue()
{
this.optionsManagerMock
.Setup(u => u.GetOptions<string>())
.Throws<Exception>();
var value = this.optionsWrapper.Value;
value.Should().Be(Value);
}
[TestMethod]
public void UpdateOption_CallsOptionsManager()
{
this.optionsManagerMock
.Setup(u => u.UpdateOptions<string>(It.IsAny<string>()))
.Verifiable();
this.optionsWrapper.UpdateOption();
this.optionsManagerMock.Verify();
}
}
}
@@ -0,0 +1,96 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using SystemExtensions.NetStandard.DependencyInjection.Tests.Http.Models;
namespace SystemExtensions.NetStandard.DependencyInjection.Tests.Http;
[TestClass]
public sealed class HttpClientBuilderTests
{
private const string SomeHeader = "SomeHeader";
private const string SomeValue = "SomeValue";
private readonly HttpClientBuilder<object> httpClientBuilder;
private readonly IServiceCollection serviceProducerMock = Substitute.For<IServiceCollection>();
private readonly Uri baseAddress = new("http://contoso.co");
public HttpClientBuilderTests()
{
this.httpClientBuilder = new HttpClientBuilder<object>(this.serviceProducerMock);
}
[TestMethod]
public void Constructor_NullServiceProducer_Throws()
{
var action = () => new HttpClientBuilder<object>(null);
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void WithMessageHandler_NullHandler_Throws()
{
var action = () => this.httpClientBuilder.WithMessageHandler(null);
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void WithBaseAddress_NullBaseAddress_Throws()
{
var action = () => this.httpClientBuilder.WithBaseAddress(null);
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void WithDefaultRequestHeadersSetup_NullSetup_Throws()
{
var action = () => this.httpClientBuilder.WithDefaultRequestHeadersSetup(null);
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void Build_ReturnsServiceProducer()
{
var producer = this.httpClientBuilder.Build();
producer.Should().BeEquivalentTo(this.serviceProducerMock);
}
[TestMethod]
public async Task HttpClientBuilder_RegistersClientCorrectly_IServiceProviderReturnsExpected()
{
var container = new ServiceCollection();
var messageHandler = new HttpMessageHandlerMock();
new HttpClientBuilder<object>(container)
.WithBaseAddress(this.baseAddress)
.WithDefaultRequestHeadersSetup(header => header.TryAddWithoutValidation(SomeHeader, SomeValue))
.WithMaxResponseBufferSize(5)
.WithMessageHandler(sp => messageHandler)
.WithTimeout(TimeSpan.FromSeconds(5))
.Build();
var di = container.BuildServiceProvider();
var client = di.GetService<IHttpClient<object>>();
await client.GetAsync("");
client.Should().NotBeNull();
client.BaseAddress.Should().Be(this.baseAddress);
client.DefaultRequestHeaders.Should().HaveCount(1);
client.DefaultRequestHeaders.First().Key.Should().Be(SomeHeader);
client.DefaultRequestHeaders.First().Value.Should().HaveCount(1);
client.DefaultRequestHeaders.First().Value.First().Should().Be(SomeValue);
client.MaxResponseContentBufferSize.Should().Be(5);
client.Timeout.Should().Be(TimeSpan.FromSeconds(5));
messageHandler.Called.Should().BeTrue();
}
}
@@ -1,69 +0,0 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Http;
using System.Net.Http;
namespace SystemExtensions.DependencyInjection.Tests.Http
{
[TestClass]
public class HttpClientResolverTests
{
private readonly HttpClientResolver httpClientResolver = new();
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
[TestMethod]
public void CanResolve_IHttpClient_ReturnsTrue()
{
var type = typeof(IHttpClient<>);
var canResolve = this.httpClientResolver.CanResolve(type);
canResolve.Should().BeTrue();
}
[TestMethod]
public void CanResolve_AnythingElse_ReturnsFalse()
{
var types = new Type[] { typeof(HttpClient), typeof(object), typeof(string), typeof(HttpClientResolverTests), typeof(int) };
foreach (var type in types)
{
var canResolve = this.httpClientResolver.CanResolve(type);
canResolve.Should().BeFalse();
}
}
[TestMethod]
public void Resolve_TypedClient_ReturnsIHttpClient()
{
var client = this.httpClientResolver.Resolve(this.serviceProviderMock.Object, typeof(IHttpClient<string>));
client.Should().BeAssignableTo<IHttpClient<string>>();
}
[TestMethod]
public void Resolve_NonGenericType_Throws()
{
Action action = new(() =>
{
this.httpClientResolver.Resolve(this.serviceProviderMock.Object, typeof(IHttpClient<>));
});
action.Should().Throw<Exception>();
}
[TestMethod]
public void Resolve_RandomType_Throws()
{
Action action = new(() =>
{
this.httpClientResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
});
action.Should().Throw<Exception>();
}
}
}
@@ -0,0 +1,16 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace SystemExtensions.NetStandard.DependencyInjection.Tests.Http.Models;
public sealed class HttpMessageHandlerMock : HttpMessageHandler
{
public bool Called { get; private set; }
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
this.Called = true;
return Task.FromResult(new HttpResponseMessage());
}
}
@@ -1,43 +0,0 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Logging;
namespace SystemExtensions.DependencyInjection.Tests.Logging
{
[TestClass]
public class CVLoggerProviderTests
{
private readonly Mock<ILogsWriter> logsWriterMock = new();
private CVLoggerProvider cVLoggerProvider;
[TestInitialize]
public void TestInitialize()
{
this.cVLoggerProvider = new CVLoggerProvider(this.logsWriterMock.Object);
}
[TestMethod]
public void CreateLogger_CreatesNewLogger()
{
var logger = this.cVLoggerProvider.CreateLogger(string.Empty);
logger.Should().NotBeNull();
}
[TestMethod]
public void LogEntry_CallsLogWriter()
{
this.cVLoggerProvider.LogEntry(new Log());
this.logsWriterMock.Verify();
}
private void SetupLogsWriter()
{
this.logsWriterMock
.Setup(u => u.WriteLog(It.IsAny<Log>()))
.Verifiable();
}
}
}
@@ -1,76 +0,0 @@
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Logging;
namespace SystemExtensions.DependencyInjection.Tests.Logging
{
[TestClass]
public class CVLoggerTests
{
private readonly Mock<ICVLoggerProvider> cvLoggerProviderMock = new();
private CVLogger cVLogger;
[TestInitialize]
public void TestInitialize()
{
this.cVLogger = new CVLogger(string.Empty, this.cvLoggerProviderMock.Object);
}
[TestMethod]
public void BeginScope_ReturnsNull()
{
var scope = this.cVLogger.BeginScope(string.Empty);
scope.Should().BeNull();
}
[TestMethod]
[DataRow(LogLevel.Debug)]
[DataRow(LogLevel.Trace)]
[DataRow(LogLevel.Information)]
[DataRow(LogLevel.Warning)]
[DataRow(LogLevel.Error)]
[DataRow(LogLevel.Critical)]
public void IsEnabled_OnAllLogLevels_ReturnsTrue(LogLevel logLevel)
{
var enabled = this.cVLogger.IsEnabled(logLevel);
enabled.Should().BeTrue();
}
[TestMethod]
public void Log_CallsFormatter()
{
var called = false;
Func<string, Exception, string> messageFormatter = new((state, exception) =>
{
called = true;
return string.Empty;
});
this.cVLogger.Log(LogLevel.Debug, new EventId(), "Some message", new Exception(), messageFormatter);
called.Should().BeTrue();
}
[TestMethod]
public void Log_CallsLogsProvider()
{
this.SetupLoggerProvider();
this.cVLogger.Log(LogLevel.Debug, new EventId(), "Some message", new Exception(), new Func<string, Exception, string>((s, e) => string.Empty));
this.cvLoggerProviderMock.Verify();
}
private void SetupLoggerProvider()
{
this.cvLoggerProviderMock
.Setup(u => u.LogEntry(It.IsAny<Log>()))
.Verifiable();
}
}
}
@@ -1,30 +0,0 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Logging;
namespace SystemExtensions.DependencyInjection.Tests.Logging
{
[TestClass]
public class DebugLogsWriterTests
{
private readonly DebugLogsWriter logsWriter = new();
[TestMethod]
public void WriteLog_Succeeds()
{
this.logsWriter.WriteLog(new Log());
}
[TestMethod]
public void WriteNullLog_Throws()
{
Action action = new(() =>
{
this.logsWriter.WriteLog(null);
});
action.Should().Throw<Exception>();
}
}
}
@@ -1,108 +0,0 @@
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Logging;
namespace SystemExtensions.DependencyInjection.Tests.Logging
{
[TestClass]
public class LoggerResolverTests
{
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
private readonly Mock<ILoggerFactory> loggerFactoryMock = new();
private readonly Mock<ILogger> loggerMock = new();
private readonly LoggerResolver loggerResolver = new();
[TestMethod]
public void CanResolve_ILogger_ReturnsTrue()
{
var type = typeof(ILogger);
var canResolve = this.loggerResolver.CanResolve(type);
canResolve.Should().BeTrue();
}
[TestMethod]
public void CanResolve_GenericILogger_ReturnsTrue()
{
var type = typeof(ILogger<string>);
var canResolve = this.loggerResolver.CanResolve(type);
canResolve.Should().BeTrue();
}
[TestMethod]
public void CanResolve_AnythingElse_ReturnsFalse()
{
var types = new Type[] { typeof(CVLogger), typeof(object), typeof(string), typeof(LoggerResolverTests), typeof(int) };
foreach(var type in types)
{
var canResolve = this.loggerResolver.CanResolve(type);
canResolve.Should().BeFalse();
}
}
[TestMethod]
public void Resolve_ILogger_ReturnsILogger()
{
this.SetupIServiceProvider();
var logger = this.loggerResolver.Resolve(this.serviceProviderMock.Object, typeof(ILogger));
logger.Should().BeAssignableTo<ILogger>();
}
[TestMethod]
public void Resolve_TypedILogger_ReturnsTypedILogger()
{
this.SetupIServiceProvider();
var logger = this.loggerResolver.Resolve(this.serviceProviderMock.Object, typeof(ILogger<string>));
logger.Should().BeAssignableTo<ILogger<string>>();
}
[TestMethod]
public void Resolve_RandomType_Throws()
{
this.SetupIServiceProvider();
Action action = new(() =>
{
this.loggerResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
});
action.Should().Throw<Exception>();
}
[TestMethod]
public void Resolve_NonTypedGeneric_Throws()
{
this.SetupIServiceProvider();
Action action = new(() =>
{
this.loggerResolver.Resolve(this.serviceProviderMock.Object, typeof(ILogger<>));
});
action.Should().Throw<Exception>();
}
private void SetupIServiceProvider()
{
this.serviceProviderMock
.Setup(u => u.GetService<ILoggerFactory>())
.Returns(this.loggerFactoryMock.Object);
this.loggerFactoryMock
.Setup(u => u.CreateLogger(It.IsAny<string>()))
.Returns(this.loggerMock.Object);
}
}
}
@@ -1,18 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
<PackageReference Include="coverlet.collector" Version="3.0.2" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.5.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.5.2" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.17">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,70 @@
using FluentAssertions;
using global::SystemExtensions.NetStandard.DependencyInjection.Tests.Http.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSubstitute;
using System;
using System.Extensions;
using System.Linq;
using System.Net.Http;
using System.Net.WebSockets;
using System.Threading.Tasks;
namespace SystemExtensions.NetStandard.DependencyInjection.Tests.WebSockets;
[TestClass]
public sealed class HttpClientBuilderTests
{
private readonly ClientWebSocketBuilder<object> clientWebSocketBuilder;
private readonly IServiceCollection serviceProducerMock = Substitute.For<IServiceCollection>();
public HttpClientBuilderTests()
{
this.clientWebSocketBuilder = new ClientWebSocketBuilder<object>(this.serviceProducerMock);
}
[TestMethod]
public void Constructor_NullServiceProducer_Throws()
{
var action = () => new ClientWebSocketBuilder<object>(null);
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void WithInnerWebSocket_NullHandler_Throws()
{
var action = () => this.clientWebSocketBuilder.WithInnerWebSocket(null);
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void WIthInnerWebSocketFactory_NullBaseAddress_Throws()
{
var action = () => this.clientWebSocketBuilder.WithInnerWebSocketFactory(null);
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void Build_ReturnsServiceProducer()
{
var producer = this.clientWebSocketBuilder.Build();
producer.Should().BeEquivalentTo(this.serviceProducerMock);
}
[TestMethod]
public async Task ClientWebSocketBuilder_RegistersClientCorrectly_IServiceProviderReturnsExpected()
{
var container = new ServiceCollection();
var innerWebSocket = new ClientWebSocket();
container.RegisterClientWebSocket<object>()
.WithInnerWebSocket(innerWebSocket)
.Build();
var di = container.BuildServiceProvider();
var client = di.GetService<IClientWebSocket<object>>();
client.Should().NotBeNull();
}
}
@@ -0,0 +1,17 @@
using Microsoft.Extensions.Logging;
using System.Logging;
using System.Runtime.CompilerServices;
namespace System.Extensions.Core;
public static class LoggingExtensions
{
public static ScopedLogger<T> CreateScopedLogger<T>(this ILogger<T> logger, string flowIdentifier, [CallerMemberName] string? methodName = default)
{
return ScopedLogger<T>.Create(logger, methodName ?? string.Empty, flowIdentifier);
}
public static ScopedLogger<T> CreateScopedLogger<T>(this ILogger<T> logger, [CallerMemberName] string? methodName = default)
{
return ScopedLogger<T>.Create(logger, methodName ?? string.Empty, string.Empty);
}
}
@@ -0,0 +1,18 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace System.Core.Extensions;
public static class ObjectExtensions
{
public static T ThrowIfNull<T>([NotNull] this T? obj, [CallerArgumentExpression("obj")] string? paramName = null)
where T : class
{
if (obj is null)
{
throw new ArgumentNullException(paramName);
}
return obj;
}
}
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Nullable>enable</Nullable>
<RootNamespace>System</RootNamespace>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Version>1.9</Version>
<Authors>Alexandru Macocian</Authors>
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
<Description>Extensions for the System namespace</Description>
</PropertyGroup>
<ItemGroup>
<None Include="../LICENSE">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj" />
</ItemGroup>
</Project>
@@ -1,14 +0,0 @@
namespace System.Configuration
{
public sealed class DefaultOptionsManager : IOptionsManager
{
public T GetOptions<T>() where T : class
{
return default;
}
public void UpdateOptions<T>(T value) where T : class
{
}
}
}
@@ -1,9 +0,0 @@
using Microsoft.Extensions.Options;
namespace System.Configuration
{
public interface ILiveOptions<T> : IOptions<T>
where T : class
{
}
}
@@ -1,7 +0,0 @@
namespace System.Configuration
{
public interface ILiveUpdateableOptions<T> : ILiveOptions<T>, IUpdateableOptions<T>
where T : class
{
}
}
@@ -1,10 +0,0 @@
namespace System.Configuration
{
public interface IOptionsManager
{
T GetOptions<T>()
where T : class;
void UpdateOptions<T>(T value)
where T : class;
}
}
@@ -1,13 +0,0 @@
using Microsoft.Extensions.Options;
namespace System.Configuration
{
public interface IUpdateableOptions<out T> : IOptions<T>
where T : class
{
/// <summary>
/// Updates the configuration with the current value stored in <see cref="IOptions{TOptions}.Value"/>.
/// </summary>
void UpdateOption();
}
}
@@ -1,27 +0,0 @@
using Slim.Resolvers;
namespace System.Configuration
{
public sealed class LiveOptionsResolver : IDependencyResolver
{
private static readonly Type optionsType = typeof(LiveUpdateableOptionsWrapper<>);
public bool CanResolve(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILiveOptions<>))
{
return true;
}
return false;
}
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
{
var typedOptionsType = optionsType.MakeGenericType(type.GetGenericArguments());
var configurationManager = serviceProvider.GetService<IOptionsManager>();
return Activator.CreateInstance(typedOptionsType, configurationManager);
}
}
}
@@ -1,26 +0,0 @@
using Slim.Resolvers;
namespace System.Configuration
{
public sealed class LiveUpdateableOptionsResolver : IDependencyResolver
{
private static readonly Type optionsType = typeof(LiveUpdateableOptionsWrapper<>);
public bool CanResolve(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILiveUpdateableOptions<>))
{
return true;
}
return false;
}
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
{
var typedOptionsType = optionsType.MakeGenericType(type.GetGenericArguments());
var configurationManager = serviceProvider.GetService<IOptionsManager>();
return Activator.CreateInstance(typedOptionsType, configurationManager);
}
}
}
@@ -1,31 +0,0 @@
using System.Extensions;
namespace System.Configuration
{
public sealed class LiveUpdateableOptionsWrapper<T> : ILiveUpdateableOptions<T>
where T : class
{
private readonly IOptionsManager configurationManager;
private T value;
public T Value
{
get
{
this.value = this.configurationManager.GetOptions<T>();
return this.value;
}
}
public LiveUpdateableOptionsWrapper(IOptionsManager configurationManager)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
}
public void UpdateOption()
{
this.configurationManager.UpdateOptions<T>(this.value);
}
}
}
@@ -1,33 +0,0 @@
using Microsoft.Extensions.Options;
using Slim.Resolvers;
namespace System.Configuration
{
public sealed class OptionsResolver : IDependencyResolver
{
private static readonly Type optionsType = typeof(OptionsWrapper<>);
private static readonly Type configurationType = typeof(IOptionsManager);
public bool CanResolve(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IOptions<>))
{
return true;
}
return false;
}
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
{
var typedOptionsType = optionsType.MakeGenericType(type.GetGenericArguments());
var configurationManager = serviceProvider.GetService<IOptionsManager>();
var optionsValue = configurationType
.GetMethod(nameof(IOptionsManager.GetOptions))
.MakeGenericMethod(type.GetGenericArguments())
.Invoke(configurationManager, Array.Empty<object>());
return Activator.CreateInstance(typedOptionsType, optionsValue);
}
}
}
@@ -1,33 +0,0 @@
using Slim.Resolvers;
using System.Extensions.Configuration;
namespace System.Configuration
{
public sealed class UpdateableOptionsResolver : IDependencyResolver
{
private static readonly Type optionsType = typeof(UpdateableOptionsWrapper<>);
private static readonly Type configurationType = typeof(IOptionsManager);
public bool CanResolve(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IUpdateableOptions<>))
{
return true;
}
return false;
}
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
{
var typedOptionsType = optionsType.MakeGenericType(type.GetGenericArguments());
var configurationManager = serviceProvider.GetService<IOptionsManager>();
var optionsValue = configurationType
.GetMethod(nameof(IOptionsManager.GetOptions))
.MakeGenericMethod(type.GetGenericArguments())
.Invoke(configurationManager, Array.Empty<object>());
return Activator.CreateInstance(typedOptionsType, configurationManager, optionsValue);
}
}
}
@@ -1,23 +0,0 @@
using System.Configuration;
namespace System.Extensions.Configuration
{
public sealed class UpdateableOptionsWrapper<T> : IUpdateableOptions<T>
where T : class
{
private readonly IOptionsManager configurationManager;
public T Value { get; }
public UpdateableOptionsWrapper(IOptionsManager configurationManager, T options)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.Value = options;
}
public void UpdateOption()
{
this.configurationManager.UpdateOptions(this.Value);
}
}
}
@@ -0,0 +1,34 @@
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
using System.Net.WebSockets;
namespace System.Extensions;
public static class ServiceCollectionExtensions
{
/// <summary>
/// Register a scoped <see cref="IHttpClient{TScope}"/> to be used by the DI engine.
/// </summary>
/// <typeparam name="T">Type of the scoped <see cref="IHttpClient{TScope}"/>.</typeparam>
/// <param name="services"><see cref="IServiceCollection"/>.</param>
/// <returns><see cref="HttpClientBuilder{T}"/> to build the http client.</returns>
public static HttpClientBuilder<T> RegisterHttpClient<T>(this IServiceCollection services)
{
services.ThrowIfNull(nameof(services));
return new HttpClientBuilder<T>(services);
}
/// <summary>
/// Register a scoped <see cref="IClientWebSocket{TScope}"/> to be used by the DI engine.
/// </summary>
/// <typeparam name="T">Type of the scoped <see cref="IClientWebSocket{TScope}"/>.</typeparam>
/// <param name="services"><see cref="IServiceCollection"/>.</param>
/// <returns><see cref="ClientWebSocketBuilder{T}"/> to build the websocket client.</returns>
public static ClientWebSocketBuilder<T> RegisterClientWebSocket<T>(this IServiceCollection services)
{
services.ThrowIfNull(nameof(services));
return new ClientWebSocketBuilder<T>(services);
}
}
@@ -1,158 +0,0 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Slim;
using System.Configuration;
using System.Http;
using System.Logging;
using System.Net.Http;
namespace System.Extensions
{
public static class ServiceManagerExtensions
{
/// <summary>
/// Registers a <see cref="IOptionsManager"/> with the default <see cref="DefaultOptionsManager"/>.
/// This call also registers the resolver that resolves <see cref="IUpdateableOptions{T}"/> and <see cref="IOptions{T}"/>.
/// </summary>
/// <param name="serviceManager"><see cref="IServiceManager"/>.</param>
/// <returns>Provided <see cref="IServiceManager"/>.</returns>
public static IServiceManager RegisterOptionsManager(this IServiceManager serviceManager)
{
serviceManager.RegisterSingleton<IOptionsManager, DefaultOptionsManager>();
serviceManager.RegisterOptionsResolver();
return serviceManager;
}
/// <summary>
/// Registers a <see cref="IOptionsManager"/>.
/// This call also registers the resolver that resolves <see cref="IUpdateableOptions{T}"/> and <see cref="IOptions{T}"/>.
/// </summary>
/// <typeparam name="T">Implementation of <see cref="IOptionsManager"/>.</typeparam>
/// <param name="serviceManager"><see cref="IServiceManager"/>.</param>
/// <returns>Provided <see cref="IServiceManager"/>.</returns>
public static IServiceManager RegisterOptionsManager<T>(this IServiceManager serviceManager)
where T : IOptionsManager
{
serviceManager.RegisterSingleton<IOptionsManager, T>();
serviceManager.RegisterOptionsResolver();
return serviceManager;
}
/// <summary>
/// Registers resolvers for <see cref="IOptions{TOptions}"/> and <see cref="IUpdateableOptions{T}"/>.
/// Depends on a <see cref="IOptionsManager"/> in order to properly resolve options.
/// </summary>
/// <param name="serviceManager"><see cref="IServiceManager"/>.</param>
/// <returns><see cref="IServiceManager"/>.</returns>
public static IServiceManager RegisterOptionsResolver(this IServiceManager serviceManager)
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.RegisterResolver(new OptionsResolver());
serviceManager.RegisterResolver(new UpdateableOptionsResolver());
serviceManager.RegisterResolver(new LiveOptionsResolver());
serviceManager.RegisterResolver(new LiveUpdateableOptionsResolver());
return serviceManager;
}
/// <summary>
/// Registers a <see cref="ILogsWriter"/> with the default <see cref="CVLoggerProvider"/>.
/// </summary>
/// <typeparam name="TLogsWriter">Implementation of <see cref="ILogsWriter"/>.</typeparam>
/// <param name="serviceManager"><see cref="IServiceProducer"/>.</param>
/// <returns>Provided <see cref="IServiceProducer"/>.</returns>
public static IServiceProducer RegisterLogWriter<TLogsWriter>(this IServiceProducer serviceManager)
where TLogsWriter : ILogsWriter
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.RegisterSingleton<ILogsWriter, TLogsWriter>();
serviceManager.RegisterScoped<ILoggerFactory, LoggerFactory>(sp =>
{
var factory = new LoggerFactory();
factory.AddProvider(new CVLoggerProvider(sp.GetService<ILogsWriter>()));
return factory;
});
return serviceManager;
}
/// <summary>
/// Registers a <see cref="ILogsWriter"/> with the default <see cref="CVLoggerProvider"/>.
/// </summary>
/// <typeparam name="TILogsWriter">Interface of <see cref="ILogsWriter"/>.</typeparam>
/// <typeparam name="TLogsWriter">Implementation of <see cref="ILogsWriter"/>.</typeparam>
/// <param name="serviceManager"><see cref="IServiceProducer"/>.</param>
/// <returns>Provided <see cref="IServiceProducer"/>.</returns>
public static IServiceProducer RegisterLogWriter<TILogsWriter, TLogsWriter>(this IServiceProducer serviceManager)
where TLogsWriter : TILogsWriter
where TILogsWriter : class, ILogsWriter
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.RegisterSingleton<TILogsWriter, TLogsWriter>();
serviceManager.RegisterScoped<ILoggerFactory, LoggerFactory>(sp =>
{
var factory = new LoggerFactory();
factory.AddProvider(new CVLoggerProvider(sp.GetService<TILogsWriter>()));
return factory;
});
return serviceManager;
}
/// <summary>
/// Register a <see cref="ILoggerFactory"/> with a <see cref="CVLoggerProvider"/>.
/// </summary>
/// <param name="serviceManager"></param>
/// <returns></returns>
public static IServiceManager RegisterCVLoggerFactory(this IServiceManager serviceManager)
{
serviceManager.RegisterScoped<ILoggerFactory, LoggerFactory>(sp =>
{
LoggerFactory loggerFactory = new();
loggerFactory.AddProvider(new CVLoggerProvider(sp.GetService<ILogsWriter>()));
return loggerFactory;
});
return serviceManager;
}
public static IServiceManager RegisterLoggerFactory(this IServiceManager serviceManager, Func<Slim.IServiceProvider, ILoggerFactory> loggerFactory)
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.RegisterSingleton<ILoggerFactory, ILoggerFactory>(loggerFactory);
return serviceManager;
}
public static IServiceManager RegisterDebugLoggerFactory(this IServiceManager serviceManager)
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.RegisterLogWriter<ILogsWriter, DebugLogsWriter>();
return serviceManager;
}
public static IServiceManager RegisterHttpFactory(this IServiceManager serviceManager)
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.RegisterResolver(new HttpClientResolver());
return serviceManager;
}
public static IServiceManager RegisterHttpFactory(this IServiceManager serviceManager, Func<Slim.IServiceProvider, Type, HttpMessageHandler> handlerFactory)
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.RegisterResolver(
new HttpClientResolver()
.WithHttpMessageHandlerFactory(handlerFactory));
return serviceManager;
}
}
}
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("SystemExtensions.NetStandard.DependencyInjection.Tests")]
@@ -0,0 +1,88 @@
using Microsoft.Extensions.DependencyInjection;
using System.Extensions;
using System.Net.Http.Headers;
namespace System.Net.Http;
public sealed class HttpClientBuilder<T>
{
private readonly IServiceCollection services;
private Uri? baseAddress;
private Func<IServiceProvider, HttpMessageHandler>? httpMessageHandlerFactory;
private Action<HttpRequestHeaders>? defaultRequestHeadersSetup;
private bool disposeMessageHandler;
private long maxResponseBufferSize = 2147483647L; //2GB default HttpClient value [https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.maxresponsecontentbuffersize?view=net-6.0]
private TimeSpan timeout = TimeSpan.FromSeconds(100); //100 seconds default HttpClient value [https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=net-6.0]
internal HttpClientBuilder(IServiceCollection services)
{
services.ThrowIfNull(nameof(services));
this.services = services;
}
public HttpClientBuilder<T> WithMessageHandler(Func<IServiceProvider, HttpMessageHandler> httpMessageHandlerFactory)
{
httpMessageHandlerFactory.ThrowIfNull(nameof(httpMessageHandlerFactory));
this.httpMessageHandlerFactory = httpMessageHandlerFactory;
return this;
}
public HttpClientBuilder<T> WithDisposeMessageHandler(bool disposeMessageHandler)
{
this.disposeMessageHandler = disposeMessageHandler;
return this;
}
public HttpClientBuilder<T> WithBaseAddress(Uri baseAddress)
{
baseAddress.ThrowIfNull(nameof(baseAddress));
this.baseAddress = baseAddress;
return this;
}
public HttpClientBuilder<T> WithDefaultRequestHeadersSetup(Action<HttpRequestHeaders> setup)
{
setup.ThrowIfNull(nameof(setup));
this.defaultRequestHeadersSetup = setup;
return this;
}
public HttpClientBuilder<T> WithMaxResponseBufferSize(long responseBufferSize)
{
this.maxResponseBufferSize = responseBufferSize;
return this;
}
public HttpClientBuilder<T> WithTimeout(TimeSpan timeout)
{
this.timeout = timeout;
return this;
}
public IServiceCollection Build()
{
this.services.AddScoped<IHttpClient<T>>(sp =>
{
var client = this.httpMessageHandlerFactory is not null ?
new HttpClient<T>(this.httpMessageHandlerFactory(sp), this.disposeMessageHandler) :
new HttpClient<T>(true);
if (this.baseAddress is not null)
{
client.BaseAddress = this.baseAddress;
}
this.defaultRequestHeadersSetup?.Invoke(client.DefaultRequestHeaders);
client.MaxResponseContentBufferSize = this.maxResponseBufferSize;
client.Timeout = this.timeout;
return client;
});
return this.services;
}
}
@@ -1,47 +0,0 @@
using Slim.Resolvers;
using System.Linq;
using System.Net.Http;
namespace System.Http
{
public sealed class HttpClientResolver : IDependencyResolver
{
private static readonly Type clientType = typeof(HttpClient<>);
/// <summary>
/// Factory method. <see cref="Type"/> parameter of the factory is the scope of <see cref="IHttpClient{TScope}"/>.
/// </summary>
public Func<Slim.IServiceProvider, Type, HttpMessageHandler> HttpMessageHandlerFactory { get; set; }
/// <summary>
/// Sets the <see cref="HttpMessageHandlerFactory"/>.
/// </summary>
/// <param name="factory">Factory method. <see cref="Type"/> parameter of the factory is the scope of <see cref="IHttpClient{TScope}"/>.</param>
/// <returns></returns>
public HttpClientResolver WithHttpMessageHandlerFactory(Func<Slim.IServiceProvider, Type, HttpMessageHandler> factory)
{
this.HttpMessageHandlerFactory = factory;
return this;
}
public bool CanResolve(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IHttpClient<>))
{
return true;
}
return false;
}
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
{
var typedClientType = clientType.MakeGenericType(type.GetGenericArguments());
var handler = this.HttpMessageHandlerFactory?.Invoke(serviceProvider, type.GetGenericArguments().First());
var httpClient = handler is null ?
Activator.CreateInstance(typedClientType) :
Activator.CreateInstance(typedClientType, new object[] { handler });
return httpClient;
}
}
}
@@ -1,44 +0,0 @@
using Microsoft.Extensions.Logging;
using System.Extensions;
namespace System.Logging
{
public sealed class CVLogger : ILogger
{
private readonly string category;
private readonly ICVLoggerProvider cvLoggerProvider;
public CVLogger(string category, ICVLoggerProvider cvLoggerProvider)
{
this.category = category;
this.cvLoggerProvider = cvLoggerProvider.ThrowIfNull(nameof(cvLoggerProvider));
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var message = formatter(state, exception);
var log = new Log
{
Exception = exception,
LogLevel = logLevel,
EventId = eventId.Name,
Message = message,
Category = category,
LogTime = DateTime.Now
};
this.cvLoggerProvider.LogEntry(log);
}
}
}
@@ -1,39 +0,0 @@
using Microsoft.CorrelationVector;
using Microsoft.Extensions.Logging;
using System.Extensions;
namespace System.Logging
{
public sealed class CVLoggerProvider : ICVLoggerProvider
{
private readonly ILogsWriter logsManager;
private readonly CorrelationVector correlationVector;
public CVLoggerProvider(ILogsWriter logsWriter)
{
this.logsManager = logsWriter.ThrowIfNull(nameof(logsWriter));
this.correlationVector = new CorrelationVector();
}
public void LogEntry(Log log)
{
if (this.correlationVector is not null)
{
log.CorrelationVector = this.correlationVector.Value.ToString();
this.correlationVector.Increment();
}
this.logsManager.WriteLog(log);
}
public ILogger CreateLogger(string categoryName)
{
return new CVLogger(categoryName, this);
}
public void Dispose()
{
throw new System.NotImplementedException();
}
}
}
@@ -1,12 +0,0 @@
using System.Diagnostics;
namespace System.Logging
{
public sealed class DebugLogsWriter : ILogsWriter
{
public void WriteLog(Log log)
{
Debug.WriteLine($"{log.LogTime} - {log.Category} - {log.EventId} - {log.CorrelationVector} - {log.LogLevel} - {log.Message}{Environment.NewLine}{log.Exception}");
}
}
}
@@ -1,9 +0,0 @@
using Microsoft.Extensions.Logging;
namespace System.Logging
{
public interface ICVLoggerProvider : ILoggerProvider
{
void LogEntry(Log log);
}
}
@@ -1,7 +0,0 @@
namespace System.Logging
{
public interface ILogsWriter
{
void WriteLog(Log log);
}
}
@@ -1,50 +0,0 @@
using Microsoft.Extensions.Logging;
using Slim.Resolvers;
using System.Linq;
namespace System.Logging
{
public sealed class LoggerResolver : IDependencyResolver
{
public bool CanResolve(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILogger<>) ||
type == typeof(ILogger))
{
return true;
}
return false;
}
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILogger<>))
{
return ResolveScopedLogger(serviceProvider, type);
}
else if (type == typeof(ILogger))
{
return ResolveLogger(serviceProvider);
}
else
{
throw new InvalidOperationException($"{nameof(LoggerResolver)} cannot resolve an object of type {type.Name}");
}
}
private static object ResolveScopedLogger(Slim.IServiceProvider serviceProvider, Type type)
{
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
var categoryTypes = type.GetGenericArguments();
var createLoggerMethod = typeof(LoggerFactoryExtensions).GetMethods().Where(m => m.IsGenericMethodDefinition && m.Name == nameof(LoggerFactoryExtensions.CreateLogger)).First();
return createLoggerMethod.MakeGenericMethod(categoryTypes.First()).Invoke(null, new object[] { loggerFactory });
}
private static object ResolveLogger(Slim.IServiceProvider serviceProvider)
{
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
return loggerFactory.CreateLogger(string.Empty);
}
}
}
@@ -1,15 +0,0 @@
using Microsoft.Extensions.Logging;
namespace System.Logging
{
public sealed record Log
{
public Exception Exception { get; set; }
public DateTime LogTime { get; set; }
public string Category { get; set; }
public string EventId { get; set; }
public LogLevel LogLevel { get; set; }
public string Message { get; set; }
public string CorrelationVector { get; set; }
}
}
@@ -6,9 +6,11 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<LangVersion>latest</LangVersion>
<Version>1.1.6</Version>
<Version>1.8</Version>
<Authors>Alexandru Macocian</Authors>
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
<Description>Extensions for the Slim Dependency Injection engine</Description>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
@@ -20,9 +22,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Slim" Version="1.6.0" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.3.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.8.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,58 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Extensions;
using System.Net.WebSockets;
namespace System.Net.Http;
public sealed class ClientWebSocketBuilder<T>
{
private readonly IServiceCollection services;
private ClientWebSocket? innerWebSocket;
private Func<IServiceProvider, ClientWebSocket>? innerWebSocketFactory;
internal ClientWebSocketBuilder(IServiceCollection services)
{
services.ThrowIfNull(nameof(services));
this.services = services;
}
public ClientWebSocketBuilder<T> WithInnerWebSocket(ClientWebSocket innerWebSocket)
{
this.innerWebSocket = innerWebSocket.ThrowIfNull(nameof(innerWebSocket));
return this;
}
public ClientWebSocketBuilder<T> WithInnerWebSocketFactory(Func<IServiceProvider, ClientWebSocket> innerWebSocketFactory)
{
this.innerWebSocketFactory = innerWebSocketFactory.ThrowIfNull(nameof(innerWebSocketFactory));
return this;
}
public IServiceCollection Build()
{
this.services.AddScoped<IClientWebSocket<T>>(sp =>
{
var logger = sp.GetService<ILogger<T>>();
if (logger is null)
{
return new ClientWebSocket<T>();
}
if (this.innerWebSocketFactory is not null)
{
return new ClientWebSocket<T>(this.innerWebSocketFactory(sp), logger);
}
if (this.innerWebSocket is not null)
{
return new ClientWebSocket<T>(this.innerWebSocket, logger);
}
return new ClientWebSocket<T>(logger);
});
return this.services;
}
}
@@ -0,0 +1,142 @@
using Microsoft.CodeAnalysis;
using Sybil;
using System.Text;
using System.Linq;
namespace System.Extensions;
#nullable enable
[Generator(LanguageNames.CSharp)]
public class BlittableStringGenerator : IIncrementalGenerator
{
private const string UsingSystem = "System";
private const string UsingSystemRuntimeInterop = "System.Runtime.InteropServices";
private const string AttributeNamespace = "System.Extensions";
private const string AttributeName = "GenerateBlittableStringAttribute";
private const string AttributeShortName = "GenerateBlittableString";
private const string String = "String";
private const string Public = "public";
private const string Required = "required";
private const string Size = "Size";
private const string Int = "int";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(context =>
{
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
.WithNamespace(
SyntaxBuilder.CreateNamespace(AttributeNamespace)
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
.WithModifier(Public)
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
.WithModifier(Public))
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
.WithArgument(AttributeTargets.Assembly)
.WithArgument("Inherited", false)
.WithArgument("AllowMultiple", true))
.WithProperty(SyntaxBuilder.CreateProperty(Int, Size)
.WithModifier(Public)
.WithModifier(Required)
.WithAccessor(SyntaxBuilder.CreateGetter())
.WithAccessor(SyntaxBuilder.CreateSetter()))
.WithBaseClass(nameof(Attribute))));
var compilationUnitSyntax = compilationUnitBuilder.Build();
var source = compilationUnitSyntax.ToFullString();
context.AddSource($"{AttributeName}.g", source);
});
context.RegisterSourceOutput(context.CompilationProvider, (sourceProductionContext, compilation) =>
{
var assemblyAttributes =
compilation.Assembly.GetAttributes().Where(a => a.AttributeClass != null &&
(a.AttributeClass.Name == AttributeName || a.AttributeClass.Name == AttributeShortName));
foreach (var attr in assemblyAttributes)
{
// Extract the Size parameter from the named arguments.
int? fixedSize = default;
foreach (var arg in attr.NamedArguments)
{
if (arg.Key == Size && arg.Value.Value is int intValue)
{
fixedSize = intValue;
break;
}
}
if (fixedSize is null)
{
// As Size is required, skip if not provided.
continue;
}
Execute(sourceProductionContext, (int)fixedSize);
}
});
}
private static void Execute(SourceProductionContext sourceProductionContext, int size)
{
var structName = $"{String}{size}";
var builder = new StringBuilder();
builder.AppendLine($"using {UsingSystem};");
builder.AppendLine($"using {UsingSystemRuntimeInterop};");
builder.AppendLine();
builder.AppendLine($"namespace {AttributeNamespace};");
builder.AppendLine();
builder.AppendLine("[StructLayout(LayoutKind.Sequential, Pack = 1)]");
builder.AppendLine($"public unsafe partial struct {structName}");
builder.AppendLine("{");
builder.AppendLine($" public const int Size = {size};");
builder.AppendLine();
builder.AppendLine(" public fixed char Value[Size];");
builder.AppendLine();
builder.AppendLine(" public Span<char> AsSpan()");
builder.AppendLine(" {");
builder.AppendLine(" fixed (char* ptr = this.Value)");
builder.AppendLine(" {");
builder.AppendLine(" return MemoryMarshal.CreateSpan(ref *ptr, Size);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(" public readonly ReadOnlySpan<char> AsReadOnlySpan()");
builder.AppendLine(" {");
builder.AppendLine(" fixed (char* ptr = this.Value)");
builder.AppendLine(" {");
builder.AppendLine(" return new ReadOnlySpan<char>(ptr, Size);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(" public void Set(ReadOnlySpan<char> value)");
builder.AppendLine(" {");
builder.AppendLine(" fixed (char* ptr = this.Value)");
builder.AppendLine(" {");
builder.AppendLine(" var span = MemoryMarshal.CreateSpan(ref *ptr, Size);");
builder.AppendLine(" span.Clear();");
builder.AppendLine(" value[..Math.Min(value.Length, Size - 1)].CopyTo(span);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(" public override readonly string ToString()");
builder.AppendLine(" {");
builder.AppendLine(" var span = this.AsReadOnlySpan();");
builder.AppendLine(" var nullIndex = span.IndexOf('\\0');");
builder.AppendLine(" return nullIndex >= 0 ? new string(span[..nullIndex]) : new string(span);");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public static implicit operator string({structName} value) => value.ToString();");
builder.AppendLine();
builder.AppendLine($" public static implicit operator {structName}(string value)");
builder.AppendLine(" {");
builder.AppendLine($" var result = default({structName});");
builder.AppendLine(" result.Set(value);");
builder.AppendLine(" return result;");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public static implicit operator ReadOnlySpan<char>({structName} value) => value.AsReadOnlySpan();");
builder.AppendLine("}");
sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString());
}
}
#nullable disable
@@ -0,0 +1,78 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
namespace System.Extensions;
internal static class Diagnostics
{
public const string DependencyPropertyGenerator_NoAttributeFound = "DGD 0001";
public const string DependencyPropertyGenerator_ClassMustBeTopLevel = "DGD 1001";
public const string DependencyPropertyGenerator_ClassMustBePartial = "DGD 1002";
public const string DependencyPropertyGenerator_ClassMustBeInNamespace = "DGD 1003";
public const string DependencyPropertyGenerator_ClassMustImplementINotifyPropertyChanged = "DGD 1004";
public static Diagnostic MissingAttributeDiagnostic(string attributeName, string attributeNamespace) => Diagnostic.Create(
new DiagnosticDescriptor(
DependencyPropertyGenerator_NoAttributeFound,
$"{attributeNamespace}.{attributeName} not found",
"Could not find attribute with name {0} in namespace {1}.",
"SystemExtensions.NetStandard.Generators",
DiagnosticSeverity.Error,
true,
$"This error occurs when the attribute generated by the {nameof(NotifyPropertyChangedGenerator)} is not found",
null),
Location.None,
attributeName, attributeNamespace);
public static Diagnostic ClassNotTopLevelDiagnostic(string className, string containingSymbol, SyntaxTree syntaxTree, TextSpan textSpan) => Diagnostic.Create(
new DiagnosticDescriptor(
DependencyPropertyGenerator_ClassMustBeTopLevel,
$"{className} must be top level",
"Class {0} must be top level. It is currently declared under {1}.",
"SystemExtensions.NetStandard.Generators",
DiagnosticSeverity.Error,
true,
null,
null),
Location.Create(syntaxTree, textSpan),
className, containingSymbol);
public static Diagnostic ClassNotPartial(string className) => Diagnostic.Create(
new DiagnosticDescriptor(
DependencyPropertyGenerator_ClassMustBePartial,
$"{className} is not partial",
"Class {0} must be marked as partial in order for the generator to work",
"SystemExtensions.NetStandard.Generators",
DiagnosticSeverity.Error,
true,
null,
null),
Location.None,
className);
public static Diagnostic ClassNotInNamespace(string className) => Diagnostic.Create(
new DiagnosticDescriptor(
DependencyPropertyGenerator_ClassMustBeInNamespace,
$"{className} is not defined in a namespace",
"Class {0} must be defined inside a namespace for the generator to work",
"SystemExtensions.NetStandard.Generators",
DiagnosticSeverity.Error,
true,
null,
null),
Location.None,
className);
public static Diagnostic ClassDoesNotImplementINotifyPropertyChanged(string className) => Diagnostic.Create(
new DiagnosticDescriptor(
DependencyPropertyGenerator_ClassMustImplementINotifyPropertyChanged,
$"{className} must implement INotifyPropertyChanged",
"Class {0} must implement INotifyPropertyChanged for the generator to work",
"SystemExtensions.NetStandard.Generators",
DiagnosticSeverity.Error,
true,
null,
null),
Location.None,
className);
}
@@ -0,0 +1,164 @@
using Microsoft.CodeAnalysis;
using Sybil;
using System.Text;
using System.Linq;
namespace System.Extensions;
#nullable enable
[Generator(LanguageNames.CSharp)]
public class FixedArrayGenerator : IIncrementalGenerator
{
private const string UsingSystem = "System";
private const string UsingSystemRuntimeInterop = "System.Runtime.InteropServices";
private const string AttributeNamespace = "System.Extensions";
private const string AttributeName = "GenerateFixedArrayAttribute";
private const string AttributeShortName = "GenerateFixedArray";
private const string Array = "Array";
private const string Public = "public";
private const string Struct = "struct";
private const string Required = "required";
private const string Size = "Size";
private const string Int = "int";
private const string TType = "T";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(context =>
{
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
.WithNamespace(
SyntaxBuilder.CreateNamespace(AttributeNamespace)
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
.WithModifier(Public)
.WithTypeParameter(SyntaxBuilder.CreateTypeParameter(TType))
.WithTypeParameterConstraint(SyntaxBuilder.CreateTypeParameterConstraint(TType)
.WithType(Struct))
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
.WithModifier(Public))
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
.WithArgument(AttributeTargets.Assembly)
.WithArgument("Inherited", false)
.WithArgument("AllowMultiple", true))
.WithProperty(SyntaxBuilder.CreateProperty(Int, Size)
.WithModifier(Public)
.WithModifier(Required)
.WithAccessor(SyntaxBuilder.CreateGetter())
.WithAccessor(SyntaxBuilder.CreateSetter()))
.WithBaseClass(nameof(Attribute))));
var compilationUnitSyntax = compilationUnitBuilder.Build();
var source = compilationUnitSyntax.ToFullString();
context.AddSource($"{AttributeName}.g", source);
});
context.RegisterSourceOutput(context.CompilationProvider, (sourceProductionContext, compilation) =>
{
var assemblyAttributes =
compilation.Assembly.GetAttributes().Where(a => a.AttributeClass != null &&
(a.AttributeClass.Name == AttributeName || a.AttributeClass.Name == AttributeShortName));
foreach (var attr in assemblyAttributes)
{
// Extract the type argument (T) from GenerateFixedArrayAttribute<T>
var namedTypeSymbol = attr.AttributeClass as INamedTypeSymbol;
if (namedTypeSymbol is null || namedTypeSymbol.TypeArguments.Length != 1)
{
continue;
}
var targetType = namedTypeSymbol.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
// Extract the Size parameter from the named arguments.
int? fixedSize = default;
foreach (var arg in attr.NamedArguments)
{
if (arg.Key == Size && arg.Value.Value is int intValue)
{
fixedSize = intValue;
break;
}
}
if (fixedSize is null)
{
// As Size is required, skip if not provided.
continue;
}
Execute(sourceProductionContext, targetType, (int)fixedSize);
}
});
}
private static void Execute(SourceProductionContext sourceProductionContext, string targetType, int size)
{
var capitalizedType = targetType.ToUpperInvariant()[0] + targetType.Substring(1);
var structName = $"{Array}{size}{capitalizedType}";
var builder = new StringBuilder();
builder.AppendLine($"using {UsingSystem};");
builder.AppendLine($"using {UsingSystemRuntimeInterop};");
builder.AppendLine();
builder.AppendLine($"namespace {AttributeNamespace};");
builder.AppendLine();
builder.AppendLine("[StructLayout(LayoutKind.Sequential, Pack = 1)]");
builder.AppendLine($"public unsafe struct {structName}");
builder.AppendLine("{");
builder.AppendLine($" private const int Size = {size};");
builder.AppendLine();
builder.AppendLine($" private fixed {targetType} elements[Size];");
builder.AppendLine();
builder.AppendLine(" public int Length => Size;");
builder.AppendLine();
builder.AppendLine($" public ref {targetType} this[int index]");
builder.AppendLine(" {");
builder.AppendLine(" get");
builder.AppendLine(" {");
builder.AppendLine(" if ((uint)index >= Size) throw new IndexOutOfRangeException();");
builder.AppendLine($" return ref this.elements[index];");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public Span<{targetType}> AsSpan()");
builder.AppendLine(" {");
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
builder.AppendLine(" {");
builder.AppendLine($" return MemoryMarshal.CreateSpan(ref *ptr, Size);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public readonly ReadOnlySpan<{targetType}> AsReadOnlySpan()");
builder.AppendLine(" {");
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
builder.AppendLine(" {");
builder.AppendLine($" return new ReadOnlySpan<{targetType}>(ptr, Size);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public void Set(ReadOnlySpan<{targetType}> value)");
builder.AppendLine(" {");
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
builder.AppendLine(" {");
builder.AppendLine($" var span = MemoryMarshal.CreateSpan(ref *ptr, Size);");
builder.AppendLine(" span.Clear();");
builder.AppendLine(" value[..Math.Min(value.Length, Size)].CopyTo(span);");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public void Clear()");
builder.AppendLine(" {");
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
builder.AppendLine(" {");
builder.AppendLine($" MemoryMarshal.CreateSpan(ref *ptr, Size).Clear();");
builder.AppendLine(" }");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine($" public static {structName} From{capitalizedType}Array({targetType}[] array)");
builder.AppendLine(" {");
builder.AppendLine($" var arr = default({structName});");
builder.AppendLine(" arr.Set(array);");
builder.AppendLine(" return arr;");
builder.AppendLine(" }");
builder.AppendLine("}");
sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString());
}
}
#nullable disable
@@ -0,0 +1,173 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Sybil;
using System.Collections.Immutable;
using System.Linq;
namespace System.Extensions;
#nullable enable
[Generator(LanguageNames.CSharp)]
public class NotifyPropertyChangedGenerator : IIncrementalGenerator
{
private const string AttributeNamespace = "System.Extensions";
private const string AttributeName = "GenerateNotifyPropertyChangedAttribute";
private const string AttributeShortName = "GenerateNotifyPropertyChanged";
private const string PropertyChangedEventHandler = "PropertyChangedEventHandler";
private const string NullablePropertyChangedEventHandler = "PropertyChangedEventHandler?";
private const string PropertyChanged = "PropertyChanged";
private const string Partial = "partial";
private const string Public = "public";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(context =>
{
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
.WithNamespace(
SyntaxBuilder.CreateNamespace(AttributeNamespace)
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
.WithModifier(Public)
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
.WithModifier(Public))
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
.WithArgument(AttributeTargets.Field)
.WithArgument("Inherited", false)
.WithArgument("AllowMultiple", false))
.WithBaseClass(nameof(Attribute))));
var compilationUnitSyntax = compilationUnitBuilder.Build();
var source = compilationUnitSyntax.ToFullString();
context.AddSource($"{AttributeName}.g", source);
});
var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (s, _) => s is FieldDeclarationSyntax,
transform: static (ctx, _) => GetFilteredFieldDeclarationSyntax(ctx)).Where(static c => c is not null);
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext));
}
private static ClassDeclarationSyntax? GetFilteredFieldDeclarationSyntax(GeneratorSyntaxContext context)
{
var fieldDeclarationSyntax = (FieldDeclarationSyntax)context.Node;
if (fieldDeclarationSyntax.AttributeLists
.SelectMany(l => l.Attributes)
.OfType<AttributeSyntax>()
.Any(s => s.Name.ToString() == AttributeName || s.Name.ToString() == AttributeShortName))
{
return fieldDeclarationSyntax.Parent as ClassDeclarationSyntax;
}
return default;
}
private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax?> classes, SourceProductionContext sourceProductionContext)
{
if (classes.IsDefaultOrEmpty)
{
return;
}
var maybeLanguageVersion = (compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions)?.LanguageVersion;
if (!maybeLanguageVersion.HasValue)
{
return;
}
var languageVersion = maybeLanguageVersion.Value;
var distinctClasses = classes.Distinct();
foreach (var c in distinctClasses.OfType<ClassDeclarationSyntax>())
{
var className = c.Identifier.ToFullString();
if (!c.Modifiers.Any(m => m.ToString() == Partial))
{
Diagnostics.ClassNotPartial(className);
continue;
}
var originalNamespaceSyntax = GetParentOfType<BaseNamespaceDeclarationSyntax>(c);
if (originalNamespaceSyntax is null)
{
Diagnostics.ClassNotInNamespace(className);
continue;
}
if (!c.Members
.OfType<EventFieldDeclarationSyntax>()
.Any(eventField =>
(eventField.Declaration.Variables.Any(variable => variable.Identifier.Text == PropertyChanged) &&
eventField.Modifiers.Any(modifier => modifier.Text == Public) &&
eventField.Declaration.Type.ToString() == PropertyChangedEventHandler) ||
eventField.Declaration.Type.ToString() == NullablePropertyChangedEventHandler))
{
Diagnostics.ClassDoesNotImplementINotifyPropertyChanged(className);
continue;
}
var generatedClassBuilder = SyntaxBuilder.CreateClass(className)
.WithModifiers(string.Join(" ", c.Modifiers.Select(m => m.ToFullString())));
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
.WithNamespace(
(languageVersion >= LanguageVersion.CSharp10 ?
SyntaxBuilder.CreateFileScopedNamespace(originalNamespaceSyntax.Name.ToFullString()) :
SyntaxBuilder.CreateNamespace(originalNamespaceSyntax.Name.ToFullString()))
.WithClass(generatedClassBuilder));
foreach (var field in c.DescendantNodes().OfType<FieldDeclarationSyntax>())
{
if (field.AttributeLists
.SelectMany(l => l.Attributes)
.OfType<AttributeSyntax>()
.Any(s => s.Name.ToString() == AttributeName ||
s.Name.ToString() == AttributeShortName) is false)
{
continue;
}
var fieldName = field.Declaration.Variables.First().Identifier.ToFullString();
var propertyName = fieldName.TrimStart('_');
propertyName = $"{char.ToUpper(propertyName[0])}{propertyName.Substring(1)}";
var propertyBuilder = SyntaxBuilder.CreateProperty(field.Declaration.Type.ToFullString(), propertyName)
.WithModifier(Public)
.WithAccessor(SyntaxBuilder.CreateGetter()
.WithBody($"return this.{fieldName};"))
.WithAccessor(SyntaxBuilder.CreateSetter()
.WithBody($"this.{fieldName} = value;\r\nthis.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName})));"));
generatedClassBuilder.WithProperty(propertyBuilder);
}
var generatedSyntax = compilationUnitBuilder.Build();
if (originalNamespaceSyntax.Usings.Count != 0)
{
generatedSyntax = generatedSyntax.AddUsings([.. originalNamespaceSyntax.Usings]);
}
else
{
if (originalNamespaceSyntax.Parent is CompilationUnitSyntax compilationUnit)
{
generatedSyntax = generatedSyntax.AddUsings([.. compilationUnit.Usings]);
}
}
var fileSource = $"#nullable enable\n{generatedSyntax.ToFullString()}\n#nullable disable";
sourceProductionContext.AddSource($"{className}.g", fileSource);
}
}
private static T? GetParentOfType<T>(SyntaxNode syntaxNode)
{
if (syntaxNode.Parent is null)
{
return default;
}
if (syntaxNode.Parent is T parentNode)
{
return parentNode;
}
return GetParentOfType<T>(syntaxNode.Parent);
}
}
#nullable disable
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Alexandru Macocian</Authors>
<Description>Source generators extensions for netstandard2.0.</Description>
<Version>0.1.7</Version>
<LangVersion>latest</LangVersion>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
<PackageReference Include="Sybil" Version="0.8.4" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<!-- Package the generator in the analyzer directory of the nuget package -->
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<None Include="$(OutputPath)\Sybil.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Project>
@@ -7,99 +7,98 @@ using System.Security.Encryption;
using System.Text;
using System.Threading.Tasks;
namespace SystemExtensions.NetStandard.Security.Tests
namespace SystemExtensions.NetStandard.Security.Tests;
[TestClass]
public class AesEncrypterTests
{
[TestClass]
public class AesEncrypterTests
private ISymmetricEncrypter symmetricEncrypter;
private string keyString;
private byte[] keyBytes;
private byte[] ivBytes;
private string ivString;
private string toEncryptString;
private byte[] toEncryptBytes;
[TestInitialize]
public void TestInitialize()
{
private ISymmetricEncrypter symmetricEncrypter;
private string keyString;
private byte[] keyBytes;
private byte[] ivBytes;
private string ivString;
private string toEncryptString;
private byte[] toEncryptBytes;
this.symmetricEncrypter = new AesEncrypter();
this.keyBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2 };
this.keyString = Convert.ToBase64String(this.keyBytes);
this.ivBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 };
this.ivString = Convert.ToBase64String(this.ivBytes);
this.toEncryptBytes = Encoding.UTF8.GetBytes("toEncrypt");
this.toEncryptString = Convert.ToBase64String(this.toEncryptBytes);
}
[TestInitialize]
public void TestInitialize()
{
this.symmetricEncrypter = new AesEncrypter();
this.keyBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2 };
this.keyString = Convert.ToBase64String(this.keyBytes);
this.ivBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 };
this.ivString = Convert.ToBase64String(this.ivBytes);
this.toEncryptBytes = Encoding.UTF8.GetBytes("toEncrypt");
this.toEncryptString = Convert.ToBase64String(this.toEncryptBytes);
}
[TestMethod]
public async Task EncryptDecryptStringWithStringKeyStringIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyString, this.ivString, this.toEncryptString).ConfigureAwait(false);
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyString, this.ivString, encrypted).ConfigureAwait(false);
[TestMethod]
public async Task EncryptDecryptStringWithStringKeyStringIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyString, this.ivString, this.toEncryptString).ConfigureAwait(false);
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyString, this.ivString, encrypted).ConfigureAwait(false);
this.toEncryptString.Should().Be(decrypted);
}
this.toEncryptString.Should().Be(decrypted);
}
[TestMethod]
public async Task EncryptDecryptStringWithByteKeyByteIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyBytes, this.ivBytes, this.toEncryptString).ConfigureAwait(false);
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyBytes, this.ivBytes, encrypted).ConfigureAwait(false);
[TestMethod]
public async Task EncryptDecryptStringWithByteKeyByteIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyBytes, this.ivBytes, this.toEncryptString).ConfigureAwait(false);
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyBytes, this.ivBytes, encrypted).ConfigureAwait(false);
this.toEncryptString.Should().Be(decrypted);
}
this.toEncryptString.Should().Be(decrypted);
}
[TestMethod]
public async Task EncryptDecryptByteWithStringKeyStringIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyString, this.ivString, this.toEncryptBytes).ConfigureAwait(false);
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyString, this.ivString, encrypted).ConfigureAwait(false);
[TestMethod]
public async Task EncryptDecryptByteWithStringKeyStringIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyString, this.ivString, this.toEncryptBytes).ConfigureAwait(false);
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyString, this.ivString, encrypted).ConfigureAwait(false);
this.toEncryptBytes.Should().BeEquivalentTo(decrypted);
}
this.toEncryptBytes.Should().BeEquivalentTo(decrypted);
}
[TestMethod]
public async Task EncryptDecryptByteWithByteKeyByteIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyBytes, this.ivBytes, this.toEncryptBytes).ConfigureAwait(false);
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyBytes, this.ivBytes, encrypted).ConfigureAwait(false);
[TestMethod]
public async Task EncryptDecryptByteWithByteKeyByteIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyBytes, this.ivBytes, this.toEncryptBytes).ConfigureAwait(false);
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyBytes, this.ivBytes, encrypted).ConfigureAwait(false);
this.toEncryptBytes.Should().BeEquivalentTo(decrypted);
}
this.toEncryptBytes.Should().BeEquivalentTo(decrypted);
}
[TestMethod]
public async Task EncryptDecryptStreamWithStringKeyStringIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyString, this.ivString, new MemoryStream(this.toEncryptBytes)).ConfigureAwait(false);
encrypted.Position = 0;
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyString, this.ivString, encrypted).ConfigureAwait(false);
[TestMethod]
public async Task EncryptDecryptStreamWithStringKeyStringIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyString, this.ivString, new MemoryStream(this.toEncryptBytes)).ConfigureAwait(false);
encrypted.Position = 0;
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyString, this.ivString, encrypted).ConfigureAwait(false);
this.toEncryptBytes.Should().BeEquivalentTo(((MemoryStream)decrypted).ToArray());
}
this.toEncryptBytes.Should().BeEquivalentTo(((MemoryStream)decrypted).ToArray());
}
[TestMethod]
public async Task EncryptDecryptStreamWithByteKeyByteIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyBytes, this.ivBytes, new MemoryStream(this.toEncryptBytes)).ConfigureAwait(false);
encrypted.Position = 0;
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyBytes, this.ivBytes, encrypted).ConfigureAwait(false);
[TestMethod]
public async Task EncryptDecryptStreamWithByteKeyByteIv()
{
var encrypted = await this.symmetricEncrypter.Encrypt(this.keyBytes, this.ivBytes, new MemoryStream(this.toEncryptBytes)).ConfigureAwait(false);
encrypted.Position = 0;
var decrypted = await this.symmetricEncrypter.Decrypt(this.keyBytes, this.ivBytes, encrypted).ConfigureAwait(false);
this.toEncryptBytes.Should().BeEquivalentTo(((MemoryStream)decrypted).ToArray());
}
this.toEncryptBytes.Should().BeEquivalentTo(((MemoryStream)decrypted).ToArray());
}
[TestMethod]
public void GetEncryptionStreamReturnsCryptoStream()
{
var encryptionStream = this.symmetricEncrypter.GetEncryptionStream(this.keyBytes, this.ivBytes, new MemoryStream());
encryptionStream.Should().BeAssignableTo<CryptoStream>();
}
[TestMethod]
public void GetEncryptionStreamReturnsCryptoStream()
{
var encryptionStream = this.symmetricEncrypter.GetEncryptionStream(this.keyBytes, this.ivBytes, new MemoryStream());
encryptionStream.Should().BeAssignableTo<CryptoStream>();
}
[TestMethod]
public void GetDecryptionStreamReturnsCryptoStream()
{
var encryptionStream = this.symmetricEncrypter.GetDecryptionStream(this.keyBytes, this.ivBytes, new MemoryStream());
encryptionStream.Should().BeAssignableTo<CryptoStream>();
}
[TestMethod]
public void GetDecryptionStreamReturnsCryptoStream()
{
var encryptionStream = this.symmetricEncrypter.GetDecryptionStream(this.keyBytes, this.ivBytes, new MemoryStream());
encryptionStream.Should().BeAssignableTo<CryptoStream>();
}
}
@@ -1,55 +1,54 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using System.Rng;
using System.Security.Rng;
namespace SystemExtensions.NetStandard.Security.Tests
namespace SystemExtensions.NetStandard.Security.Tests;
[TestClass]
public class CryptoRngProviderTests
{
[TestClass]
public class CryptoRngProviderTests
private CryptoRngProvider cryptoRngProvider;
[TestInitialize]
public void TestInitialize()
{
private CryptoRngProvider cryptoRngProvider;
this.cryptoRngProvider = new CryptoRngProvider();
}
[TestInitialize]
public void TestInitialize()
{
this.cryptoRngProvider = new CryptoRngProvider();
}
[TestMethod]
public void GetBytes_ShouldSetValues()
{
var bytes = new byte[100];
[TestMethod]
public void GetBytes_ShouldSetValues()
{
var bytes = new byte[100];
this.cryptoRngProvider.GetBytes(bytes);
this.cryptoRngProvider.GetBytes(bytes);
bytes.All(b => b == 0).Should().BeFalse();
}
bytes.All(b => b == 0).Should().BeFalse();
}
[TestMethod]
public void GetNonZeroBytes_ShouldSetNonZeroValues()
{
var bytes = new byte[100];
[TestMethod]
public void GetNonZeroBytes_ShouldSetNonZeroValues()
{
var bytes = new byte[100];
this.cryptoRngProvider.GetNonZeroBytes(bytes);
this.cryptoRngProvider.GetNonZeroBytes(bytes);
bytes.All(b => b != 0).Should().BeTrue();
}
bytes.All(b => b != 0).Should().BeTrue();
}
[TestMethod]
public void GetBytes_ShouldReturnBytes()
{
var bytes = this.cryptoRngProvider.GetBytes(10);
[TestMethod]
public void GetBytes_ShouldReturnBytes()
{
var bytes = this.cryptoRngProvider.GetBytes(10);
bytes.Length.Should().Be(10);
}
bytes.Length.Should().Be(10);
}
[TestMethod]
public void GetNonZeroBytes_ShouldReturnBytes()
{
var bytes = this.cryptoRngProvider.GetNonZeroBytes(10);
[TestMethod]
public void GetNonZeroBytes_ShouldReturnBytes()
{
var bytes = this.cryptoRngProvider.GetNonZeroBytes(10);
bytes.Length.Should().Be(10);
}
bytes.Length.Should().Be(10);
}
}
@@ -1,261 +1,260 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Hashing;
using System.Security.Hashing;
using System.Threading.Tasks;
namespace SystemExtensions.NetStandard.Security.Tests
namespace SystemExtensions.NetStandard.Security.Tests;
[TestClass]
public sealed class Rfc2898DeriveBytesPasswordHashingServiceTests
{
[TestClass]
public sealed class Rfc2898DeriveBytesPasswordHashingServiceTests
private const int TooShortHashLength = 31;
private const int DesiredHashLength = 32;
private const int Iterations = 10000;
private const int TooLittleIterations = 1000;
private readonly Rfc2898DeriveBytesPasswordHashingService rfc2898DeriveBytesPasswordHashingService = new();
private byte[] tooShortSaltBytes;
private string tooShortSaltString;
private byte[] incorrectSaltBytes;
private string incorrectSaltString;
private byte[] saltBytes;
private string saltString;
private byte[] passwordBytes;
private string passwordString;
private byte[] incorrectPasswordBytes;
private string incorrectPasswordString;
[TestInitialize]
public void TestInitialize()
{
private const int TooShortHashLength = 31;
private const int DesiredHashLength = 32;
private const int Iterations = 10000;
private const int TooLittleIterations = 1000;
this.tooShortSaltBytes = new byte[16];
this.tooShortSaltString = Convert.ToBase64String(this.tooShortSaltBytes);
this.saltBytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 };
this.incorrectSaltBytes = new byte[32];
this.incorrectSaltString = Convert.ToBase64String(this.incorrectSaltBytes);
this.saltString = Convert.ToBase64String(this.saltBytes);
this.passwordBytes = new byte[] { 5, 1, 2, 34, 35, 123, 4, 23, 1, 235, 32, 234 };
this.passwordString = Convert.ToBase64String(this.passwordBytes);
this.incorrectPasswordBytes = new byte[] { 14, 123, 23, 4, 2, 1, 23, 25 };
this.incorrectPasswordString = Convert.ToBase64String(this.incorrectPasswordBytes);
}
private readonly Rfc2898DeriveBytesPasswordHashingService rfc2898DeriveBytesPasswordHashingService = new();
[TestMethod]
public void PasswordNull_HashBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(null, this.saltBytes, DesiredHashLength, Iterations));
private byte[] tooShortSaltBytes;
private string tooShortSaltString;
private byte[] incorrectSaltBytes;
private string incorrectSaltString;
private byte[] saltBytes;
private string saltString;
private byte[] passwordBytes;
private string passwordString;
private byte[] incorrectPasswordBytes;
private string incorrectPasswordString;
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void PasswordNull_HashString_Throws_ArgumentNullException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(null, this.saltString, DesiredHashLength, Iterations));
[TestInitialize]
public void TestInitialize()
{
this.tooShortSaltBytes = new byte[16];
this.tooShortSaltString = Convert.ToBase64String(this.tooShortSaltBytes);
this.saltBytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 };
this.incorrectSaltBytes = new byte[32];
this.incorrectSaltString = Convert.ToBase64String(this.incorrectSaltBytes);
this.saltString = Convert.ToBase64String(this.saltBytes);
this.passwordBytes = new byte[] { 5, 1, 2, 34, 35, 123, 4, 23, 1, 235, 32, 234 };
this.passwordString = Convert.ToBase64String(this.passwordBytes);
this.incorrectPasswordBytes = new byte[] { 14, 123, 23, 4, 2, 1, 23, 25 };
this.incorrectPasswordString = Convert.ToBase64String(this.incorrectPasswordBytes);
}
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void SaltNull_HashBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, null, DesiredHashLength, Iterations));
[TestMethod]
public void PasswordNull_HashBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(null, this.saltBytes, DesiredHashLength, Iterations));
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void SaltNull_HashString_Throws_ArgumentNullException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, null, DesiredHashLength, Iterations));
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void PasswordNull_HashString_Throws_ArgumentNullException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(null, this.saltString, DesiredHashLength, Iterations));
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void HashLengthTooSmall_HashBytes_Throws_InvalidOperationException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, TooShortHashLength, Iterations));
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void SaltNull_HashBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, null, DesiredHashLength, Iterations));
action.Should().ThrowAsync<InvalidOperationException>();
}
[TestMethod]
public void HashLengthTooSmall_HashString_Throws_InvalidOperationException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, TooShortHashLength, Iterations));
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void SaltNull_HashString_Throws_ArgumentNullException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, null, DesiredHashLength, Iterations));
action.Should().ThrowAsync<InvalidOperationException>();
}
[TestMethod]
public void TooLittleIterations_HashBytes_Throws_InvalidOperationException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, TooLittleIterations));
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void HashLengthTooSmall_HashBytes_Throws_InvalidOperationException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, TooShortHashLength, Iterations));
action.Should().ThrowAsync<InvalidOperationException>();
}
[TestMethod]
public void TooLittleIterations_HashString_Throws_InvalidOperationException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, TooLittleIterations));
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void HashLengthTooSmall_HashString_Throws_InvalidOperationException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, TooShortHashLength, Iterations));
action.Should().ThrowAsync<InvalidOperationException>();
}
[TestMethod]
public void TooShortSalt_HashBytes_Throws_InvalidOperationException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.tooShortSaltBytes, DesiredHashLength, TooLittleIterations));
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void TooLittleIterations_HashBytes_Throws_InvalidOperationException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, TooLittleIterations));
action.Should().ThrowAsync<InvalidOperationException>();
}
[TestMethod]
public void TooShortSalt_HashString_Throws_InvalidOperationException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.tooShortSaltString, DesiredHashLength, TooLittleIterations));
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void TooLittleIterations_HashString_Throws_InvalidOperationException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, TooLittleIterations));
action.Should().ThrowAsync<InvalidOperationException>();
}
[TestMethod]
public void PasswordNull_VerifyBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(null, this.incorrectPasswordBytes, this.saltBytes, Iterations));
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void TooShortSalt_HashBytes_Throws_InvalidOperationException()
{
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.tooShortSaltBytes, DesiredHashLength, TooLittleIterations));
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void PasswordNull_VerifyString_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(null, this.incorrectPasswordString, this.saltString, Iterations));
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void TooShortSalt_HashString_Throws_InvalidOperationException()
{
var action = new Func<Task<string>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.tooShortSaltString, DesiredHashLength, TooLittleIterations));
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void HashNull_VerifyBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordBytes, null, this.saltBytes, Iterations));
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void PasswordNull_VerifyBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(null, this.incorrectPasswordBytes, this.saltBytes, Iterations));
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void HashNull_VerifyString_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordString, null, this.saltString, Iterations));
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void PasswordNull_VerifyString_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(null, this.incorrectPasswordString, this.saltString, Iterations));
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void SaltNull_VerifyBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordBytes, this.incorrectPasswordBytes, null, Iterations));
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void HashNull_VerifyBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordBytes, null, this.saltBytes, Iterations));
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void SaltNull_VerifyString_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordString, this.incorrectPasswordString, null, Iterations));
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void HashNull_VerifyString_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordString, null, this.saltString, Iterations));
action.Should().ThrowAsync<ArgumentNullException>();
}
[TestMethod]
public void TooLittleIterations_VerifyBytes_Throws_InvalidOperationException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordBytes, this.incorrectPasswordBytes, this.saltBytes, TooLittleIterations));
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void SaltNull_VerifyBytes_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordBytes, this.incorrectPasswordBytes, null, Iterations));
action.Should().ThrowAsync<InvalidOperationException>();
}
[TestMethod]
public void TooLittleIterations_VerifyString_Throws_InvalidOperationException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordString, this.incorrectPasswordString, this.saltString, TooLittleIterations));
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void SaltNull_VerifyString_Throws_ArgumentNullException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordString, this.incorrectPasswordString, null, Iterations));
action.Should().ThrowAsync<InvalidOperationException>();
}
[TestMethod]
public async Task HashBytes_ReturnsHashedBytes()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
action.Should().Throw<ArgumentNullException>();
}
[TestMethod]
public void TooLittleIterations_VerifyBytes_Throws_InvalidOperationException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordBytes, this.incorrectPasswordBytes, this.saltBytes, TooLittleIterations));
hashedBytes.Should().NotBeNull();
hashedBytes.Should().HaveCount(DesiredHashLength);
}
[TestMethod]
public async Task HashString_ReturnsHashedString()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void TooLittleIterations_VerifyString_Throws_InvalidOperationException()
{
var action = new Func<Task<bool>>(() => this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(this.passwordString, this.incorrectPasswordString, this.saltString, TooLittleIterations));
hashedString.Should().NotBeNull();
var hashedBytes = Convert.FromBase64String(hashedString);
hashedBytes.Should().HaveCount(DesiredHashLength);
}
[TestMethod]
public async Task VerifyBytes_CorrectPassword_ReturnsTrue()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public async Task HashBytes_ReturnsHashedBytes()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedBytes, this.passwordBytes, this.saltBytes, Iterations);
hashedBytes.Should().NotBeNull();
hashedBytes.Should().HaveCount(DesiredHashLength);
}
[TestMethod]
public async Task HashString_ReturnsHashedString()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
result.Should().BeTrue();
}
[TestMethod]
public async Task VerifyString_CorrectPassword_ReturnsTrue()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
hashedString.Should().NotBeNull();
var hashedBytes = Convert.FromBase64String(hashedString);
hashedBytes.Should().HaveCount(DesiredHashLength);
}
[TestMethod]
public async Task VerifyBytes_CorrectPassword_ReturnsTrue()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedString, this.passwordString, this.saltString, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedBytes, this.passwordBytes, this.saltBytes, Iterations);
result.Should().BeTrue();
}
[TestMethod]
public async Task VerifyBytes_IncorrectPassword_ReturnsFalse()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
result.Should().BeTrue();
}
[TestMethod]
public async Task VerifyString_CorrectPassword_ReturnsTrue()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedBytes, this.incorrectPasswordBytes, this.saltBytes, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedString, this.passwordString, this.saltString, Iterations);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyString_IncorrectPassword_ReturnsFalse()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
result.Should().BeTrue();
}
[TestMethod]
public async Task VerifyBytes_IncorrectPassword_ReturnsFalse()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedString, this.incorrectPasswordString, this.saltString, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedBytes, this.incorrectPasswordBytes, this.saltBytes, Iterations);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyBytes_IncorrectSalt_ReturnsFalse()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyString_IncorrectPassword_ReturnsFalse()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedBytes, this.passwordBytes, this.incorrectSaltBytes, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedString, this.incorrectPasswordString, this.saltString, Iterations);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyString_IncorrectSalt_ReturnsFalse()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyBytes_IncorrectSalt_ReturnsFalse()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedString, this.passwordString, this.incorrectSaltString, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedBytes, this.passwordBytes, this.incorrectSaltBytes, Iterations);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyBytes_IncorrectIterations_ReturnsFalse()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyString_IncorrectSalt_ReturnsFalse()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedBytes, this.passwordBytes, this.saltBytes, Iterations + 1);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedString, this.passwordString, this.incorrectSaltString, Iterations);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyString_IncorrectIterations_ReturnsFalse()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyBytes_IncorrectIterations_ReturnsFalse()
{
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedString, this.passwordString, this.saltString, Iterations + 1);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedBytes, this.passwordBytes, this.saltBytes, Iterations + 1);
result.Should().BeFalse();
}
[TestMethod]
public async Task VerifyString_IncorrectIterations_ReturnsFalse()
{
var hashedString = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordString, this.saltString, DesiredHashLength, Iterations);
var result = await this.rfc2898DeriveBytesPasswordHashingService.VerifyPassword(hashedString, this.passwordString, this.saltString, Iterations + 1);
result.Should().BeFalse();
}
result.Should().BeFalse();
}
}
@@ -1,123 +1,121 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Encryption;
using System.Security.Encryption;
namespace SystemExtensions.NetStandard.Security.Tests
namespace SystemExtensions.NetStandard.Security.Tests;
[TestClass]
public class SecureStringTests
{
[TestClass]
public class SecureStringTests
[TestMethod]
public void SecureString_NewSecureString_ReturnsValue()
{
[TestMethod]
public void SecureString_NewSecureString_ReturnsValue()
{
var str = new SecureString("hello");
var str = new SecureString("hello");
str.Value.Should().Be("hello");
}
str.Value.Should().Be("hello");
}
[TestMethod]
public void SecureStringEmpty_AreEqual()
{
var str = SecureString.Empty;
str.Should().Be(SecureString.Empty);
}
[TestMethod]
public void SecureStringEmpty_AreEqual()
{
var str = SecureString.Empty;
str.Should().Be(SecureString.Empty);
}
[TestMethod]
public void SecureStringEmpty_Equals_StringEmpty()
{
var ss = SecureString.Empty;
var s = string.Empty;
[TestMethod]
public void SecureStringEmpty_Equals_StringEmpty()
{
var ss = SecureString.Empty;
var s = string.Empty;
ss.Should().Be(s);
}
ss.Should().Be(s);
}
[TestMethod]
public void SecureString_Equals_OtherSecureString()
{
var ss1 = new SecureString("Hello");
var ss2 = new SecureString("Hello");
[TestMethod]
public void SecureString_Equals_OtherSecureString()
{
var ss1 = new SecureString("Hello");
var ss2 = new SecureString("Hello");
ss1.Equals(ss2).Should().BeTrue();
}
ss1.Equals(ss2).Should().BeTrue();
}
[TestMethod]
[DataRow("hello", "hello", true)]
[DataRow("hello", "henlo", false)]
public void SecureString_EqualOperator_OtherSecureString(string str1, string str2, bool isEqual)
{
var ss1 = new SecureString(str1);
var ss2 = new SecureString(str2);
[TestMethod]
[DataRow("hello", "hello", true)]
[DataRow("hello", "henlo", false)]
public void SecureString_EqualOperator_OtherSecureString(string str1, string str2, bool isEqual)
{
var ss1 = new SecureString(str1);
var ss2 = new SecureString(str2);
(ss1 == ss2).Should().Be(isEqual);
}
(ss1 == ss2).Should().Be(isEqual);
}
[TestMethod]
[DataRow("hello", "hello", false)]
[DataRow("hello", "henlo", true)]
public void SecureString_DifferentOperator_OtherSecureString(string str1, string str2, bool isDifferent)
{
var ss1 = new SecureString(str1);
var ss2 = new SecureString(str2);
[TestMethod]
[DataRow("hello", "hello", false)]
[DataRow("hello", "henlo", true)]
public void SecureString_DifferentOperator_OtherSecureString(string str1, string str2, bool isDifferent)
{
var ss1 = new SecureString(str1);
var ss2 = new SecureString(str2);
(ss1 != ss2).Should().Be(isDifferent);
}
(ss1 != ss2).Should().Be(isDifferent);
}
[TestMethod]
[DataRow("hello", "hello", true)]
[DataRow("hello", "henlo", false)]
public void SecureString_EqualOperator_String(string str1, string str2, bool isEqual)
{
var ss1 = new SecureString(str1);
[TestMethod]
[DataRow("hello", "hello", true)]
[DataRow("hello", "henlo", false)]
public void SecureString_EqualOperator_String(string str1, string str2, bool isEqual)
{
var ss1 = new SecureString(str1);
(ss1 == str2).Should().Be(isEqual);
}
(ss1 == str2).Should().Be(isEqual);
}
[TestMethod]
[DataRow("hello", "hello", false)]
[DataRow("hello", "henlo", true)]
public void SecureString_DifferentOperator_String(string str1, string str2, bool isDifferent)
{
var ss1 = new SecureString(str1);
[TestMethod]
[DataRow("hello", "hello", false)]
[DataRow("hello", "henlo", true)]
public void SecureString_DifferentOperator_String(string str1, string str2, bool isDifferent)
{
var ss1 = new SecureString(str1);
(ss1 != str2).Should().Be(isDifferent);
}
(ss1 != str2).Should().Be(isDifferent);
}
[TestMethod]
public void SecureString_PlusOperator_SecureString()
{
var ss1 = new SecureString("Hello ");
var ss2 = new SecureString("World");
[TestMethod]
public void SecureString_PlusOperator_SecureString()
{
var ss1 = new SecureString("Hello ");
var ss2 = new SecureString("World");
(ss1 + ss2).Should().Be("Hello World");
}
(ss1 + ss2).Should().Be("Hello World");
}
[TestMethod]
public void SecureString_PlusOperator_String()
{
var ss1 = new SecureString("Hello ");
var ss2 = "World";
[TestMethod]
public void SecureString_PlusOperator_String()
{
var ss1 = new SecureString("Hello ");
var ss2 = "World";
(ss1 + ss2).Should().Be("Hello World");
}
(ss1 + ss2).Should().Be("Hello World");
}
[TestMethod]
public void SecureString_PlusOperator_Char()
{
var ss1 = new SecureString("Hello ");
var c = 'W';
[TestMethod]
public void SecureString_PlusOperator_Char()
{
var ss1 = new SecureString("Hello ");
var c = 'W';
(ss1 + c).Should().Be("Hello W");
}
(ss1 + c).Should().Be("Hello W");
}
[TestMethod]
public void SecureString_WithOptionalEntropy_Matches()
{
SecureString.AddOptionalEntropy(new byte[] { 10, 20, 25, 34, 56, 12, 10, 81, 200, 155, 123, 144, 123, 192, 122, 1 });
var ss1 = new SecureString("Hello");
var ss2 = new SecureString("Hello");
[TestMethod]
public void SecureString_WithOptionalEntropy_Matches()
{
SecureString.AddOptionalEntropy(new byte[] { 10, 20, 25, 34, 56, 12, 10, 81, 200, 155, 123, 144, 123, 192, 122, 1 });
var ss1 = new SecureString("Hello");
var ss2 = new SecureString("Hello");
ss1.Should().Be(ss2);
}
ss1.Should().Be(ss2);
}
}
@@ -6,43 +6,42 @@ using System.Security.Hashing;
using System.Text;
using System.Threading.Tasks;
namespace SystemExtensions.NetStandard.Security.Tests
namespace SystemExtensions.NetStandard.Security.Tests;
[TestClass]
public sealed class Sha256HashingServiceTests
{
[TestClass]
public sealed class Sha256HashingServiceTests
private readonly IHashingService hashingService = new Sha256HashingService();
private string toHashtString;
private byte[] toHashtBytes;
private Stream toHashStream;
[TestInitialize]
public void TestInitialize()
{
private readonly IHashingService hashingService = new Sha256HashingService();
private string toHashtString;
private byte[] toHashtBytes;
private Stream toHashStream;
this.toHashtBytes = Encoding.UTF8.GetBytes("toEncrypt");
this.toHashtString = Convert.ToBase64String(this.toHashtBytes);
this.toHashStream = new MemoryStream(this.toHashtBytes);
}
[TestInitialize]
public void TestInitialize()
{
this.toHashtBytes = Encoding.UTF8.GetBytes("toEncrypt");
this.toHashtString = Convert.ToBase64String(this.toHashtBytes);
this.toHashStream = new MemoryStream(this.toHashtBytes);
}
[TestMethod]
public async Task HashString()
{
var hash = await this.hashingService.Hash(this.toHashtString).ConfigureAwait(false);
hash.Should().BeOfType<string>();
}
[TestMethod]
public async Task HashString()
{
var hash = await this.hashingService.Hash(this.toHashtString).ConfigureAwait(false);
hash.Should().BeOfType<string>();
}
[TestMethod]
public async Task HashBytes()
{
var hash = await this.hashingService.Hash(this.toHashtBytes).ConfigureAwait(false);
hash.Should().BeOfType<byte[]>();
}
[TestMethod]
public async Task HashBytes()
{
var hash = await this.hashingService.Hash(this.toHashtBytes).ConfigureAwait(false);
hash.Should().BeOfType<byte[]>();
}
[TestMethod]
public async Task HashStream()
{
var hash = await this.hashingService.Hash(this.toHashStream).ConfigureAwait(false);
hash.Should().BeAssignableTo<Stream>();
}
[TestMethod]
public async Task HashStream()
{
var hash = await this.hashingService.Hash(this.toHashStream).ConfigureAwait(false);
hash.Should().BeAssignableTo<Stream>();
}
}
@@ -1,17 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
<PackageReference Include="coverlet.collector" Version="3.0.2" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.5.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.5.2" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -4,211 +4,223 @@ using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Security.Utilities;
namespace System.Security.Encryption
namespace System.Security.Encryption;
public sealed class AesEncrypter : ISymmetricEncrypter, IDisposable
{
public sealed class AesEncrypter : ISymmetricEncrypter
private readonly Aes aes;
public AesEncrypter()
{
public async Task<string> Decrypt(string key, string iv, string value)
this.aes = Aes.Create();
}
public AesEncrypter(Aes aes)
{
this.aes = aes;
}
public async Task<string> Decrypt(string key, string iv, string value)
{
using var valueStream = BytesToStream(StringToBytes(value));
using var decryptedStream = await this.DecryptInternal(StringToBytes(key), StringToBytes(iv), valueStream).ConfigureAwait(false);
return StreamToString(decryptedStream);
}
public async Task<string> Decrypt(byte[] key, byte[] iv, string value)
{
using var valueStream = BytesToStream(StringToBytes(value));
using var decryptedStream = await this.DecryptInternal(key, iv, valueStream).ConfigureAwait(false);
return StreamToString(decryptedStream);
}
public async Task<byte[]> Decrypt(string key, string iv, byte[] value)
{
using var valueStream = BytesToStream(value);
using var decryptedStream = await this.DecryptInternal(StringToBytes(key), StringToBytes(iv), valueStream).ConfigureAwait(false);
return StreamToBytes(decryptedStream);
}
public async Task<byte[]> Decrypt(byte[] key, byte[] iv, byte[] value)
{
using var valueStream = BytesToStream(value);
using var decryptedStream = await this.DecryptInternal(key, iv, valueStream).ConfigureAwait(false);
return StreamToBytes(decryptedStream);
}
public async Task<Stream> Decrypt(string key, string iv, Stream value)
{
return await this.DecryptInternal(StringToBytes(key), StringToBytes(iv), value).ConfigureAwait(false);
}
public async Task<Stream> Decrypt(byte[] key, byte[] iv, Stream value)
{
return await this.DecryptInternal(key, iv, value).ConfigureAwait(false);
}
public async Task<string> Encrypt(string key, string iv, string value)
{
using var valueStream = BytesToStream(StringToBytes(value));
using var encryptedStream = await this.EncryptInternal(StringToBytes(key), StringToBytes(iv), valueStream).ConfigureAwait(false);
return StreamToString(encryptedStream);
}
public async Task<string> Encrypt(byte[] key, byte[] iv, string value)
{
using var valueStream = BytesToStream(StringToBytes(value));
using var encryptedStream = await this.EncryptInternal(key, iv, valueStream).ConfigureAwait(false);
return StreamToString(encryptedStream);
}
public async Task<byte[]> Encrypt(string key, string iv, byte[] value)
{
using var valueStream = BytesToStream(value);
using var encryptedStream = await this.EncryptInternal(StringToBytes(key), StringToBytes(iv), valueStream).ConfigureAwait(false);
return StreamToBytes(encryptedStream);
}
public async Task<byte[]> Encrypt(byte[] key, byte[] iv, byte[] value)
{
using var valueStream = BytesToStream(value);
using var encryptedStream = await this.EncryptInternal(key, iv, valueStream).ConfigureAwait(false);
return StreamToBytes(encryptedStream);
}
public async Task<Stream> Encrypt(string key, string iv, Stream value)
{
return value is null
? throw new ArgumentNullException(nameof(value))
: await this.EncryptInternal(StringToBytes(key), StringToBytes(iv), value).ConfigureAwait(false);
}
public async Task<Stream> Encrypt(byte[] key, byte[] iv, Stream value)
{
return value is null
? throw new ArgumentNullException(nameof(value))
: await this.EncryptInternal(key, iv, value).ConfigureAwait(false);
}
public Stream GetEncryptionStream(string key, string iv, Stream outStream)
{
return this.GetEncryptionStreamInternal(StringToBytes(key), StringToBytes(iv), outStream);
}
public Stream GetEncryptionStream(byte[] key, byte[] iv, Stream outStream)
{
return this.GetEncryptionStreamInternal(key, iv, outStream);
}
public Stream GetDecryptionStream(string key, string iv, Stream inStream)
{
return this.GetDecryptionStreamInternal(StringToBytes(key), StringToBytes(iv), inStream);
}
public Stream GetDecryptionStream(byte[] key, byte[] iv, Stream inStream)
{
return this.GetDecryptionStreamInternal(key, iv, inStream);
}
private async Task<Stream> EncryptInternal(byte[] key, byte[] iv, Stream toEncryptStream)
{
if (key.Length < 32)
{
using var valueStream = BytesToStream(StringToBytes(value));
using var decryptedStream = await DecryptInternal(StringToBytes(key), StringToBytes(iv), valueStream).ConfigureAwait(false);
return StreamToString(decryptedStream);
throw new ArgumentException($"{nameof(key)} must be at least 32 bytes");
}
public async Task<string> Decrypt(byte[] key, byte[] iv, string value)
if (iv.Length < 16)
{
using var valueStream = BytesToStream(StringToBytes(value));
using var decryptedStream = await DecryptInternal(key, iv, valueStream).ConfigureAwait(false);
return StreamToString(decryptedStream);
throw new ArgumentException($"{nameof(iv)} must be at least 16 bytes");
}
public async Task<byte[]> Decrypt(string key, string iv, byte[] value)
key = key.Take(32).ToArray();
iv = iv.Take(16).ToArray();
var encryptedMemoryStream = new MemoryStream();
using var cryptoStream = this.GetEncryptionStreamInternal(key, iv, encryptedMemoryStream);
await toEncryptStream.CopyToAsync(cryptoStream).ConfigureAwait(false);
if (!cryptoStream.HasFlushedFinalBlock)
{
using var valueStream = BytesToStream(value);
using var decryptedStream = await DecryptInternal(StringToBytes(key), StringToBytes(iv), valueStream).ConfigureAwait(false);
return StreamToBytes(decryptedStream);
cryptoStream.FlushFinalBlock();
}
public async Task<byte[]> Decrypt(byte[] key, byte[] iv, byte[] value)
encryptedMemoryStream.Position = 0;
return encryptedMemoryStream;
}
private async Task<Stream> DecryptInternal(byte[] key, byte[] iv, Stream toDecryptStream)
{
if (key.Length < 32)
{
using var valueStream = BytesToStream(value);
using var decryptedStream = await DecryptInternal(key, iv, valueStream).ConfigureAwait(false);
return StreamToBytes(decryptedStream);
throw new ArgumentException($"{nameof(key)} must be at least 32 bytes");
}
public async Task<Stream> Decrypt(string key, string iv, Stream value)
if (iv.Length < 16)
{
return await DecryptInternal(StringToBytes(key), StringToBytes(iv), value).ConfigureAwait(false);
throw new ArgumentException($"{nameof(iv)} must be at least 16 bytes");
}
public async Task<Stream> Decrypt(byte[] key, byte[] iv, Stream value)
key = key.Take(32).ToArray();
iv = iv.Take(16).ToArray();
var decryptedMemoryStream = new MemoryStream();
using var cryptoStream = this.GetDecryptionStreamInternal(key, iv, toDecryptStream);
await cryptoStream.CopyToAsync(decryptedMemoryStream).ConfigureAwait(false);
if (!cryptoStream.HasFlushedFinalBlock)
{
return await DecryptInternal(key, iv, value).ConfigureAwait(false);
cryptoStream.FlushFinalBlock();
}
public async Task<string> Encrypt(string key, string iv, string value)
decryptedMemoryStream.Position = 0;
return decryptedMemoryStream;
}
private CryptoStream GetEncryptionStreamInternal(byte[] key, byte[] iv, Stream encryptedStream)
{
var crypto = this.aes.CreateEncryptor(key, iv);
var cryptoStream = new NotClosingCryptoStream(encryptedStream, crypto, CryptoStreamMode.Write);
return cryptoStream;
}
private CryptoStream GetDecryptionStreamInternal(byte[] key, byte[] iv, Stream encryptedStream)
{
var crypto = this.aes.CreateDecryptor(key, iv);
var cryptoStream = new NotClosingCryptoStream(encryptedStream, crypto, CryptoStreamMode.Read);
return cryptoStream;
}
private static byte[] StreamToBytes(Stream value)
{
value.Position = 0;
var buffer = new byte[1024];
using var ms = new MemoryStream();
var read = -1;
do
{
using var valueStream = BytesToStream(StringToBytes(value));
using var encryptedStream = await EncryptInternal(StringToBytes(key), StringToBytes(iv), valueStream).ConfigureAwait(false);
return StreamToString(encryptedStream);
}
read = value.Read(buffer, 0, buffer.Length);
ms.Write(buffer, 0, read);
} while (read > 0);
return ms.ToArray();
}
public async Task<string> Encrypt(byte[] key, byte[] iv, string value)
{
using var valueStream = BytesToStream(StringToBytes(value));
using var encryptedStream = await EncryptInternal(key, iv, valueStream).ConfigureAwait(false);
return StreamToString(encryptedStream);
}
private static string StreamToString(Stream value)
{
return Convert.ToBase64String(StreamToBytes(value));
}
public async Task<byte[]> Encrypt(string key, string iv, byte[] value)
{
using var valueStream = BytesToStream(value);
using var encryptedStream = await EncryptInternal(StringToBytes(key), StringToBytes(iv), valueStream).ConfigureAwait(false);
return StreamToBytes(encryptedStream);
}
private static byte[] StringToBytes(string value)
{
return Convert.FromBase64String(value);
}
public async Task<byte[]> Encrypt(byte[] key, byte[] iv, byte[] value)
{
using var valueStream = BytesToStream(value);
using var encryptedStream = await EncryptInternal(key, iv, valueStream).ConfigureAwait(false);
return StreamToBytes(encryptedStream);
}
private static Stream BytesToStream(byte[] value)
{
return new MemoryStream(value);
}
public async Task<Stream> Encrypt(string key, string iv, Stream value)
{
return value is null
? throw new ArgumentNullException(nameof(value))
: await EncryptInternal(StringToBytes(key), StringToBytes(iv), value).ConfigureAwait(false);
}
public async Task<Stream> Encrypt(byte[] key, byte[] iv, Stream value)
{
return value is null
? throw new ArgumentNullException(nameof(value))
: await EncryptInternal(key, iv, value).ConfigureAwait(false);
}
public Stream GetEncryptionStream(string key, string iv, Stream outStream)
{
return GetEncryptionStreamInternal(StringToBytes(key), StringToBytes(iv), outStream);
}
public Stream GetEncryptionStream(byte[] key, byte[] iv, Stream outStream)
{
return GetEncryptionStreamInternal(key, iv, outStream);
}
public Stream GetDecryptionStream(string key, string iv, Stream inStream)
{
return GetDecryptionStreamInternal(StringToBytes(key), StringToBytes(iv), inStream);
}
public Stream GetDecryptionStream(byte[] key, byte[] iv, Stream inStream)
{
return GetDecryptionStreamInternal(key, iv, inStream);
}
private static byte[] StreamToBytes(Stream value)
{
value.Position = 0;
var buffer = new byte[1024];
using var ms = new MemoryStream();
var read = -1;
do
{
read = value.Read(buffer, 0, buffer.Length);
ms.Write(buffer, 0, read);
} while (read > 0);
return ms.ToArray();
}
private static string StreamToString(Stream value)
{
return Convert.ToBase64String(StreamToBytes(value));
}
private static byte[] StringToBytes(string value)
{
return Convert.FromBase64String(value);
}
private static Stream BytesToStream(byte[] value)
{
return new MemoryStream(value);
}
private static async Task<Stream> EncryptInternal(byte[] key, byte[] iv, Stream toEncryptStream)
{
if (key.Length < 32)
{
throw new ArgumentException($"{nameof(key)} must be at least 32 bytes");
}
if (iv.Length < 16)
{
throw new ArgumentException($"{nameof(iv)} must be at least 16 bytes");
}
key = key.Take(32).ToArray();
iv = iv.Take(16).ToArray();
var encryptedMemoryStream = new MemoryStream();
using var cryptoStream = GetEncryptionStreamInternal(key, iv, encryptedMemoryStream);
await toEncryptStream.CopyToAsync(cryptoStream).ConfigureAwait(false);
if (!cryptoStream.HasFlushedFinalBlock)
{
cryptoStream.FlushFinalBlock();
}
encryptedMemoryStream.Position = 0;
return encryptedMemoryStream;
}
private static async Task<Stream> DecryptInternal(byte[] key, byte[] iv, Stream toDecryptStream)
{
if (key.Length < 32)
{
throw new ArgumentException($"{nameof(key)} must be at least 32 bytes");
}
if (iv.Length < 16)
{
throw new ArgumentException($"{nameof(iv)} must be at least 16 bytes");
}
key = key.Take(32).ToArray();
iv = iv.Take(16).ToArray();
var decryptedMemoryStream = new MemoryStream();
using var cryptoStream = GetDecryptionStreamInternal(key, iv, toDecryptStream);
await cryptoStream.CopyToAsync(decryptedMemoryStream).ConfigureAwait(false);
if (!cryptoStream.HasFlushedFinalBlock)
{
cryptoStream.FlushFinalBlock();
}
decryptedMemoryStream.Position = 0;
return decryptedMemoryStream;
}
private static CryptoStream GetEncryptionStreamInternal(byte[] key, byte[] iv, Stream encryptedStream)
{
using var aes = Aes.Create();
aes.Padding = PaddingMode.PKCS7;
var crypto = aes.CreateEncryptor(key, iv);
var cryptoStream = new NotClosingCryptoStream(encryptedStream, crypto, CryptoStreamMode.Write);
return cryptoStream;
}
private static CryptoStream GetDecryptionStreamInternal(byte[] key, byte[] iv, Stream encryptedStream)
{
using var aes = Aes.Create();
aes.Padding = PaddingMode.PKCS7;
var crypto = aes.CreateDecryptor(key, iv);
var cryptoStream = new NotClosingCryptoStream(encryptedStream, crypto, CryptoStreamMode.Read);
return cryptoStream;
}
public void Dispose()
{
this.aes.Dispose();
}
}
@@ -1,25 +1,24 @@
using System.IO;
using System.Threading.Tasks;
namespace System.Security.Encryption
namespace System.Security.Encryption;
public interface ISymmetricEncrypter
{
public interface ISymmetricEncrypter
{
Stream GetEncryptionStream(string key, string iv, Stream outStream);
Stream GetEncryptionStream(byte[] key, byte[] iv, Stream outStream);
Stream GetDecryptionStream(string key, string iv, Stream inStream);
Stream GetDecryptionStream(byte[] key, byte[] iv, Stream inStream);
Task<string> Encrypt(string key, string iv, string value);
Task<string> Encrypt(byte[] key, byte[] iv, string value);
Task<byte[]> Encrypt(string key, string iv, byte[] value);
Task<byte[]> Encrypt(byte[] key, byte[] iv, byte[] value);
Task<Stream> Encrypt(string key, string iv, Stream value);
Task<Stream> Encrypt(byte[] key, byte[] iv, Stream value);
Task<string> Decrypt(string key, string iv, string value);
Task<string> Decrypt(byte[] key, byte[] iv, string value);
Task<byte[]> Decrypt(string key, string iv, byte[] value);
Task<byte[]> Decrypt(byte[] key, byte[] iv, byte[] value);
Task<Stream> Decrypt(string key, string iv, Stream value);
Task<Stream> Decrypt(byte[] key, byte[] iv, Stream value);
}
Stream GetEncryptionStream(string key, string iv, Stream outStream);
Stream GetEncryptionStream(byte[] key, byte[] iv, Stream outStream);
Stream GetDecryptionStream(string key, string iv, Stream inStream);
Stream GetDecryptionStream(byte[] key, byte[] iv, Stream inStream);
Task<string> Encrypt(string key, string iv, string value);
Task<string> Encrypt(byte[] key, byte[] iv, string value);
Task<byte[]> Encrypt(string key, string iv, byte[] value);
Task<byte[]> Encrypt(byte[] key, byte[] iv, byte[] value);
Task<Stream> Encrypt(string key, string iv, Stream value);
Task<Stream> Encrypt(byte[] key, byte[] iv, Stream value);
Task<string> Decrypt(string key, string iv, string value);
Task<string> Decrypt(byte[] key, byte[] iv, string value);
Task<byte[]> Decrypt(string key, string iv, byte[] value);
Task<byte[]> Decrypt(byte[] key, byte[] iv, byte[] value);
Task<Stream> Decrypt(string key, string iv, Stream value);
Task<Stream> Decrypt(byte[] key, byte[] iv, Stream value);
}
@@ -1,102 +1,108 @@
using System.Security.Cryptography;
using System.Text;
namespace System.Encryption
namespace System.Security.Encryption;
public sealed class SecureString
{
public sealed class SecureString
private static byte[] optionalEntropy;
private byte[] encryptedValue;
public string Value {
get => this.encryptedValue is not null ? Encoding.UTF8.GetString(ProtectedData.Unprotect(this.encryptedValue, optionalEntropy, DataProtectionScope.CurrentUser)) : string.Empty;
private set => this.encryptedValue = value is not null
? ProtectedData.Protect(Encoding.UTF8.GetBytes(value), optionalEntropy, DataProtectionScope.CurrentUser)
: null;
}
public SecureString(string value)
{
private static byte[] optionalEntropy;
private byte[] encryptedValue;
this.Value = value;
}
public string Value {
get => this.encryptedValue is not null ? Encoding.UTF8.GetString(ProtectedData.Unprotect(this.encryptedValue, optionalEntropy, DataProtectionScope.CurrentUser)) : null;
private set => this.encryptedValue = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), optionalEntropy, DataProtectionScope.CurrentUser);
}
public SecureString(string value)
public override bool Equals(object obj)
{
if (obj is string)
{
this.Value = value;
return this == (obj as string);
}
public override bool Equals(object obj)
else if (obj is SecureString)
{
if (obj is string)
{
return this == (obj as string);
}
else if (obj is SecureString)
{
return this == (obj as SecureString);
}
else
{
return base.Equals(obj);
}
return this == (obj as SecureString);
}
public override int GetHashCode()
else
{
return this.Value.GetHashCode();
}
public override string ToString()
{
return this.Value;
}
public static readonly SecureString Empty = new(string.Empty);
public static implicit operator string(SecureString ss) => ss is null ? string.Empty : ss.Value;
public static implicit operator SecureString(string s) => new(s);
public static SecureString operator +(SecureString ss1, SecureString ss2)
{
if (ss1 is null)
{
throw new ArgumentNullException(nameof(ss1));
}
if (ss2 is null)
{
throw new ArgumentNullException(nameof(ss2));
}
return new SecureString(ss1.Value + ss2.Value);
}
public static SecureString operator +(SecureString ss1, string s2)
{
if (ss1 is null)
{
throw new ArgumentNullException(nameof(ss1));
}
return new SecureString(ss1.Value + s2);
}
public static SecureString operator +(SecureString ss1, char c)
{
if (ss1 is null)
{
throw new ArgumentNullException(nameof(ss1));
}
return new SecureString(ss1.Value + c);
}
public static bool operator ==(SecureString ss1, SecureString ss2)
{
return ss1?.Value == ss2?.Value;
}
public static bool operator !=(SecureString ss1, SecureString ss2)
{
return !(ss1 == ss2);
}
public static bool operator ==(SecureString ss1, string s2)
{
return ss1?.Value == s2;
}
public static bool operator !=(SecureString ss1, string s2)
{
return !(ss1?.Value == s2);
}
public static void AddOptionalEntropy(byte[] entropy)
{
optionalEntropy = entropy;
return base.Equals(obj);
}
}
public override int GetHashCode()
{
return this.Value.GetHashCode();
}
public override string ToString()
{
return this.Value;
}
public static readonly SecureString Empty = new(null);
public static implicit operator string(SecureString ss) => ss is null ? string.Empty : ss.Value;
public static implicit operator SecureString(string s) => new(s);
public static SecureString operator +(SecureString ss1, SecureString ss2)
{
if (ss1 is null)
{
throw new ArgumentNullException(nameof(ss1));
}
if (ss2 is null)
{
throw new ArgumentNullException(nameof(ss2));
}
return new SecureString(ss1.Value + ss2.Value);
}
public static SecureString operator +(SecureString ss1, string s2)
{
if (ss1 is null)
{
throw new ArgumentNullException(nameof(ss1));
}
return new SecureString(ss1.Value + s2);
}
public static SecureString operator +(SecureString ss1, char c)
{
if (ss1 is null)
{
throw new ArgumentNullException(nameof(ss1));
}
return new SecureString(ss1.Value + c);
}
public static bool operator ==(SecureString ss1, SecureString ss2)
{
if (ss1 is null && ss2 is null)
{
return true;
}
return ss1?.Value == ss2?.Value;
}
public static bool operator !=(SecureString ss1, SecureString ss2)
{
return !(ss1 == ss2);
}
public static bool operator ==(SecureString ss1, string s2)
{
return ss1?.Value == s2;
}
public static bool operator !=(SecureString ss1, string s2)
{
return !(ss1?.Value == s2);
}
public static void AddOptionalEntropy(byte[] entropy)
{
optionalEntropy = entropy;
}
}
@@ -0,0 +1,64 @@
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace System.Security.Hashing;
public abstract class BaseHMACService<T> : IHMACService
where T : HMAC
{
protected abstract T GetHashAlgorithm(byte[] key);
public async Task<string> Hash(string key, string raw)
{
using var rawStream = BytesToStream(StringToBytes(raw));
var keyBytes = StringToBytes(key);
using var hashedStream = await this.HashInternal(keyBytes, rawStream).ConfigureAwait(false);
return StreamToString(hashedStream);
}
public async Task<byte[]> Hash(byte[] key, byte[] raw)
{
using var rawStream = BytesToStream(raw);
using var hashedStream = await this.HashInternal(key, rawStream).ConfigureAwait(false);
return StreamToBytes(hashedStream);
}
public async Task<Stream> Hash(byte[] key, Stream raw)
{
return await this.HashInternal(key, raw);
}
private static byte[] StreamToBytes(Stream value)
{
value.Position = 0;
var buffer = new byte[1024];
using var ms = new MemoryStream();
var read = -1;
do
{
read = value.Read(buffer, 0, buffer.Length);
ms.Write(buffer, 0, read);
} while (read > 0);
return ms.ToArray();
}
private static string StreamToString(Stream value)
{
return Convert.ToBase64String(StreamToBytes(value));
}
private static byte[] StringToBytes(string value)
{
return Convert.FromBase64String(value);
}
private static Stream BytesToStream(byte[] value)
{
return new MemoryStream(value);
}
private async Task<Stream> HashInternal(byte[] key, Stream raw)
{
if (raw is null)
{
throw new ArgumentNullException(nameof(raw));
}
using var algo = this.GetHashAlgorithm(key);
return await Task.Run(() => new MemoryStream(algo.ComputeHash(raw)));
}
}
@@ -0,0 +1,63 @@
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace System.Security.Hashing;
public abstract class BaseHashingService<T>
where T : HashAlgorithm
{
protected abstract T GetHashAlgorithm();
public async Task<string> Hash(string raw)
{
using var rawStream = BytesToStream(StringToBytes(raw));
using var hashedStream = await this.HashInternal(rawStream).ConfigureAwait(false);
return StreamToString(hashedStream);
}
public async Task<byte[]> Hash(byte[] raw)
{
using var rawStream = BytesToStream(raw);
using var hashedStream = await this.HashInternal(rawStream).ConfigureAwait(false);
return StreamToBytes(hashedStream);
}
public async Task<Stream> Hash(Stream raw)
{
return await this.HashInternal(raw);
}
private static byte[] StreamToBytes(Stream value)
{
value.Position = 0;
var buffer = new byte[1024];
using var ms = new MemoryStream();
var read = -1;
do
{
read = value.Read(buffer, 0, buffer.Length);
ms.Write(buffer, 0, read);
} while (read > 0);
return ms.ToArray();
}
private static string StreamToString(Stream value)
{
return Convert.ToBase64String(StreamToBytes(value));
}
private static byte[] StringToBytes(string value)
{
return Convert.FromBase64String(value);
}
private static Stream BytesToStream(byte[] value)
{
return new MemoryStream(value);
}
private async Task<Stream> HashInternal(Stream raw)
{
if (raw is null)
{
throw new ArgumentNullException(nameof(raw));
}
using var algo = this.GetHashAlgorithm();
return await Task.Run(() => new MemoryStream(algo.ComputeHash(raw)));
}
}
@@ -0,0 +1,10 @@
using System.Security.Cryptography;
namespace System.Security.Hashing;
public sealed class HMACMD5Service : BaseHMACService<HMACMD5>, IHMACMD5Service
{
protected override HMACMD5 GetHashAlgorithm(byte[] key)
{
return new HMACMD5(key);
}
}
@@ -0,0 +1,10 @@
using System.Security.Cryptography;
namespace System.Security.Hashing;
public sealed class HMACSha1Service : BaseHMACService<HMACSHA1>, IHMACSha1Service
{
protected override HMACSHA1 GetHashAlgorithm(byte[] key)
{
return new HMACSHA1(key);
}
}
@@ -0,0 +1,10 @@
using System.Security.Cryptography;
namespace System.Security.Hashing;
public sealed class HMACSha256Service : BaseHMACService<HMACSHA256>, IHMACSha256Service
{
protected override HMACSHA256 GetHashAlgorithm(byte[] key)
{
return new HMACSHA256(key);
}
}
@@ -0,0 +1,10 @@
using System.Security.Cryptography;
namespace System.Security.Hashing;
public sealed class HMACSha384Service : BaseHMACService<HMACSHA384>, IHMACSha384Service
{
protected override HMACSHA384 GetHashAlgorithm(byte[] key)
{
return new HMACSHA384(key);
}
}
@@ -0,0 +1,10 @@
using System.Security.Cryptography;
namespace System.Security.Hashing;
public sealed class HMACSha512Service : BaseHMACService<HMACSHA512>, IHMACSha512Service
{
protected override HMACSHA512 GetHashAlgorithm(byte[] key)
{
return new HMACSHA512(key);
}
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface IHMACMD5Service : IHMACService
{
}
@@ -0,0 +1,10 @@
using System.IO;
using System.Threading.Tasks;
namespace System.Security.Hashing;
public interface IHMACService
{
Task<string> Hash(string key, string raw);
Task<byte[]> Hash(byte[] key, byte[] raw);
Task<Stream> Hash(byte[] key, Stream raw);
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface IHMACSha1Service : IHMACService
{
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface IHMACSha256Service : IHMACService
{
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface IHMACSha384Service : IHMACService
{
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface IHMACSha512Service : IHMACService
{
}
@@ -1,12 +1,11 @@
using System.IO;
using System.Threading.Tasks;
namespace System.Security.Hashing
namespace System.Security.Hashing;
public interface IHashingService
{
public interface IHashingService
{
Task<string> Hash(string raw);
Task<byte[]> Hash(byte[] raw);
Task<Stream> Hash(Stream raw);
}
Task<string> Hash(string raw);
Task<byte[]> Hash(byte[] raw);
Task<Stream> Hash(Stream raw);
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface IMD5HashingService : IHashingService
{
}
@@ -1,47 +1,46 @@
using System.Threading.Tasks;
namespace System.Hashing
namespace System.Security.Hashing;
public interface IPasswordHashingService
{
public interface IPasswordHashingService
{
/// <summary>
/// Hash provided raw password and return the hashed value.
/// </summary>
/// <param name="raw">Base64 encoded password.</param>
/// <param name="salt">Base64 encoded salt to be used for hashing.</param>
/// <param name="length">Length of the resulting hash.</param>
/// <param name="iterations">Number of hashing iterations to be performed.</param>
/// <remarks>
/// The length of the hash will vary due to the base64 encoding of the resulting hash.
/// </remarks>
/// <returns>Base64 encoded hashed password.</returns>
Task<string> Hash(string raw, string salt, int length, int iterations);
/// <summary>
/// Hash provided raw password and return the hashed value.
/// </summary>
/// <param name="raw">Password to be hashed.</param>
/// <param name="salt">Salt to be used for hashing.</param>
/// <param name="length">Length of the resulting hash.</param>
/// <param name="iterations">Number of hashing iterations to be performed.</param>
/// <returns>Hashed password.</returns>
Task<byte[]> Hash(byte[] raw, byte[] salt, int length, int iterations);
/// <summary>
/// Verify provided password against a hashed password.
/// </summary>
/// <param name="hash">Base64 encoding of the previously hashed password.</param>
/// <param name="password">Base64 encoding of the password to be verified.</param>
/// <param name="salt">Salt used to hash the previous password.</param>
/// <param name="iterations">Number of iterations used to hash the previous password.</param>
/// <returns>Returns true if password matches the previously hashed password. Otherwise returns false.</returns>
Task<bool> VerifyPassword(string hash, string password, string salt, int iterations);
/// <summary>
/// Verify provided password against a hashed password.
/// </summary>
/// <param name="hash">Previously hashed password.</param>
/// <param name="password">Password to be verified.</param>
/// <param name="salt">Salt used to hash the previous password.</param>
/// <param name="iterations">Number of iterations used to hash the previous password.</param>
/// <returns>Returns true if password matches the previously hashed password. Otherwise returns false.</returns>
Task<bool> VerifyPassword(byte[] hash, byte[] password, byte[] salt, int iterations);
}
/// <summary>
/// Hash provided raw password and return the hashed value.
/// </summary>
/// <param name="raw">Base64 encoded password.</param>
/// <param name="salt">Base64 encoded salt to be used for hashing.</param>
/// <param name="length">Length of the resulting hash.</param>
/// <param name="iterations">Number of hashing iterations to be performed.</param>
/// <remarks>
/// The length of the hash will vary due to the base64 encoding of the resulting hash.
/// </remarks>
/// <returns>Base64 encoded hashed password.</returns>
Task<string> Hash(string raw, string salt, int length, int iterations);
/// <summary>
/// Hash provided raw password and return the hashed value.
/// </summary>
/// <param name="raw">Password to be hashed.</param>
/// <param name="salt">Salt to be used for hashing.</param>
/// <param name="length">Length of the resulting hash.</param>
/// <param name="iterations">Number of hashing iterations to be performed.</param>
/// <returns>Hashed password.</returns>
Task<byte[]> Hash(byte[] raw, byte[] salt, int length, int iterations);
/// <summary>
/// Verify provided password against a hashed password.
/// </summary>
/// <param name="hash">Base64 encoding of the previously hashed password.</param>
/// <param name="password">Base64 encoding of the password to be verified.</param>
/// <param name="salt">Salt used to hash the previous password.</param>
/// <param name="iterations">Number of iterations used to hash the previous password.</param>
/// <returns>Returns true if password matches the previously hashed password. Otherwise returns false.</returns>
Task<bool> VerifyPassword(string hash, string password, string salt, int iterations);
/// <summary>
/// Verify provided password against a hashed password.
/// </summary>
/// <param name="hash">Previously hashed password.</param>
/// <param name="password">Password to be verified.</param>
/// <param name="salt">Salt used to hash the previous password.</param>
/// <param name="iterations">Number of iterations used to hash the previous password.</param>
/// <returns>Returns true if password matches the previously hashed password. Otherwise returns false.</returns>
Task<bool> VerifyPassword(byte[] hash, byte[] password, byte[] salt, int iterations);
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface ISha1HashingService : IHashingService
{
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface ISha256HashingService : IHashingService
{
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface ISha384HashingService : IHashingService
{
}
@@ -0,0 +1,4 @@
namespace System.Security.Hashing;
public interface ISha512HashingService : IHashingService
{
}
@@ -0,0 +1,10 @@
using System.Security.Cryptography;
namespace System.Security.Hashing;
public sealed class MD5HashingService : BaseHashingService<MD5>, IMD5HashingService
{
protected override MD5 GetHashAlgorithm()
{
return MD5.Create();
}
}
@@ -1,101 +1,100 @@
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace System.Hashing
namespace System.Security.Hashing;
public sealed class Rfc2898DeriveBytesPasswordHashingService : IPasswordHashingService
{
public sealed class Rfc2898DeriveBytesPasswordHashingService : IPasswordHashingService
private const int MinimumIterations = 10000;
private const int MinimumHashLength = 32;
public Rfc2898DeriveBytesPasswordHashingService()
{
private const int MinimumIterations = 10000;
private const int MinimumHashLength = 32;
}
public Rfc2898DeriveBytesPasswordHashingService()
public async Task<string> Hash(string raw, string salt, int length, int iterations)
{
this.ValidateHashArguments(raw, salt, length, iterations);
var rawBytes = Convert.FromBase64String(raw);
var saltBytes = Convert.FromBase64String(salt);
var hashBytes = await this.HashInternal(rawBytes, saltBytes, length, iterations);
return Convert.ToBase64String(hashBytes);
}
public Task<byte[]> Hash(byte[] raw, byte[] salt, int length, int iterations)
{
this.ValidateHashArguments(raw, salt, length, iterations);
return this.HashInternal(raw, salt, length, iterations);
}
public Task<bool> VerifyPassword(string hash, string password, string salt, int iterations)
{
this.ValidateVerifyArguments(hash, password, salt, iterations);
var hashBytes = Convert.FromBase64String(hash);
var saltBytes = Convert.FromBase64String(salt);
var passwordBytes = Convert.FromBase64String(password);
return this.VerifyPasswordInternal(hashBytes, passwordBytes, saltBytes, iterations);
}
public Task<bool> VerifyPassword(byte[] hash, byte[] password, byte[] salt, int iterations)
{
this.ValidateVerifyArguments(hash, password, salt, iterations);
return this.VerifyPasswordInternal(hash, password, salt, iterations);
}
private void ValidateHashArguments(object raw, object salt, int length, int iterations)
{
_ = raw ?? throw new ArgumentNullException(nameof(raw));
_ = salt ?? throw new ArgumentNullException(nameof(salt));
if (iterations < MinimumIterations)
{
throw new InvalidOperationException($"Unable to perform secure hash. Iteration count must be over {MinimumIterations}");
}
public async Task<string> Hash(string raw, string salt, int length, int iterations)
if (length < MinimumHashLength)
{
this.ValidateHashArguments(raw, salt, length, iterations);
var rawBytes = Convert.FromBase64String(raw);
var saltBytes = Convert.FromBase64String(salt);
var hashBytes = await this.HashInternal(rawBytes, saltBytes, length, iterations);
return Convert.ToBase64String(hashBytes);
}
public Task<byte[]> Hash(byte[] raw, byte[] salt, int length, int iterations)
{
this.ValidateHashArguments(raw, salt, length, iterations);
return this.HashInternal(raw, salt, length, iterations);
}
public Task<bool> VerifyPassword(string hash, string password, string salt, int iterations)
{
this.ValidateVerifyArguments(hash, password, salt, iterations);
var hashBytes = Convert.FromBase64String(hash);
var saltBytes = Convert.FromBase64String(salt);
var passwordBytes = Convert.FromBase64String(password);
return this.VerifyPasswordInternal(hashBytes, passwordBytes, saltBytes, iterations);
}
public Task<bool> VerifyPassword(byte[] hash, byte[] password, byte[] salt, int iterations)
{
this.ValidateVerifyArguments(hash, password, salt, iterations);
return this.VerifyPasswordInternal(hash, password, salt, iterations);
}
private void ValidateHashArguments(object raw, object salt, int length, int iterations)
{
_ = raw ?? throw new ArgumentNullException(nameof(raw));
_ = salt ?? throw new ArgumentNullException(nameof(salt));
if (iterations < MinimumIterations)
{
throw new InvalidOperationException($"Unable to perform secure hash. Iteration count must be over {MinimumIterations}");
}
if (length < MinimumHashLength)
{
throw new InvalidOperationException($"Unable to perform secure hash. Hash length must be over {MinimumHashLength}");
}
}
private void ValidateVerifyArguments(object hash, object password, object salt, int iterations)
{
_ = hash ?? throw new ArgumentNullException(nameof(hash));
_ = salt ?? throw new ArgumentNullException(nameof(salt));
_ = password ?? throw new ArgumentNullException(nameof(password));
if (iterations < MinimumIterations)
{
throw new InvalidOperationException($"Unable to verify hash. Iteration count must be over {MinimumIterations}");
}
}
private Task<byte[]> HashInternal(byte[] raw, byte[] salt, int length, int iterations)
{
using var pbkdf2 = new Rfc2898DeriveBytes(raw, salt, iterations);
var hash = pbkdf2.GetBytes(length);
return Task.FromResult(hash);
}
private Task<bool> VerifyPasswordInternal(byte[] hash, byte[] password, byte[] salt, int iterations)
{
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
var hashToVerify = pbkdf2.GetBytes(hash.Length);
var lengthToVerify = Math.Max(hash.Length, hashToVerify.Length);
var match = true;
if (lengthToVerify <= 0)
{
return Task.FromResult(false);
}
for(var i = 0; i < lengthToVerify; i++)
{
if (i < hash.Length && i < hashToVerify.Length)
{
if (hashToVerify[i].Equals(hash[i]) is false)
{
match = false;
}
}
}
return Task.FromResult(match && hash.Length.Equals(hashToVerify.Length));
throw new InvalidOperationException($"Unable to perform secure hash. Hash length must be over {MinimumHashLength}");
}
}
private void ValidateVerifyArguments(object hash, object password, object salt, int iterations)
{
_ = hash ?? throw new ArgumentNullException(nameof(hash));
_ = salt ?? throw new ArgumentNullException(nameof(salt));
_ = password ?? throw new ArgumentNullException(nameof(password));
if (iterations < MinimumIterations)
{
throw new InvalidOperationException($"Unable to verify hash. Iteration count must be over {MinimumIterations}");
}
}
private Task<byte[]> HashInternal(byte[] raw, byte[] salt, int length, int iterations)
{
using var pbkdf2 = new Rfc2898DeriveBytes(raw, salt, iterations);
var hash = pbkdf2.GetBytes(length);
return Task.FromResult(hash);
}
private Task<bool> VerifyPasswordInternal(byte[] hash, byte[] password, byte[] salt, int iterations)
{
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
var hashToVerify = pbkdf2.GetBytes(hash.Length);
var lengthToVerify = Math.Max(hash.Length, hashToVerify.Length);
var match = true;
if (lengthToVerify <= 0)
{
return Task.FromResult(false);
}
for(var i = 0; i < lengthToVerify; i++)
{
if (i < hash.Length && i < hashToVerify.Length)
{
if (hashToVerify[i].Equals(hash[i]) is false)
{
match = false;
}
}
}
return Task.FromResult(match && hash.Length.Equals(hashToVerify.Length));
}
}
@@ -0,0 +1,7 @@
using System.Security.Cryptography;
namespace System.Security.Hashing;
public sealed class Sha1HashingService : BaseHashingService<SHA1>, ISha1HashingService
{
protected override SHA1 GetHashAlgorithm() => SHA1.Create();
}
@@ -1,62 +1,8 @@
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Security.Cryptography;
namespace System.Security.Hashing
namespace System.Security.Hashing;
public sealed class Sha256HashingService : BaseHashingService<SHA256>, ISha256HashingService
{
public sealed class Sha256HashingService : IHashingService
{
public async Task<string> Hash(string raw)
{
using var rawStream = BytesToStream(StringToBytes(raw));
using var hashedStream = await HashInternal(rawStream).ConfigureAwait(false);
return StreamToString(hashedStream);
}
public async Task<byte[]> Hash(byte[] raw)
{
using var rawStream = BytesToStream(raw);
using var hashedStream = await HashInternal(rawStream).ConfigureAwait(false);
return StreamToBytes(hashedStream);
}
public async Task<Stream> Hash(Stream raw)
{
return await HashInternal(raw);
}
private static byte[] StreamToBytes(Stream value)
{
value.Position = 0;
var buffer = new byte[1024];
using var ms = new MemoryStream();
var read = -1;
do
{
read = value.Read(buffer, 0, buffer.Length);
ms.Write(buffer, 0, read);
} while (read > 0);
return ms.ToArray();
}
private static string StreamToString(Stream value)
{
return Convert.ToBase64String(StreamToBytes(value));
}
private static byte[] StringToBytes(string value)
{
return Convert.FromBase64String(value);
}
private static Stream BytesToStream(byte[] value)
{
return new MemoryStream(value);
}
private static async Task<Stream> HashInternal(Stream raw)
{
if (raw is null)
{
throw new ArgumentNullException(nameof(raw));
}
using var sha256 = SHA256.Create();
return await Task.Run(() => new MemoryStream(sha256.ComputeHash(raw)));
}
}
protected override SHA256 GetHashAlgorithm() => SHA256.Create();
}
@@ -0,0 +1,10 @@
using System.Security.Cryptography;
namespace System.Security.Hashing;
public sealed class Sha384HashingService : BaseHashingService<SHA384>, ISha384HashingService
{
protected override SHA384 GetHashAlgorithm()
{
return SHA384.Create();
}
}
@@ -0,0 +1,7 @@
using System.Security.Cryptography;
namespace System.Security.Hashing;
public sealed class Sha512HashingService : BaseHashingService<SHA512>, ISha512HashingService
{
protected override SHA512 GetHashAlgorithm() => SHA512.Create();
}
@@ -1,59 +1,58 @@
using System.Security.Cryptography;
namespace System.Rng
namespace System.Security.Rng;
public sealed class CryptoRngProvider : ICryptoRngProvider, IDisposable
{
public sealed class CryptoRngProvider : ICryptoRngProvider, IDisposable
private readonly RNGCryptoServiceProvider rngProvider;
private bool disposedValue;
public CryptoRngProvider()
{
private readonly RNGCryptoServiceProvider rngProvider;
private bool disposedValue;
public CryptoRngProvider()
{
this.rngProvider = new RNGCryptoServiceProvider();
}
public CryptoRngProvider(CspParameters cspParams)
{
this.rngProvider = new RNGCryptoServiceProvider(cspParams);
}
this.rngProvider = new RNGCryptoServiceProvider();
}
public CryptoRngProvider(CspParameters cspParams)
{
this.rngProvider = new RNGCryptoServiceProvider(cspParams);
}
public void GetBytes(byte[] data)
{
this.rngProvider.GetBytes(data);
}
public void GetNonZeroBytes(byte[] data)
{
this.rngProvider.GetNonZeroBytes(data);
}
public byte[] GetBytes(int byteCount)
{
var bytes = new byte[byteCount];
this.GetBytes(bytes);
return bytes;
}
public byte[] GetNonZeroBytes(int byteCount)
{
var bytes = new byte[byteCount];
this.GetNonZeroBytes(bytes);
return bytes;
}
public void GetBytes(byte[] data)
{
this.rngProvider.GetBytes(data);
}
public void GetNonZeroBytes(byte[] data)
{
this.rngProvider.GetNonZeroBytes(data);
}
public byte[] GetBytes(int byteCount)
{
var bytes = new byte[byteCount];
this.GetBytes(bytes);
return bytes;
}
public byte[] GetNonZeroBytes(int byteCount)
{
var bytes = new byte[byteCount];
this.GetNonZeroBytes(bytes);
return bytes;
}
private void Dispose(bool disposing)
private void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (!this.disposedValue)
if (disposing)
{
if (disposing)
{
this.rngProvider.Dispose();
}
this.disposedValue = true;
this.rngProvider.Dispose();
}
}
public void Dispose()
{
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
this.disposedValue = true;
}
}
public void Dispose()
{
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
@@ -1,28 +1,27 @@
namespace System.Rng
namespace System.Security.Rng;
public interface ICryptoRngProvider
{
public interface ICryptoRngProvider
{
/// <summary>
/// Populate provided array of bytes with cryptographically secure random bytes.
/// </summary>
/// <param name="data">Array of bytes to be populated.</param>
public void GetBytes(byte[] data);
/// <summary>
/// Return an array of cryptographically secure random bytes.
/// </summary>
/// <param name="byteCount">Length of the returned array.</param>
/// <returns>Array containing cryptographically secure random values.</returns>
public byte[] GetBytes(int byteCount);
/// <summary>
/// Populate provided array of bytes with cryptographically secure random non-zero bytes.
/// </summary>
/// <param name="data">Array of bytes to be populated.</param>
public void GetNonZeroBytes(byte[] data);
/// <summary>
/// Return an array of cryptographically secure random non-zero bytes.
/// </summary>
/// <param name="byteCount">Length of the returned array.</param>
/// <returns>Array containing cryptographically secure random non-zero values.</returns>
public byte[] GetNonZeroBytes(int byteCount);
}
/// <summary>
/// Populate provided array of bytes with cryptographically secure random bytes.
/// </summary>
/// <param name="data">Array of bytes to be populated.</param>
public void GetBytes(byte[] data);
/// <summary>
/// Return an array of cryptographically secure random bytes.
/// </summary>
/// <param name="byteCount">Length of the returned array.</param>
/// <returns>Array containing cryptographically secure random values.</returns>
public byte[] GetBytes(int byteCount);
/// <summary>
/// Populate provided array of bytes with cryptographically secure random non-zero bytes.
/// </summary>
/// <param name="data">Array of bytes to be populated.</param>
public void GetNonZeroBytes(byte[] data);
/// <summary>
/// Return an array of cryptographically secure random non-zero bytes.
/// </summary>
/// <param name="byteCount">Length of the returned array.</param>
/// <returns>Array containing cryptographically secure random non-zero values.</returns>
public byte[] GetNonZeroBytes(int byteCount);
}
@@ -7,9 +7,10 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RootNamespace>System</RootNamespace>
<Version>1.2.1</Version>
<Version>1.3.1</Version>
<Authors>Alexandru Macocian</Authors>
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
<Description>Security extensions for the System namespace</Description>
</PropertyGroup>
<ItemGroup>
@@ -20,7 +21,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="5.0.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
</ItemGroup>
</Project>
@@ -1,23 +1,22 @@
using System.IO;
using System.Security.Cryptography;
namespace System.Security.Utilities
namespace System.Security.Utilities;
internal sealed class NotClosingCryptoStream : CryptoStream
{
internal sealed class NotClosingCryptoStream : CryptoStream
public NotClosingCryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
: base(stream, transform, mode)
{
public NotClosingCryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
: base(stream, transform, mode)
}
protected override void Dispose(bool disposing)
{
if (!this.HasFlushedFinalBlock)
{
this.FlushFinalBlock();
}
protected override void Dispose(bool disposing)
{
if (!this.HasFlushedFinalBlock)
{
this.FlushFinalBlock();
}
base.Dispose(false);
}
base.Dispose(false);
}
}
@@ -0,0 +1,36 @@
using System.Extensions;
using System.Threading.Tasks;
namespace System.Cache;
public sealed class AsyncValueCache<T>(Func<Task<T>> cacheRefreshOperation, TimeSpan cacheDuration)
where T : class
{
private readonly Func<Task<T>> cacheRefreshOperation = cacheRefreshOperation.ThrowIfNull(nameof(cacheRefreshOperation));
private readonly TimeSpan cacheDuration = cacheDuration;
private T? value;
private DateTime lastCacheRefresh = DateTime.MinValue;
public async Task<T> GetValue()
{
if (DateTime.Now - this.lastCacheRefresh < this.cacheDuration &&
this.value is not null)
{
return this.value;
}
return await this.PerformCacheRefresh();
}
public async Task<T> ForceCacheRefresh()
{
return await this.PerformCacheRefresh();
}
private async Task<T> PerformCacheRefresh()
{
this.value = await this.cacheRefreshOperation();
this.lastCacheRefresh = DateTime.Now;
return this.value;
}
}
@@ -0,0 +1,35 @@
using System.Extensions;
namespace System.Cache;
public sealed class ValueCache<T>(Func<T> cacheRefreshOperation, TimeSpan cacheDuration)
where T : class
{
private readonly Func<T> cacheRefreshOperation = cacheRefreshOperation.ThrowIfNull(nameof(cacheRefreshOperation));
private readonly TimeSpan cacheDuration = cacheDuration;
private T? value;
private DateTime lastCacheRefresh = DateTime.MinValue;
public T GetValue()
{
if (DateTime.Now - this.lastCacheRefresh < this.cacheDuration &&
this.value is not null)
{
return this.value;
}
return this.PerformCacheRefresh();
}
public T ForceCacheRefresh()
{
return this.PerformCacheRefresh();
}
private T PerformCacheRefresh()
{
this.value = this.cacheRefreshOperation();
this.lastCacheRefresh = DateTime.Now;
return this.value;
}
}
@@ -1,393 +1,401 @@
using System.Collections.Generic;
namespace System.Collections.Generic
{
/// <summary>
/// AVL tree implementation.
/// Thanks to Karim Oumghar for the implementation example.
/// Read on https://simpledevcode.wordpress.com/2014/09/16/avl-tree-in-c/
/// </summary>
/// <typeparam name="T">Provided type.</typeparam>
[Serializable]
public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
{
#region Fields
[Serializable]
private class AVLNode<TKey>
{
public TKey Value;
public AVLNode<TKey> Left;
public AVLNode<TKey> Right;
public AVLNode(TKey value)
{
this.Value = value;
}
}
AVLNode<T> root;
private int count = 0;
private readonly bool isReadOnly = false;
#endregion
#region Properties
/// <summary>
/// Count of items currently stored in the tree.
/// </summary>
public int Count
{
get
{
return count;
}
}
/// <summary>
/// True if the collection is readonly. False otherwise.
/// </summary>
public bool IsReadOnly => isReadOnly;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of an AVLTree collection.
/// </summary>
public AVLTree()
{
namespace System.Collections.Generic;
}
#endregion
#region Public Methods
/// <summary>
/// Adds the value to the tree.
/// </summary>
/// <param name="value">Value to be added to the tree.</param>
public void Add(T value)
/// <summary>
/// AVL tree implementation.
/// Thanks to Karim Oumghar for the implementation example.
/// Read on https://simpledevcode.wordpress.com/2014/09/16/avl-tree-in-c/
/// </summary>
/// <typeparam name="T">Provided type.</typeparam>
[Serializable]
public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
{
#region Fields
[Serializable]
private class AVLNode<TKey>
{
public TKey Value;
public AVLNode<TKey> Left = default!;
public AVLNode<TKey> Right = default!;
public AVLNode(TKey value)
{
count++;
var newItem = new AVLNode<T>(value);
if (root == null)
{
root = newItem;
}
else
{
root = RecursiveInsertion(root, newItem);
}
this.Value = value;
}
/// <summary>
/// Checks if the key is contained into the tree.
/// </summary>
/// <param name="value">Value to be checked if present in the tree.</param>
/// <returns>True if the value is in the tree.</returns>
public bool Contains(T value)
}
AVLNode<T> root = default!;
private int count = 0;
private readonly bool isReadOnly = false;
#endregion
#region Properties
/// <summary>
/// Count of items currently stored in the tree.
/// </summary>
public int Count
{
get
{
var node = Find(value, root);
if (node == null)
{
return false;
}
if (node.Value.CompareTo(value) == 0)
{
return true;
}
else
{
return false;
}
return this.count;
}
/// <summary>
/// Removes the specified value from the tree.
/// </summary>
/// <param name="value">Value to be deleted.</param>
public bool Remove(T value)
}
/// <summary>
/// True if the collection is readonly. False otherwise.
/// </summary>
public bool IsReadOnly => this.isReadOnly;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of an AVLTree collection.
/// </summary>
public AVLTree()
{
}
#endregion
#region Public Methods
/// <summary>
/// Adds the value to the tree.
/// </summary>
/// <param name="value">Value to be added to the tree.</param>
public void Add(T value)
{
this.count++;
var newItem = new AVLNode<T>(value);
if (this.root == null)
{
this.root = newItem;
}
else
{
this.root = this.RecursiveInsertion(this.root, newItem);
}
}
/// <summary>
/// Checks if the key is contained into the tree.
/// </summary>
/// <param name="value">Value to be checked if present in the tree.</param>
/// <returns>True if the value is in the tree.</returns>
public bool Contains(T value)
{
var node = this.Find(value, this.root);
if (node == null)
{
return false;
}
if (node.Value.CompareTo(value) == 0)
{
root = Delete(root, value);
return true;
}
/// <summary>
/// Clears the tree.
/// </summary>
public void Clear()
else
{
var queue = new Queue<AVLNode<T>>();
queue.Enqueue(root);
return false;
}
}
/// <summary>
/// Removes the specified value from the tree.
/// </summary>
/// <param name="value">Value to be deleted.</param>
public bool Remove(T value)
{
this.root = this.Delete(this.root, value)!;
return true;
}
/// <summary>
/// Clears the tree.
/// </summary>
public void Clear()
{
var queue = new Queue<AVLNode<T>>();
queue.Enqueue(this.root);
while (queue.Count > 0)
{
var currentNode = queue.Dequeue();
if (currentNode.Left != null)
{
queue.Enqueue(currentNode.Left);
currentNode.Left = null;
count--;
}
if (currentNode.Right != null)
{
queue.Enqueue(currentNode.Right);
currentNode.Right = null;
count--;
}
}
root = null;
count--;
}
/// <summary>
/// Copies the tree onto the provided array.
/// </summary>
/// <param name="array">Array to store the values in the tree.</param>
/// <param name="arrayIndex">Starting index of the provided array.</param>
public void CopyTo(T[] array, int arrayIndex)
while (queue.Count > 0)
{
var queue = new Queue<AVLNode<T>>();
queue.Enqueue(root);
while (queue.Count > 0)
var currentNode = queue.Dequeue();
if (currentNode.Left != null)
{
var currentNode = queue.Dequeue();
array[arrayIndex++] = currentNode.Value;
if (currentNode.Left != null)
{
queue.Enqueue(currentNode.Left);
}
if (currentNode.Right != null)
{
queue.Enqueue(currentNode.Right);
}
queue.Enqueue(currentNode.Left);
currentNode.Left = default!;
this.count--;
}
if (currentNode.Right != null)
{
queue.Enqueue(currentNode.Right);
currentNode.Right = default!;
this.count--;
}
}
/// <summary>
/// Enumerator that iterates over the tree.
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
this.root = default!;
this.count--;
}
/// <summary>
/// Copies the tree onto the provided array.
/// </summary>
/// <param name="array">Array to store the values in the tree.</param>
/// <param name="arrayIndex">Starting index of the provided array.</param>
public void CopyTo(T[] array, int arrayIndex)
{
var queue = new Queue<AVLNode<T>>();
queue.Enqueue(this.root);
while (queue.Count > 0)
{
return GetEnumerator(root);
var currentNode = queue.Dequeue();
array[arrayIndex++] = currentNode.Value;
if (currentNode.Left != null)
{
queue.Enqueue(currentNode.Left);
}
if (currentNode.Right != null)
{
queue.Enqueue(currentNode.Right);
}
}
/// <summary>
/// Copies the tree structure into an array.
/// </summary>
/// <returns>Array containing the values contained in the tree.</returns>
public T[] ToArray()
}
/// <summary>
/// Enumerator that iterates over the tree.
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
return this.GetEnumerator(this.root);
}
/// <summary>
/// Copies the tree structure into an array.
/// </summary>
/// <returns>Array containing the values contained in the tree.</returns>
public T[] ToArray()
{
var array = new T[this.count];
this.CopyTo(array, 0);
return array;
}
#endregion
#region Private Methods
private AVLNode<T> RecursiveInsertion(AVLNode<T> current, AVLNode<T> n)
{
if (current == null)
{
var array = new T[count];
CopyTo(array, 0);
return array;
}
#endregion
#region Private Methods
private AVLNode<T> RecursiveInsertion(AVLNode<T> current, AVLNode<T> n)
{
if (current == null)
{
current = n;
return current;
}
else if (n.Value.CompareTo(current.Value) < 0)
{
current.Left = RecursiveInsertion(current.Left, n);
current = BalanceTree(current);
}
else if (n.Value.CompareTo(current.Value) > 0)
{
current.Right = RecursiveInsertion(current.Right, n);
current = BalanceTree(current);
}
current = n;
return current;
}
private AVLNode<T> BalanceTree(AVLNode<T> current)
else if (n.Value.CompareTo(current.Value) < 0)
{
var b_factor = BalanceFactor(current);
if (b_factor > 1)
{
if (BalanceFactor(current.Left) > 0)
{
current = RotateLL(current);
}
else
{
current = RotateLR(current);
}
}
else if (b_factor < -1)
{
if (BalanceFactor(current.Right) > 0)
{
current = RotateRL(current);
}
else
{
current = RotateRR(current);
}
}
return current;
current.Left = this.RecursiveInsertion(current.Left, n);
current = this.BalanceTree(current);
}
private AVLNode<T> Delete(AVLNode<T> current, T target)
else if (n.Value.CompareTo(current.Value) > 0)
{
AVLNode<T> parent;
if (current == null)
{ return null; }
current.Right = this.RecursiveInsertion(current.Right, n);
current = this.BalanceTree(current);
}
return current;
}
private AVLNode<T> BalanceTree(AVLNode<T> current)
{
var b_factor = this.BalanceFactor(current);
if (b_factor > 1)
{
if (this.BalanceFactor(current.Left) > 0)
{
current = this.RotateLL(current);
}
else
{
//left subtree
if (target.CompareTo(current.Value) < 0)
{
current.Left = Delete(current.Left, target);
if (BalanceFactor(current) == -2)//here
{
if (BalanceFactor(current.Right) <= 0)
{
current = RotateRR(current);
}
else
{
current = RotateRL(current);
}
}
}
//right subtree
else if (target.CompareTo(current.Value) > 0)
{
current.Right = Delete(current.Right, target);
if (BalanceFactor(current) == 2)
{
if (BalanceFactor(current.Left) >= 0)
{
current = RotateLL(current);
}
else
{
current = RotateLR(current);
}
}
}
//if target is found
else
{
count--;
if (current.Right != null)
{
//delete its inorder successor
parent = current.Right;
while (parent.Left != null)
{
parent = parent.Left;
}
current.Value = parent.Value;
current.Right = Delete(current.Right, parent.Value);
if (BalanceFactor(current) == 2)//rebalancing
{
if (BalanceFactor(current.Left) >= 0)
{
current = RotateLL(current);
}
else { current = RotateLR(current); }
}
}
else
{ //if current.left != null
return current.Left;
}
}
current = this.RotateLR(current);
}
return current;
}
private AVLNode<T> Find(T target, AVLNode<T> current)
else if (b_factor < -1)
{
if (current == null)
if (this.BalanceFactor(current.Right) > 0)
{
return null;
current = this.RotateRL(current);
}
else
{
current = this.RotateRR(current);
}
}
return current;
}
private AVLNode<T>? Delete(AVLNode<T> current, T target)
{
if (current == null)
{ return null; }
else
{
//left subtree
if (target.CompareTo(current.Value) < 0)
{
if (target.CompareTo(current.Value) == 0)
current.Left = this.Delete(current.Left, target)!;
if (this.BalanceFactor(current) == -2)//here
{
return current;
if (this.BalanceFactor(current.Right) <= 0)
{
current = this.RotateRR(current);
}
else
{
current = this.RotateRL(current);
}
}
}
//right subtree
else if (target.CompareTo(current.Value) > 0)
{
current.Right = this.Delete(current.Right, target)!;
if (this.BalanceFactor(current) == 2)
{
if (this.BalanceFactor(current.Left) >= 0)
{
current = this.RotateLL(current);
}
else
{
current = this.RotateLR(current);
}
}
}
//if target is found
else
{
this.count--;
if (current.Right != null)
{
//delete its inorder successor
var parent = current.Right;
while (parent.Left != null)
{
parent = parent.Left;
}
current.Value = parent.Value;
current.Right = this.Delete(current.Right, parent.Value)!;
if (this.BalanceFactor(current) == 2)//rebalancing
{
if (this.BalanceFactor(current.Left) >= 0)
{
current = this.RotateLL(current);
}
else { current = this.RotateLR(current); }
}
}
else
{
return Find(target, current.Left);
{ //if current.left != null
return current.Left;
}
}
}
return current;
}
private AVLNode<T>? Find(T target, AVLNode<T> current)
{
if (current == null)
{
return null;
}
if (target.CompareTo(current.Value) < 0)
{
if (target.CompareTo(current.Value) == 0)
{
return current;
}
else
{
if (target.CompareTo(current.Value) == 0)
{
return current;
}
else
{
return Find(target, current.Right);
}
return this.Find(target, current.Left);
}
}
private int Max(int l, int r)
else
{
return l > r ? l : r;
}
private int GetHeight(AVLNode<T> current)
{
var height = 0;
if (current != null)
if (target.CompareTo(current.Value) == 0)
{
var l = GetHeight(current.Left);
var r = GetHeight(current.Right);
var m = Max(l, r);
height = m + 1;
return current;
}
return height;
}
private int BalanceFactor(AVLNode<T> current)
{
var l = GetHeight(current.Left);
var r = GetHeight(current.Right);
var b_factor = l - r;
return b_factor;
}
private AVLNode<T> RotateRR(AVLNode<T> parent)
{
var pivot = parent.Right;
parent.Right = pivot.Left;
pivot.Left = parent;
return pivot;
}
private AVLNode<T> RotateLL(AVLNode<T> parent)
{
var pivot = parent.Left;
parent.Left = pivot.Right;
pivot.Right = parent;
return pivot;
}
private AVLNode<T> RotateLR(AVLNode<T> parent)
{
var pivot = parent.Left;
parent.Left = RotateRR(pivot);
return RotateLL(parent);
}
private AVLNode<T> RotateRL(AVLNode<T> parent)
{
var pivot = parent.Right;
parent.Right = RotateLL(pivot);
return RotateRR(parent);
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
private IEnumerator<T> GetEnumerator(AVLNode<T> rootNode)
{
var queue = new Queue<AVLNode<T>>();
queue.Enqueue(rootNode);
while (queue.Count > 0)
else
{
var currentNode = queue.Dequeue();
yield return currentNode.Value;
if (currentNode.Left != null)
{
queue.Enqueue(currentNode.Left);
}
if (currentNode.Right != null)
{
queue.Enqueue(currentNode.Right);
}
return this.Find(target, current.Right);
}
}
#endregion
}
private int Max(int l, int r)
{
return l > r ? l : r;
}
private int GetHeight(AVLNode<T> current)
{
var height = 0;
if (current != null)
{
var l = this.GetHeight(current.Left);
var r = this.GetHeight(current.Right);
var m = this.Max(l, r);
height = m + 1;
}
return height;
}
private int BalanceFactor(AVLNode<T> current)
{
var l = this.GetHeight(current.Left);
var r = this.GetHeight(current.Right);
var b_factor = l - r;
return b_factor;
}
private AVLNode<T> RotateRR(AVLNode<T> parent)
{
var pivot = parent.Right;
parent.Right = pivot.Left;
pivot.Left = parent;
return pivot;
}
private AVLNode<T> RotateLL(AVLNode<T> parent)
{
var pivot = parent.Left;
parent.Left = pivot.Right;
pivot.Right = parent;
return pivot;
}
private AVLNode<T> RotateLR(AVLNode<T> parent)
{
var pivot = parent.Left;
parent.Left = this.RotateRR(pivot);
return this.RotateLL(parent);
}
private AVLNode<T> RotateRL(AVLNode<T> parent)
{
var pivot = parent.Right;
parent.Right = this.RotateLL(pivot);
return this.RotateRR(parent);
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
private IEnumerator<T> GetEnumerator(AVLNode<T> rootNode)
{
var queue = new Queue<AVLNode<T>>();
queue.Enqueue(rootNode);
while (queue.Count > 0)
{
var currentNode = queue.Dequeue();
yield return currentNode.Value;
if (currentNode.Left != null)
{
queue.Enqueue(currentNode.Left);
}
if (currentNode.Right != null)
{
queue.Enqueue(currentNode.Right);
}
}
}
#endregion
}
@@ -1,214 +1,219 @@
using System.Linq;
namespace System.Collections.Generic
namespace System.Collections.Generic;
/// <summary>
/// Binary heap implementation.
/// </summary>
/// <typeparam name="T">Provided type.</typeparam>
[Serializable]
public sealed class BinaryHeap<T> : IEnumerable<T> where T : IComparable<T>
{
#region Fields
T[] items;
private int capacity;
private int count;
private readonly int initialCapacity;
#endregion
#region Properties
/// <summary>
/// Binary heap implementation.
/// Minimum value from the heap.
/// </summary>
/// <typeparam name="T">Provided type.</typeparam>
[Serializable]
public sealed class BinaryHeap<T> : IEnumerable<T> where T : IComparable<T>
public T Min
{
#region Fields
T[] items;
private int capacity;
private int count;
private readonly int initialCapacity;
#endregion
#region Properties
/// <summary>
/// Minimum value from the heap.
/// </summary>
public T Min
get
{
get
{
return items[1];
}
return this.items[1];
}
/// <summary>
/// Maximum value from the heap.
/// </summary>
public T Max
{
get
{
return items[count];
}
}
/// <summary>
/// Capacity of the heap.
/// </summary>
public int Capacity
{
get => capacity;
set
{
if (value > capacity)
{
Array.Resize(ref items, value);
capacity = value;
}
}
}
/// <summary>
/// Number of elements in the heap.
/// </summary>
public int Count { get => count; }
#endregion
#region Constructors
/// <summary>
/// Constructor for a binary heap data structure.
/// </summary>
public BinaryHeap()
{
capacity = 10;
initialCapacity = capacity;
items = new T[capacity];
}
/// <summary>
/// Constructor for a binary heap data structure.
/// </summary>
/// <param name="capacity">Initial capacity of the heap. Used for initial alocation of the array.</param>
public BinaryHeap(int capacity)
{
this.capacity = capacity;
initialCapacity = capacity;
items = new T[capacity];
}
#endregion
#region Public Methods
/// <summary>
/// Adds value to the queue.
/// </summary>
/// <param name="value">Value to be added.</param>
public void Add(T value)
{
if (count == Capacity - 1)
{
Capacity = 2 * Capacity;
}
var position = ++count;
for (; position > 1 && value.CompareTo(items[position / 2]) < 0; position /= 2)
{
items[position] = items[position / 2];
}
items[position] = value;
}
/// <summary>
/// Removes the item at the root. Throws exception if there are no items in the heap.
/// </summary>
/// <returns>Value removed.</returns>
public T Remove()
{
if (count == 0)
{
throw new IndexOutOfRangeException("Heap is empty!");
}
var min = items[1];
items[1] = items[count--];
BubbleDown(1);
return min;
}
/// <summary>
/// Peeks at the item at the root. Throws exception if there are no items in the heap.
/// </summary>
/// <returns></returns>
public T Peek()
{
if (count == 0)
{
throw new IndexOutOfRangeException("Heap is empty!");
}
var min = items[1];
return min;
}
/// <summary>
/// Return the heap structure as an array
/// </summary>
/// <returns>Array with values sorted as in heap</returns>
public T[] ToArray()
{
var newArray = new T[count];
Array.Copy(items, 1, newArray, 0, count);
return newArray;
}
/// <summary>
/// Determines whether the heap contains specified value
/// </summary>
/// <param name="value">Value to locate in the heap</param>
/// <returns></returns>
public bool Contains(T value)
{
return items.Contains(value);
}
/// <summary>
/// Clears the heap
/// </summary>
public void Clear()
{
count = 0;
}
/// <summary>
/// Clears the heap.
/// </summary>
/// <param name="completeClear">Specifies if the underlying array should be cleared as well</param>
public void Clear(bool completeClear)
{
count = 0;
if (completeClear)
{
capacity = initialCapacity;
items = new T[initialCapacity];
}
}
/// <summary>
/// Returns an enumerator that iterates over the heap.
/// </summary>
/// <returns>Enumerator that iterates over the heap.</returns>
public IEnumerator<T> GetEnumerator()
{
for (var i = 0; i < count; i++)
{
yield return items[i + 1];
}
}
#endregion
#region Private Methods
/// <summary>
/// Bubble the specified element to its position
/// </summary>
/// <param name="index">Index of element to bubble</param>
private void BubbleDown(int index)
{
var temp = items[index];
int childIndex;
for (; 2 * index <= count; index = childIndex)
{
childIndex = 2 * index;
if (childIndex != Count && items[childIndex].CompareTo(items[childIndex + 1]) > 0)
{
childIndex++;
}
if (temp.CompareTo(items[childIndex]) > 0)
{
items[index] = items[childIndex];
}
else
{
break;
}
}
items[index] = temp;
}
/// <summary>
/// Implementation of IEnumerator.
/// </summary>
/// <returns>Enumerator over the array.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
#endregion
}
/// <summary>
/// Maximum value from the heap.
/// </summary>
public T Max
{
get
{
return this.items[this.count];
}
}
/// <summary>
/// Capacity of the heap.
/// </summary>
public int Capacity
{
get => this.capacity;
set
{
if (value > this.capacity)
{
Array.Resize(ref this.items, value);
this.capacity = value;
}
}
}
/// <summary>
/// Number of elements in the heap.
/// </summary>
public int Count { get => this.count; }
#endregion
#region Constructors
/// <summary>
/// Constructor for a binary heap data structure.
/// </summary>
public BinaryHeap()
{
this.capacity = 10;
this.initialCapacity = this.capacity;
this.items = new T[this.capacity];
}
/// <summary>
/// Constructor for a binary heap data structure.
/// </summary>
/// <param name="capacity">Initial capacity of the heap. Used for initial alocation of the array.</param>
public BinaryHeap(int capacity)
{
this.capacity = capacity;
this.initialCapacity = capacity;
this.items = new T[capacity];
}
#endregion
#region Public Methods
/// <summary>
/// Adds value to the queue.
/// </summary>
/// <param name="value">Value to be added.</param>
public void Add(T value)
{
if (this.count == this.Capacity - 1)
{
this.Capacity = 2 * this.Capacity;
}
var position = ++this.count;
for (; position > 1 && value.CompareTo(this.items[position / 2]) < 0; position /= 2)
{
this.items[position] = this.items[position / 2];
}
this.items[position] = value;
}
/// <summary>
/// Removes the item at the root. Throws exception if there are no items in the heap.
/// </summary>
/// <returns>Value removed.</returns>
public T Remove()
{
if (this.count == 0)
{
throw new IndexOutOfRangeException("Heap is empty!");
}
var min = this.items[1];
this.items[1] = this.items[this.count--];
this.BubbleDown(1);
return min;
}
/// <summary>
/// Peeks at the item at the root. Throws exception if there are no items in the heap.
/// </summary>
/// <returns></returns>
public T Peek()
{
if (this.count == 0)
{
throw new IndexOutOfRangeException("Heap is empty!");
}
var min = this.items[1];
return min;
}
/// <summary>
/// Return the heap structure as an array
/// </summary>
/// <returns>Array with values sorted as in heap</returns>
public T[] ToArray()
{
var newArray = new T[this.count];
Array.Copy(this.items, 1, newArray, 0, this.count);
return newArray;
}
/// <summary>
/// Determines whether the heap contains specified value
/// </summary>
/// <param name="value">Value to locate in the heap</param>
/// <returns></returns>
public bool Contains(T value)
{
return this.items.Contains(value);
}
/// <summary>
/// Clears the heap
/// </summary>
public void Clear()
{
this.count = 0;
}
/// <summary>
/// Clears the heap.
/// </summary>
/// <param name="completeClear">Specifies if the underlying array should be cleared as well</param>
public void Clear(bool completeClear)
{
this.count = 0;
if (completeClear)
{
this.capacity = this.initialCapacity;
this.items = new T[this.initialCapacity];
}
}
/// <summary>
/// Returns an enumerator that iterates over the heap.
/// </summary>
/// <returns>Enumerator that iterates over the heap.</returns>
public IEnumerator<T> GetEnumerator()
{
for (var i = 0; i < this.count; i++)
{
yield return this.items[i + 1];
}
}
#endregion
#region Private Methods
/// <summary>
/// Bubble the specified element to its position
/// </summary>
/// <param name="index">Index of element to bubble</param>
private void BubbleDown(int index)
{
var temp = this.items[index];
int childIndex;
for (; 2 * index <= this.count; index = childIndex)
{
childIndex = 2 * index;
if (childIndex != this.Count && this.items[childIndex].CompareTo(this.items[childIndex + 1]) > 0)
{
childIndex++;
}
if (temp.CompareTo(this.items[childIndex]) > 0)
{
this.items[index] = this.items[childIndex];
}
else
{
break;
}
}
this.items[index] = temp;
}
/// <summary>
/// Implementation of IEnumerator.
/// </summary>
/// <returns>Enumerator over the array.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
#endregion
}
@@ -0,0 +1,208 @@
using System.Linq;
using System.Threading;
namespace System.Collections.Generic;
public sealed class CircularBuffer<T> : IList<T>
{
private readonly T[] buffer;
private int head;
private int tail;
public int Count { get; private set; }
public bool IsReadOnly { get; } = false;
public T this[int index]
{
get
{
if (index > this.Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return this.buffer[(this.head + index) % this.buffer.Length];
}
set
{
if (index > this.Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
this.buffer[(this.head + index) % this.buffer.Length] = value;
}
}
public CircularBuffer(int capacity)
{
if (capacity < 1) throw new ArgumentException($"{nameof(capacity)} cannot be smaller than 1");
this.buffer = new T[capacity];
this.head = 0;
this.tail = 0;
}
public CircularBuffer(T[] buffer, int head, int tail)
{
if (buffer.Length == 0) throw new ArgumentException($"{nameof(buffer)} cannot be of length 0");
this.buffer = buffer;
this.head = head;
this.tail = tail;
this.Count = this.tail < this.head ?
this.tail + this.buffer.Length - this.head + 1 :
this.tail - this.head + 1;
}
public CircularBuffer(T[] buffer)
{
if (buffer.Length == 0) throw new ArgumentException($"{nameof(buffer)} cannot be of length 0");
this.buffer = buffer;
this.head = 0;
this.tail = this.buffer.Length - 1;
this.Count = this.buffer.Length;
}
public void Add(T item)
{
if (this.Count == this.buffer.Length)
{
this.buffer[this.tail] = item;
this.tail = (this.tail + 1) % this.buffer.Length;
this.head = (this.head + 1) % this.buffer.Length;
}
else
{
this.buffer[this.tail] = item;
this.tail = (this.tail + 1) % this.buffer.Length;
this.Count++;
}
}
public void Clear()
{
this.head = 0;
this.tail = 0;
this.Count = 0;
}
public void DeepClear()
{
this.Clear();
Array.Clear(this.buffer, 0, this.buffer.Length);
}
public bool Contains(T item)
{
var comparer = EqualityComparer<T>.Default;
foreach(var itItem in this)
{
if (comparer.Equals(itItem, item))
{
return true;
}
}
return false;
}
public bool Remove(T item)
{
var index = this.head;
var comparer = EqualityComparer<T>.Default;
for (var i = 0; i < this.Count; i++)
{
if (comparer.Equals(this.buffer[index % this.buffer.Length], item))
{
int nextIndex;
for (var j = 0; j < this.Count - i - 1; j++)
{
nextIndex = (index + 1) % this.buffer.Length;
this.buffer[index] = this.buffer[nextIndex];
index = nextIndex;
}
this.Count--;
this.tail = this.tail == 0 ? this.buffer.Length - 1 : this.tail - 1;
return true;
}
index = (index + 1) % this.buffer.Length;
}
return false;
}
public void CopyTo(T[] array, int arrayIndex)
{
foreach(var item in this)
{
array[arrayIndex++] = item;
}
}
public IEnumerator<T> GetEnumerator()
{
var index = this.head;
for (var i = 0; i < this.Count; i++)
{
yield return this.buffer[index];
index = (index + 1) % this.buffer.Length;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public int IndexOf(T item)
{
var comparer = EqualityComparer<T>.Default;
for (var i = 0; i < this.Count; i++)
{
if (comparer.Equals(this[i], item))
{
return i;
}
}
return -1;
}
public void Insert(int index, T item)
{
if (index < 0 || index > this.Count) throw new ArgumentOutOfRangeException(nameof(index));
if (this.Count == this.buffer.Length) throw new InvalidOperationException("Cannot insert into a full buffer.");
if (index == this.Count)
{
this.Add(item);
return;
}
for (var i = this.Count; i > index; i--)
{
this.buffer[(this.head + i) % this.buffer.Length] = this.buffer[(this.head + i - 1) % this.buffer.Length];
}
this.buffer[(this.head + index) % this.buffer.Length] = item;
this.tail = (this.tail + 1) % this.buffer.Length;
this.Count++;
}
public void RemoveAt(int index)
{
if (index < 0 || index >= this.Count) throw new ArgumentOutOfRangeException(nameof(index));
for (var i = index; i < this.Count - 1; i++)
{
this.buffer[(this.head + i) % this.buffer.Length] = this.buffer[(this.head + i + 1) % this.buffer.Length];
}
this.tail = (this.tail - 1 + this.buffer.Length) % this.buffer.Length;
this.Count--;
}
}
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More