mirror of
https://github.com/AlexMacocian/SystemExtensions.git
synced 2026-07-16 14:39:28 +00:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c918cf4a4 | |||
| dbdf5cfbe2 | |||
| 124ae942b5 | |||
| 5e5cdb3b47 | |||
| a4d0d5cab7 | |||
| 3b47d0bd0c | |||
| 4033f9d401 | |||
| c9f6541904 | |||
| 18cd59d414 | |||
| c63fd45e38 | |||
| 7ec573098b | |||
| af1dfd6751 | |||
| bae28b2552 | |||
| 818240fcea | |||
| 25eae4061b | |||
| 33584e8a14 | |||
| 7d06595307 | |||
| d4ef0dc34e | |||
| 0fc24b8307 | |||
| def0258695 | |||
| 535a2cedc0 | |||
| 2b23665145 | |||
| 3b942c2102 | |||
| 681bec01f2 | |||
| 552b72b1b9 | |||
| 00459fc59b | |||
| 84a9f8e8d6 | |||
| a8de6cd15d | |||
| ec343797b4 | |||
| 8a58a6e9a3 | |||
| 3131558017 | |||
| 6a541e07a3 | |||
| 791387fb29 | |||
| b7244b2bdb | |||
| fe049d4c00 | |||
| 0de73a967b | |||
| 8d7676bedf | |||
| 2ed3de439e | |||
| de08c0e3b0 | |||
| a25f2e5acb | |||
| 8e64309c2e | |||
| 24c3e37b5e | |||
| f6435b24ab | |||
| 9118c864bf | |||
| fb84036215 | |||
| f6fd9ec616 | |||
| 6a10fe91e3 | |||
| e890b21bb6 | |||
| e36e6605ff | |||
| 5e8e772d36 | |||
| e6a06915cd | |||
| 3c741ab969 | |||
| a46fde652d | |||
| ecc65a4302 | |||
| 7222528eed | |||
| 41b1b48f98 | |||
| 721b444dda | |||
| 1c7d3a2be2 | |||
| 0a30872781 | |||
| 7eed1ca466 | |||
| b6cc64fd51 | |||
| c8bfaffb42 | |||
| bf163251dd | |||
| c0b5734b71 | |||
| 2944d9887e | |||
| 57925151fa | |||
| d20e5bd308 | |||
| c9d1ba8773 | |||
| ce3cac9fa2 | |||
| a2d23ed716 | |||
| 7660893087 | |||
| 68ec8f983d |
+129
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
* amacocian@yahoo.com
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
-26
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-69
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-44
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-69
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-48
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-7
@@ -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>
|
||||
-14
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
-7
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-26
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-31
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-33
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-23
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -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);
|
||||
}
|
||||
}
|
||||
-141
@@ -1,141 +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;
|
||||
}
|
||||
|
||||
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,44 +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 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)
|
||||
{
|
||||
if (this.correlationVector is not null)
|
||||
{
|
||||
this.correlationVector = CorrelationVector.Extend(this.correlationVector.ToString());
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -6,9 +6,11 @@
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>1.1.1</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="5.0.0" />
|
||||
<PackageReference Include="Slim" Version="1.5.1" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.3.0" />
|
||||
<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
|
||||
+30
@@ -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>
|
||||
@@ -3,103 +3,102 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Encryption;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SystemExtensions.NetStandard.Security.Encryption;
|
||||
|
||||
namespace VaultO.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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Linq;
|
||||
using System.Security.Rng;
|
||||
|
||||
namespace SystemExtensions.NetStandard.Security.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class CryptoRngProviderTests
|
||||
{
|
||||
private CryptoRngProvider cryptoRngProvider;
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
this.cryptoRngProvider = new CryptoRngProvider();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetBytes_ShouldSetValues()
|
||||
{
|
||||
var bytes = new byte[100];
|
||||
|
||||
this.cryptoRngProvider.GetBytes(bytes);
|
||||
|
||||
bytes.All(b => b == 0).Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetNonZeroBytes_ShouldSetNonZeroValues()
|
||||
{
|
||||
var bytes = new byte[100];
|
||||
|
||||
this.cryptoRngProvider.GetNonZeroBytes(bytes);
|
||||
|
||||
bytes.All(b => b != 0).Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetBytes_ShouldReturnBytes()
|
||||
{
|
||||
var bytes = this.cryptoRngProvider.GetBytes(10);
|
||||
|
||||
bytes.Length.Should().Be(10);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetNonZeroBytes_ShouldReturnBytes()
|
||||
{
|
||||
var bytes = this.cryptoRngProvider.GetNonZeroBytes(10);
|
||||
|
||||
bytes.Length.Should().Be(10);
|
||||
}
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Security.Hashing;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SystemExtensions.NetStandard.Security.Tests;
|
||||
|
||||
[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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
[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 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 SaltNull_HashBytes_Throws_ArgumentNullException()
|
||||
{
|
||||
var action = new Func<Task<byte[]>>(() => this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, null, 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().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().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().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().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().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().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().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().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().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().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().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().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().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().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().ThrowAsync<InvalidOperationException>();
|
||||
}
|
||||
[TestMethod]
|
||||
public async Task HashBytes_ReturnsHashedBytes()
|
||||
{
|
||||
var hashedBytes = await this.rfc2898DeriveBytesPasswordHashingService.Hash(this.passwordBytes, this.saltBytes, DesiredHashLength, 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);
|
||||
|
||||
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(hashedBytes, this.passwordBytes, this.saltBytes, 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(hashedString, this.passwordString, this.saltString, 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(hashedBytes, this.incorrectPasswordBytes, this.saltBytes, 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(hashedString, this.incorrectPasswordString, this.saltString, 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(hashedBytes, this.passwordBytes, this.incorrectSaltBytes, 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(hashedString, this.passwordString, this.incorrectSaltString, 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(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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Security.Encryption;
|
||||
|
||||
namespace SystemExtensions.NetStandard.Security.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class SecureStringTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void SecureString_NewSecureString_ReturnsValue()
|
||||
{
|
||||
var str = new SecureString("hello");
|
||||
|
||||
str.Value.Should().Be("hello");
|
||||
}
|
||||
|
||||
[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;
|
||||
|
||||
ss.Should().Be(s);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SecureString_Equals_OtherSecureString()
|
||||
{
|
||||
var ss1 = new SecureString("Hello");
|
||||
var ss2 = new SecureString("Hello");
|
||||
|
||||
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);
|
||||
|
||||
(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);
|
||||
|
||||
(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);
|
||||
|
||||
(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);
|
||||
|
||||
(ss1 != str2).Should().Be(isDifferent);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SecureString_PlusOperator_SecureString()
|
||||
{
|
||||
var ss1 = new SecureString("Hello ");
|
||||
var ss2 = new SecureString("World");
|
||||
|
||||
(ss1 + ss2).Should().Be("Hello World");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SecureString_PlusOperator_String()
|
||||
{
|
||||
var ss1 = new SecureString("Hello ");
|
||||
var ss2 = "World";
|
||||
|
||||
(ss1 + ss2).Should().Be("Hello World");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SecureString_PlusOperator_Char()
|
||||
{
|
||||
var ss1 = new SecureString("Hello ");
|
||||
var c = '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");
|
||||
|
||||
ss1.Should().Be(ss2);
|
||||
}
|
||||
}
|
||||
@@ -2,47 +2,46 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Hashing;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SystemExtensions.NetStandard.Security.Hashing;
|
||||
|
||||
namespace VaultO.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>();
|
||||
}
|
||||
}
|
||||
|
||||
+9
-6
@@ -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>
|
||||
|
||||
@@ -1,215 +1,226 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using SystemExtensions.NetStandard.Security.Utilities;
|
||||
using System.Security.Utilities;
|
||||
|
||||
namespace SystemExtensions.NetStandard.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)
|
||||
{
|
||||
if (value is null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return await EncryptInternal(StringToBytes(key), StringToBytes(iv), value).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<Stream> Encrypt(byte[] key, byte[] iv, Stream value)
|
||||
{
|
||||
if (value is null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return 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;
|
||||
byte[] buffer = new byte[1024];
|
||||
using var ms = new MemoryStream();
|
||||
int 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 SystemExtensions.NetStandard.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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Security.Encryption;
|
||||
|
||||
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)
|
||||
{
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is string)
|
||||
{
|
||||
return this == (obj as string);
|
||||
}
|
||||
else if (obj is SecureString)
|
||||
{
|
||||
return this == (obj as SecureString);
|
||||
}
|
||||
else
|
||||
{
|
||||
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 SystemExtensions.NetStandard.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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Security.Hashing;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Security.Hashing;
|
||||
|
||||
public sealed class Rfc2898DeriveBytesPasswordHashingService : IPasswordHashingService
|
||||
{
|
||||
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}");
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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,60 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace SystemExtensions.NetStandard.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;
|
||||
byte[] buffer = new byte[1024];
|
||||
using var ms = new MemoryStream();
|
||||
int 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();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace System.Security.Rng;
|
||||
|
||||
public sealed class CryptoRngProvider : ICryptoRngProvider, IDisposable
|
||||
{
|
||||
private readonly RNGCryptoServiceProvider rngProvider;
|
||||
private bool disposedValue;
|
||||
|
||||
public CryptoRngProvider()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!this.disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.rngProvider.Dispose();
|
||||
}
|
||||
|
||||
this.disposedValue = true;
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace System.Security.Rng;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -7,9 +7,10 @@
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<RootNamespace>System</RootNamespace>
|
||||
<Version>1.0.0</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>
|
||||
@@ -19,4 +20,8 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,21 +1,22 @@
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace SystemExtensions.NetStandard.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,389 +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++;
|
||||
AVLNode<T> 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
|
||||
{
|
||||
AVLNode<T> 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
|
||||
{
|
||||
Queue<AVLNode<T>> 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)
|
||||
{
|
||||
AVLNode<T> 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)
|
||||
{
|
||||
Queue<AVLNode<T>> queue = new Queue<AVLNode<T>>();
|
||||
queue.Enqueue(root);
|
||||
while (queue.Count > 0)
|
||||
var currentNode = queue.Dequeue();
|
||||
if (currentNode.Left != null)
|
||||
{
|
||||
AVLNode<T> 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)
|
||||
{
|
||||
T[] 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)
|
||||
{
|
||||
int 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)
|
||||
{
|
||||
int height = 0;
|
||||
if (current != null)
|
||||
if (target.CompareTo(current.Value) == 0)
|
||||
{
|
||||
int l = GetHeight(current.Left);
|
||||
int r = GetHeight(current.Right);
|
||||
int m = Max(l, r);
|
||||
height = m + 1;
|
||||
return current;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
private int BalanceFactor(AVLNode<T> current)
|
||||
{
|
||||
int l = GetHeight(current.Left);
|
||||
int r = GetHeight(current.Right);
|
||||
int b_factor = l - r;
|
||||
return b_factor;
|
||||
}
|
||||
private AVLNode<T> RotateRR(AVLNode<T> parent)
|
||||
{
|
||||
AVLNode<T> pivot = parent.Right;
|
||||
parent.Right = pivot.Left;
|
||||
pivot.Left = parent;
|
||||
return pivot;
|
||||
}
|
||||
private AVLNode<T> RotateLL(AVLNode<T> parent)
|
||||
{
|
||||
AVLNode<T> pivot = parent.Left;
|
||||
parent.Left = pivot.Right;
|
||||
pivot.Right = parent;
|
||||
return pivot;
|
||||
}
|
||||
private AVLNode<T> RotateLR(AVLNode<T> parent)
|
||||
{
|
||||
AVLNode<T> pivot = parent.Left;
|
||||
parent.Left = RotateRR(pivot);
|
||||
return RotateLL(parent);
|
||||
}
|
||||
private AVLNode<T> RotateRL(AVLNode<T> parent)
|
||||
{
|
||||
AVLNode<T> pivot = parent.Right;
|
||||
parent.Right = RotateLL(pivot);
|
||||
return RotateRR(parent);
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
private IEnumerator<T> GetEnumerator(AVLNode<T> rootNode)
|
||||
{
|
||||
Queue<AVLNode<T>> queue = new Queue<AVLNode<T>>();
|
||||
queue.Enqueue(rootNode);
|
||||
|
||||
while (queue.Count > 0)
|
||||
else
|
||||
{
|
||||
AVLNode<T> 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;
|
||||
}
|
||||
int 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!");
|
||||
}
|
||||
T 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!");
|
||||
}
|
||||
T 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()
|
||||
{
|
||||
T[] 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 (int 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)
|
||||
{
|
||||
T 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
Reference in New Issue
Block a user