diff --git a/.editorconfig b/.editorconfig
index 326085a..e25eb42 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -7,270 +7,270 @@ root = true
#### Core EditorConfig Options ####
# Indentation and spacing
-indent_size = 4
-indent_style = space
-tab_width = 4
+indent_size = 4
+indent_style = space
+tab_width = 4
# New line preferences
-end_of_line = crlf
-insert_final_newline = false
+end_of_line = crlf
+insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
-dotnet_separate_import_directive_groups = false
-dotnet_sort_system_directives_first = true
-file_header_template = unset
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = true
+file_header_template = unset
# this. and Me. preferences
-dotnet_style_qualification_for_event = false:error
-dotnet_style_qualification_for_field = false
-dotnet_style_qualification_for_method = false:error
-dotnet_style_qualification_for_property = false:error
+dotnet_style_qualification_for_event = false:error
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false:error
+dotnet_style_qualification_for_property = false:error
# Language keywords vs BCL types preferences
-dotnet_style_predefined_type_for_locals_parameters_members = true
-dotnet_style_predefined_type_for_member_access = true
+dotnet_style_predefined_type_for_locals_parameters_members = true
+dotnet_style_predefined_type_for_member_access = true
# Parentheses preferences
-dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
-dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary
-dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
# Modifier preferences
-dotnet_style_require_accessibility_modifiers = for_non_interface_members
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
-dotnet_style_coalesce_expression = true:warning
-dotnet_style_collection_initializer = true:error
-dotnet_style_explicit_tuple_names = true:error
-dotnet_style_namespace_match_folder = true
-dotnet_style_null_propagation = true:warning
-dotnet_style_object_initializer = true:error
-dotnet_style_operator_placement_when_wrapping = beginning_of_line
-dotnet_style_prefer_auto_properties = true:warning
-dotnet_style_prefer_collection_expression = when_types_loosely_match
-dotnet_style_prefer_compound_assignment = true:error
-dotnet_style_prefer_conditional_expression_over_assignment = true:error
-dotnet_style_prefer_conditional_expression_over_return = true:error
-dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
-dotnet_style_prefer_inferred_anonymous_type_member_names = true:error
-dotnet_style_prefer_inferred_tuple_names = true:error
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
-dotnet_style_prefer_simplified_boolean_expressions = true:error
-dotnet_style_prefer_simplified_interpolation = true
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_collection_initializer = true:error
+dotnet_style_explicit_tuple_names = true:error
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true:warning
+dotnet_style_object_initializer = true:error
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_collection_expression = when_types_loosely_match
+dotnet_style_prefer_compound_assignment = true:error
+dotnet_style_prefer_conditional_expression_over_assignment = true:error
+dotnet_style_prefer_conditional_expression_over_return = true:error
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:error
+dotnet_style_prefer_inferred_tuple_names = true:error
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_simplified_boolean_expressions = true:error
+dotnet_style_prefer_simplified_interpolation = true
# Field preferences
-dotnet_style_readonly_field = true:warning
+dotnet_style_readonly_field = true:warning
# Parameter preferences
-dotnet_code_quality_unused_parameters = all:error
+dotnet_code_quality_unused_parameters = all:error
# Suppression preferences
-dotnet_remove_unnecessary_suppression_exclusions = none
+dotnet_remove_unnecessary_suppression_exclusions = none
# New line preferences
-dotnet_style_allow_multiple_blank_lines_experimental = false:error
-dotnet_style_allow_statement_immediately_after_block_experimental = false:warning
+dotnet_style_allow_multiple_blank_lines_experimental = false:error
+dotnet_style_allow_statement_immediately_after_block_experimental = false:warning
#### C# Coding Conventions ####
# var preferences
-csharp_style_var_elsewhere = true:silent
-csharp_style_var_for_built_in_types = true:silent
-csharp_style_var_when_type_is_apparent = true:silent
+csharp_style_var_elsewhere = true:silent
+csharp_style_var_for_built_in_types = true:silent
+csharp_style_var_when_type_is_apparent = true:silent
# Expression-bodied members
-csharp_style_expression_bodied_accessors = true:warning
-csharp_style_expression_bodied_constructors = true:warning
-csharp_style_expression_bodied_indexers = true:warning
-csharp_style_expression_bodied_lambdas = true:warning
-csharp_style_expression_bodied_local_functions = false:silent
-csharp_style_expression_bodied_methods = true:warning
-csharp_style_expression_bodied_operators = true:warning
-csharp_style_expression_bodied_properties = true:warning
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_constructors = true:warning
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_lambdas = true:warning
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = true:warning
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
# Pattern matching preferences
-csharp_style_pattern_matching_over_as_with_null_check = true:error
-csharp_style_pattern_matching_over_is_with_cast_check = true:error
-csharp_style_prefer_extended_property_pattern = true:suggestion
-csharp_style_prefer_not_pattern = true:error
-csharp_style_prefer_pattern_matching = true:error
-csharp_style_prefer_switch_expression = true:error
+csharp_style_pattern_matching_over_as_with_null_check = true:error
+csharp_style_pattern_matching_over_is_with_cast_check = true:error
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_prefer_not_pattern = true:error
+csharp_style_prefer_pattern_matching = true:error
+csharp_style_prefer_switch_expression = true:error
# Null-checking preferences
-csharp_style_conditional_delegate_call = true:warning
+csharp_style_conditional_delegate_call = true:warning
# Modifier preferences
-csharp_prefer_static_local_function = true:warning
-csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async
-csharp_style_prefer_readonly_struct = true:warning
-csharp_style_prefer_readonly_struct_member = true:suggestion
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async
+csharp_style_prefer_readonly_struct = true:warning
+csharp_style_prefer_readonly_struct_member = true:suggestion
# Code-block preferences
-csharp_prefer_braces = true:error
-csharp_prefer_simple_using_statement = true:warning
-csharp_style_namespace_declarations = file_scoped:warning
-csharp_style_prefer_method_group_conversion = true:warning
-csharp_style_prefer_primary_constructors = true:suggestion
-csharp_style_prefer_top_level_statements = false:silent
+csharp_prefer_braces = true:error
+csharp_prefer_simple_using_statement = true:warning
+csharp_style_namespace_declarations = file_scoped:warning
+csharp_style_prefer_method_group_conversion = true:warning
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_style_prefer_top_level_statements = false:silent
# Expression-level preferences
-csharp_prefer_simple_default_expression = true:error
-csharp_style_deconstructed_variable_declaration = true:warning
-csharp_style_implicit_object_creation_when_type_is_apparent = true:error
-csharp_style_inlined_variable_declaration = true:warning
-csharp_style_prefer_index_operator = true:error
-csharp_style_prefer_local_over_anonymous_function = true:error
-csharp_style_prefer_null_check_over_type_check = true:warning
-csharp_style_prefer_range_operator = true:error
-csharp_style_prefer_tuple_swap = true:error
-csharp_style_prefer_utf8_string_literals = true:suggestion
-csharp_style_throw_expression = true:warning
-csharp_style_unused_value_assignment_preference = discard_variable:warning
-csharp_style_unused_value_expression_statement_preference = discard_variable:warning
+csharp_prefer_simple_default_expression = true:error
+csharp_style_deconstructed_variable_declaration = true:warning
+csharp_style_implicit_object_creation_when_type_is_apparent = true:error
+csharp_style_inlined_variable_declaration = true:warning
+csharp_style_prefer_index_operator = true:error
+csharp_style_prefer_local_over_anonymous_function = true:error
+csharp_style_prefer_null_check_over_type_check = true:warning
+csharp_style_prefer_range_operator = true:error
+csharp_style_prefer_tuple_swap = true:error
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_throw_expression = true:warning
+csharp_style_unused_value_assignment_preference = discard_variable:warning
+csharp_style_unused_value_expression_statement_preference = discard_variable:warning
# 'using' directive preferences
-csharp_using_directive_placement = outside_namespace:warning
+csharp_using_directive_placement = outside_namespace:warning
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:warning
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false:warning
-csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning
-csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:error
-csharp_style_allow_embedded_statements_on_same_line_experimental = true:warning
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:error
+csharp_style_allow_embedded_statements_on_same_line_experimental = true:warning
#### C# Formatting Rules ####
# New line preferences
-csharp_new_line_before_catch = true
-csharp_new_line_before_else = true
-csharp_new_line_before_finally = true
-csharp_new_line_before_members_in_anonymous_types = true
-csharp_new_line_before_members_in_object_initializers = true
-csharp_new_line_before_open_brace = all
-csharp_new_line_between_query_expression_clauses = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
-csharp_indent_block_contents = true
-csharp_indent_braces = false
-csharp_indent_case_contents = true
-csharp_indent_case_contents_when_block = false
-csharp_indent_labels = one_less_than_current
-csharp_indent_switch_labels = true
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
# Space preferences
-csharp_space_after_cast = false
-csharp_space_after_colon_in_inheritance_clause = true
-csharp_space_after_comma = true
-csharp_space_after_dot = false
-csharp_space_after_keywords_in_control_flow_statements = false
-csharp_space_after_semicolon_in_for_statement = true
-csharp_space_around_binary_operators = before_and_after
-csharp_space_around_declaration_statements = ignore
-csharp_space_before_colon_in_inheritance_clause = true
-csharp_space_before_comma = false
-csharp_space_before_dot = false
-csharp_space_before_open_square_brackets = false
-csharp_space_before_semicolon_in_for_statement = false
-csharp_space_between_empty_square_brackets = false
-csharp_space_between_method_call_empty_parameter_list_parentheses = false
-csharp_space_between_method_call_name_and_opening_parenthesis = false
-csharp_space_between_method_call_parameter_list_parentheses = false
-csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
-csharp_space_between_method_declaration_name_and_open_parenthesis = false
-csharp_space_between_method_declaration_parameter_list_parentheses = false
-csharp_space_between_parentheses = false
-csharp_space_between_square_brackets = false
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = false
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = ignore
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
# Wrapping preferences
-csharp_preserve_single_line_blocks = true
-csharp_preserve_single_line_statements = false
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = false
#### Naming styles ####
# Naming rules
-dotnet_naming_rule.interface_should_be_begins_with_i.severity = error
-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.interface_should_be_begins_with_i.severity = error
+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 = error
-dotnet_naming_rule.types_should_be_pascal_case.symbols = types
-dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+dotnet_naming_rule.types_should_be_pascal_case.severity = error
+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 = warning
-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
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
+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
-dotnet_naming_rule.static_field_should_be_pascal_case.severity = warning
-dotnet_naming_rule.static_field_should_be_pascal_case.symbols = static_field
-dotnet_naming_rule.static_field_should_be_pascal_case.style = pascal_case
+dotnet_naming_rule.static_field_should_be_pascal_case.severity = warning
+dotnet_naming_rule.static_field_should_be_pascal_case.symbols = static_field
+dotnet_naming_rule.static_field_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.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.static_field.applicable_kinds = field
-dotnet_naming_symbols.static_field.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.static_field.required_modifiers = static
+dotnet_naming_symbols.static_field.applicable_kinds = field
+dotnet_naming_symbols.static_field.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.static_field.required_modifiers = static
-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.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 =
+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.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_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.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
[*.{cs,vb}]
-dotnet_style_operator_placement_when_wrapping = beginning_of_line
-tab_width = 4
-indent_size = 4
-end_of_line = crlf
-dotnet_style_coalesce_expression = true:warning
-dotnet_style_null_propagation = true:warning
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
-dotnet_style_prefer_auto_properties = true:warning
-dotnet_style_object_initializer = true:error
-dotnet_style_collection_initializer = true:error
-dotnet_style_prefer_simplified_boolean_expressions = true:error
-dotnet_style_prefer_conditional_expression_over_assignment = true:error
-dotnet_style_explicit_tuple_names = true:error
-dotnet_style_prefer_conditional_expression_over_return = true:error
-dotnet_style_prefer_inferred_tuple_names = true:error
-dotnet_style_prefer_inferred_anonymous_type_member_names = true:error
-dotnet_style_prefer_compound_assignment = true:error
-dotnet_style_prefer_simplified_interpolation = true:suggestion
-dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
-dotnet_style_namespace_match_folder = true:suggestion
-dotnet_style_readonly_field = true:warning
-dotnet_style_predefined_type_for_member_access = true:silent
-dotnet_style_predefined_type_for_locals_parameters_members = true:silent
-dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
-dotnet_style_allow_multiple_blank_lines_experimental = false: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:warning
+dotnet_style_null_propagation = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_object_initializer = true:error
+dotnet_style_collection_initializer = true:error
+dotnet_style_prefer_simplified_boolean_expressions = true:error
+dotnet_style_prefer_conditional_expression_over_assignment = true:error
+dotnet_style_explicit_tuple_names = true:error
+dotnet_style_prefer_conditional_expression_over_return = true:error
+dotnet_style_prefer_inferred_tuple_names = true:error
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:error
+dotnet_style_prefer_compound_assignment = true:error
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_readonly_field = true:warning
+dotnet_style_predefined_type_for_member_access = true:silent
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+dotnet_style_allow_multiple_blank_lines_experimental = false:error
dotnet_style_allow_statement_immediately_after_block_experimental = false:warning
-dotnet_code_quality_unused_parameters = all:error
-dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
-dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
-dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
-dotnet_style_qualification_for_field = false:silent
-dotnet_style_qualification_for_property = false:error
-dotnet_style_qualification_for_method = false:error
-dotnet_style_qualification_for_event = false:error
\ No newline at end of file
+dotnet_code_quality_unused_parameters = all:error
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_property = false:error
+dotnet_style_qualification_for_method = false:error
+dotnet_style_qualification_for_event = false:error
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index fd126ee..446b951 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,4 +4,3 @@ updates:
directory: "/"
schedule:
interval: "weekly"
-
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 0473985..aff294f 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -1,9 +1,10 @@
-# This workflow will build a .NET project
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
-
name: .NET
+permissions:
+ contents: read
+
on:
+ workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
@@ -11,25 +12,51 @@ on:
jobs:
build:
-
- runs-on: ubuntu-latest
+ name: Build and analyze
+ runs-on: windows-latest
steps:
- - uses: actions/checkout@v4
-
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: 9.0.x
-
- - name: Delete nuget*.config files
- run: rm -f nuget*.config
-
- - name: Restore dependencies
- run: dotnet restore
-
- - name: Build
- run: dotnet build --no-restore
-
- - name: Test
- run: dotnet test --no-build --verbosity normal
+ - name: Set up JDK
+ uses: actions/setup-java@v4.4.0
+ with:
+ java-version: 17
+ distribution: 'zulu'
+
+ - name: Checkout
+ uses: actions/checkout@v4.2.1
+ with:
+ fetch-depth: 0
+
+ - name: 🛠 Cache SonarQube Cloud packages
+ uses: actions/cache@v4.2.3
+ with:
+ path: ~\sonar\cache
+ key: ${{ runner.os }}-sonar
+ restore-keys: ${{ runner.os }}-sonar
+
+ - name: 🛠 Cache SonarQube Cloud scanner
+ id: cache-sonar-scanner
+ uses: actions/cache@v4.2.3
+ with:
+ path: .\.sonar\scanner
+ key: ${{ runner.os }}-sonar-scanner
+ restore-keys: ${{ runner.os }}-sonar-scanner
+
+ - name: 🛠 Install SonarQube Cloud scanner
+ if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
+ shell: powershell
+ run: |
+ New-Item -Path .\.sonar\scanner -ItemType Directory
+ dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
+
+ - name: 🔍 Restore, 🛠 Build and 🧪 Test with ☁️ SonarCloud / Qube project - ${{ vars.SONAR_PROJECT_NAME }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ shell: powershell
+ run: |
+ dotnet tool install --global dotnet-coverage
+ .\.sonar\scanner\dotnet-sonarscanner begin /k:"astar-development_${{ github.event.repository.name }}" /o:"astar-development" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml /d:sonar.scanner.scanAll=false /d:sonar.scanner.skipJreProvisioning=true
+ dotnet build --configuration Release
+ dotnet-coverage collect 'dotnet test --filter "FullyQualifiedName!~Tests.EndToEnd"' -f xml -o 'coverage.xml'
+ .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..1f8c366
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,32 @@
+name: 🚀 Publish NuGet Package
+
+on:
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+
+ steps:
+ - name: 🧾 Checkout code
+ uses: actions/checkout@v4
+
+ - name: 🛠 Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+
+ - name: 🔍 Restore dependencies
+ run: dotnet restore
+
+ - name: 🛠 Build solution
+ run: dotnet build --configuration Release
+
+ - name: 📦 Pack NuGet package for ${{ github.event.repository.name }}
+ run: dotnet pack ./src/**/*.csproj --configuration Release --output ./nupkg
+
+ - name: 🚀 Publish to NuGet.org
+ run: dotnet nuget push ./nupkg/*.nupkg --skip-duplicate --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NuGet_API_Key }} #
+
diff --git a/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/.gitignore b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/.gitignore
new file mode 100644
index 0000000..85b4678
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/modules.xml
+/.idea.AStar.Dev.Api.Usage.Sdk.iml
+/contentModel.xml
+/projectSettingsUpdater.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/.name b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/.name
new file mode 100644
index 0000000..c704c56
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/.name
@@ -0,0 +1 @@
+AStar.Dev.Api.Usage.Sdk
\ No newline at end of file
diff --git a/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/encodings.xml b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/indexLayout.xml b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..8d66637
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/misc.xml b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/misc.xml
new file mode 100644
index 0000000..90dee70
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+ {}
+
\ No newline at end of file
diff --git a/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/vcs.xml b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/vcs.xml
new file mode 100644
index 0000000..e493e8c
--- /dev/null
+++ b/.idea/.idea.AStar.Dev.Api.Usage.Sdk/.idea/vcs.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AStar.Dev.Api.Usage.Sdk.slnx b/AStar.Dev.Api.Usage.Sdk.slnx
new file mode 100644
index 0000000..20e34ca
--- /dev/null
+++ b/AStar.Dev.Api.Usage.Sdk.slnx
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AStar.Dev.Example.sln.sln b/AStar.Dev.Example.sln.sln
deleted file mode 100644
index be68fef..0000000
--- a/AStar.Dev.Example.sln.sln
+++ /dev/null
@@ -1,57 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31903.59
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ADD5430F-CD80-42C7-80DA-90048E210EE7}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{73794993-9898-4968-AF19-C3E7450C94E4}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.Example.ClassLib", "src\AStar.Dev.Example.ClassLib\AStar.Dev.Example.ClassLib.csproj", "{A9C19332-40FE-4E24-A890-405D46CD72A5}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "end-to-end", "end-to-end", "{F1C7FB9E-2F0F-41C9-822A-7320339193CA}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integration", "integration", "{D724595D-C6BC-4F31-9D2A-4F4707436F10}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit", "unit", "{10DD984D-6788-4E04-A89C-3270006F5C56}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.Example.ClassLib.Tests.Unit", "test\unit\AStar.Dev.Example.ClassLib.Tests.Unit\AStar.Dev.Example.ClassLib.Tests.Unit.csproj", "{1D7D41F6-3866-4C00-A1BA-1675227FA9FA}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E1CEEE40-22D0-4F7B-AB2B-A308F8DE6A54}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- .gitignore = .gitignore
- build-and-test.ps1 = build-and-test.ps1
- CodeMaid.config = CodeMaid.config
- LICENSE = LICENSE
- nuget.ci.config = nuget.ci.config
- nuget.config = nuget.config
- README.md = README.md
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {A9C19332-40FE-4E24-A890-405D46CD72A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A9C19332-40FE-4E24-A890-405D46CD72A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A9C19332-40FE-4E24-A890-405D46CD72A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A9C19332-40FE-4E24-A890-405D46CD72A5}.Release|Any CPU.Build.0 = Release|Any CPU
- {1D7D41F6-3866-4C00-A1BA-1675227FA9FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1D7D41F6-3866-4C00-A1BA-1675227FA9FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1D7D41F6-3866-4C00-A1BA-1675227FA9FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1D7D41F6-3866-4C00-A1BA-1675227FA9FA}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {A9C19332-40FE-4E24-A890-405D46CD72A5} = {ADD5430F-CD80-42C7-80DA-90048E210EE7}
- {F1C7FB9E-2F0F-41C9-822A-7320339193CA} = {73794993-9898-4968-AF19-C3E7450C94E4}
- {D724595D-C6BC-4F31-9D2A-4F4707436F10} = {73794993-9898-4968-AF19-C3E7450C94E4}
- {10DD984D-6788-4E04-A89C-3270006F5C56} = {73794993-9898-4968-AF19-C3E7450C94E4}
- {1D7D41F6-3866-4C00-A1BA-1675227FA9FA} = {10DD984D-6788-4E04-A89C-3270006F5C56}
- EndGlobalSection
-EndGlobal
diff --git a/CodeMaid.config b/CodeMaid.config
deleted file mode 100644
index 54f2ebe..0000000
--- a/CodeMaid.config
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- <?xml version="1.0" encoding="utf-16"?>
- <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema">
- <string>ReSharper disable </string>
- <string>ReSharper enable </string>
- </ArrayOfString>
-
-
-
- True
-
-
- 1
-
-
- False
-
-
- True
-
-
- True
-
-
- 1
-
-
- 2
-
-
- True
-
-
- False
-
-
- False
-
-
- False
-
-
- False
-
-
- False
-
-
- False
-
-
- False
-
-
- False
-
-
- True
-
-
- 1
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/Readme.md
similarity index 100%
rename from README.md
rename to Readme.md
diff --git a/astar.ico b/astar.ico
new file mode 100644
index 0000000..38b6709
Binary files /dev/null and b/astar.ico differ
diff --git a/astar.png b/astar.png
new file mode 100644
index 0000000..74b197e
Binary files /dev/null and b/astar.png differ
diff --git a/nuget.ci.config b/nuget.ci.config
deleted file mode 100644
index aa5beec..0000000
--- a/nuget.ci.config
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/nuget.config b/nuget.config
deleted file mode 100644
index 782724b..0000000
--- a/nuget.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/src/AStar.Dev.Api.Usage.Sdk/AStar.Dev.Api.Usage.Sdk.csproj b/src/AStar.Dev.Api.Usage.Sdk/AStar.Dev.Api.Usage.Sdk.csproj
new file mode 100644
index 0000000..c6f03a9
--- /dev/null
+++ b/src/AStar.Dev.Api.Usage.Sdk/AStar.Dev.Api.Usage.Sdk.csproj
@@ -0,0 +1,69 @@
+
+
+
+ net9.0
+ enable
+ enable
+ latest-recommended
+ True
+
+
+
+ AStar Developement, Jason Barden
+ AStar Development
+ AStar Developement, 2025
+ A package to help consume the API Usage endpoint without duplicating code.
+ $(AssemblyName).xml
+ true
+ true
+ true
+ true
+ astar.png
+ AStar.Dev.Api.Usage.Sdk
+ MIT
+ https://github.com/astar-development/astar-dev-api-usage-sdk
+ Readme.md
+ Initial creation.
+ true
+ AStar
+ true
+ git
+ https://github.com/astar-development/astar-dev-api-usage-sdk.git
+ snupkg
+ AStar Dev Api Usage Sdk
+ 0.1.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ 1701;1702;
+
+
+
+ True
+ 1701;1702;
+
+
+
diff --git a/src/AStar.Dev.Api.Usage.Sdk/AStar.Dev.Api.Usage.Sdk.xml b/src/AStar.Dev.Api.Usage.Sdk/AStar.Dev.Api.Usage.Sdk.xml
new file mode 100644
index 0000000..34e5179
--- /dev/null
+++ b/src/AStar.Dev.Api.Usage.Sdk/AStar.Dev.Api.Usage.Sdk.xml
@@ -0,0 +1,164 @@
+
+
+
+ AStar.Dev.Api.Usage.Sdk
+
+
+
+
+ Represents the configuration settings for API usage, including connection and authentication details.
+
+
+
+
+ Specifies the name of the configuration section used to load settings for the
+
+ class.
+ This property provides a consistent identifier for the configuration section within
+ application configuration files, such as appsettings.json.
+
+
+
+
+ Specifies the host name used for connecting to the API in the
+
+ class.
+ This property represents the endpoint or server address where the API service is hosted.
+
+
+
+
+ Represents the username required for authentication within the
+
+ class.
+ This property is essential for establishing a connection to the API or associated services.
+
+
+
+
+ Represents the password used for authenticating with the API. This property must be provided
+ as part of the configuration settings to ensure secure access to the API.
+
+
+
+
+ Defines the name of the queue used for messaging or task distribution in the API usage configuration.
+ This property is required to identify the specific queue for sending and receiving messages
+ within the system's messaging infrastructure.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The
+
+ interface contains additional information for logging the API Usage.
+
+
+
+
+ Gets the name of the Endpoint.
+
+
+
+
+ Gets the HTTP Method type of the Endpoint.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AStar.Dev.Api.Usage.Sdk/ApiUsageConfiguration.cs b/src/AStar.Dev.Api.Usage.Sdk/ApiUsageConfiguration.cs
new file mode 100644
index 0000000..5e47f2a
--- /dev/null
+++ b/src/AStar.Dev.Api.Usage.Sdk/ApiUsageConfiguration.cs
@@ -0,0 +1,39 @@
+namespace AStar.Dev.Api.Usage.Sdk;
+
+///
+/// Represents the configuration settings for API usage, including connection and authentication details.
+///
+public sealed class ApiUsageConfiguration
+{
+ ///
+ /// Specifies the name of the configuration section used to load settings for the class.
+ /// This property provides a consistent identifier for the configuration section within
+ /// application configuration files, such as appsettings.json.
+ ///
+ public static string ConfigurationSectionName => "ApiUsageConfiguration";
+
+ ///
+ /// Specifies the host name used for connecting to the API in the class.
+ /// This property represents the endpoint or server address where the API service is hosted.
+ ///
+ public required string HostName { get; init; }
+
+ ///
+ /// Represents the username required for authentication within the class.
+ /// This property is essential for establishing a connection to the API or associated services.
+ ///
+ public required string UserName { get; init; }
+
+ ///
+ /// Represents the password used for authenticating with the API. This property must be provided
+ /// as part of the configuration settings to ensure secure access to the API.
+ ///
+ public required string Password { get; init; }
+
+ ///
+ /// Defines the name of the queue used for messaging or task distribution in the API usage configuration.
+ /// This property is required to identify the specific queue for sending and receiving messages
+ /// within the system's messaging infrastructure.
+ ///
+ public required string QueueName { get; init; }
+}
\ No newline at end of file
diff --git a/src/AStar.Dev.Api.Usage.Sdk/ApiUsageEvent.cs b/src/AStar.Dev.Api.Usage.Sdk/ApiUsageEvent.cs
new file mode 100644
index 0000000..55e05c0
--- /dev/null
+++ b/src/AStar.Dev.Api.Usage.Sdk/ApiUsageEvent.cs
@@ -0,0 +1,19 @@
+namespace AStar.Dev.Api.Usage.Sdk;
+
+///
+///
+///
+///
+///
+///
+///
+public record ApiUsageEvent(string ApiName, string ApiEndpoint, string HttpMethod, double ElapsedMilliseconds, int StatusCode)
+{
+ ///
+ ///
+ public Guid Id { get; init; } = Guid.CreateVersion7();
+
+ ///
+ ///
+ public DateTime Timestamp { get; init; } = DateTime.UtcNow;
+}
\ No newline at end of file
diff --git a/src/AStar.Dev.Api.Usage.Sdk/Metrics/IEndpointName.cs b/src/AStar.Dev.Api.Usage.Sdk/Metrics/IEndpointName.cs
new file mode 100644
index 0000000..66874de
--- /dev/null
+++ b/src/AStar.Dev.Api.Usage.Sdk/Metrics/IEndpointName.cs
@@ -0,0 +1,17 @@
+namespace AStar.Dev.Api.Usage.Sdk.Metrics;
+
+///
+/// The interface contains additional information for logging the API Usage.
+///
+public interface IEndpointName
+{
+ ///
+ /// Gets the name of the Endpoint.
+ ///
+ string Name { get; }
+
+ ///
+ /// Gets the HTTP Method type of the Endpoint.
+ ///
+ string HttpMethod { get; }
+}
\ No newline at end of file
diff --git a/src/AStar.Dev.Api.Usage.Sdk/Metrics/UsageMetricHandler.cs b/src/AStar.Dev.Api.Usage.Sdk/Metrics/UsageMetricHandler.cs
new file mode 100644
index 0000000..a7c9580
--- /dev/null
+++ b/src/AStar.Dev.Api.Usage.Sdk/Metrics/UsageMetricHandler.cs
@@ -0,0 +1,59 @@
+using System.Diagnostics;
+using AStar.Dev.Logging.Extensions;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+
+namespace AStar.Dev.Api.Usage.Sdk.Metrics;
+
+///
+///
+///
+///
+///
+public sealed class UsageMetricHandler(RequestDelegate next, ILogger logger, Send send)
+{
+ ///
+ ///
+ ///
+ public async Task InvokeAsync(HttpContext context)
+ {
+ try
+ {
+ var (httpRequest, httpMethod, apiEndpoint, apiName) = (context.Request, context.Request.Method, context.Request.Path, context.Request.Host.Host);
+
+ apiName = UpdateApiNameIfRequired(apiName);
+
+ var startTimestamp = Stopwatch.GetTimestamp();
+
+ // AStar.Dev.Api.Usage.Sdk.Metrics - feels pointless
+ LogMessage.Information(logger, "AStar.Dev.Api.Usage.Sdk.Metrics", httpRequest.Path, httpMethod, apiEndpoint, apiName);
+
+ await next(context);
+
+ var endTimestamp = Stopwatch.GetTimestamp();
+ var diff = Stopwatch.GetElapsedTime(startTimestamp, endTimestamp);
+
+ await send.SendUsageEventAsync(new(apiName, apiEndpoint, httpMethod, diff.TotalMilliseconds, context.Response.StatusCode), CancellationToken.None);
+ }
+ catch(Exception ex)
+ {
+ // nameof(UsageMetricHandler) - feels pointless
+ LogMessage.LogException(logger, nameof(UsageMetricHandler), ex.GetType().Name, ex.GetBaseException().Message, ex.StackTrace ?? string.Empty);
+ }
+ }
+
+ private string UpdateApiNameIfRequired(string apiName)
+ {
+ if(apiName != "host.docker.internal")
+ {
+ return apiName;
+ }
+
+ LogMessage.Debug(logger, "Need to revisit this to see if it is required",
+ "Updating host.docker.internal - doubt this ever happens as 99.999999% sure this is a long gone requirement. I'm just not in the mood to dig now...");
+
+ apiName = "astar.dev.images.api";
+
+ return apiName;
+ }
+}
\ No newline at end of file
diff --git a/src/AStar.Dev.Api.Usage.Sdk/Metrics/UsageMetricHandlerExtensions.cs b/src/AStar.Dev.Api.Usage.Sdk/Metrics/UsageMetricHandlerExtensions.cs
new file mode 100644
index 0000000..c2ccfdc
--- /dev/null
+++ b/src/AStar.Dev.Api.Usage.Sdk/Metrics/UsageMetricHandlerExtensions.cs
@@ -0,0 +1,15 @@
+using Microsoft.AspNetCore.Builder;
+
+namespace AStar.Dev.Api.Usage.Sdk.Metrics;
+
+///
+///
+public static class UsageMetricHandlerExtensions
+{
+ ///
+ ///
+ ///
+ ///
+ public static void UseMetrics(this IApplicationBuilder builder)
+ => builder.UseMiddleware();
+}
\ No newline at end of file
diff --git a/src/AStar.Dev.Api.Usage.Sdk/Send.cs b/src/AStar.Dev.Api.Usage.Sdk/Send.cs
new file mode 100644
index 0000000..501d22f
--- /dev/null
+++ b/src/AStar.Dev.Api.Usage.Sdk/Send.cs
@@ -0,0 +1,38 @@
+using System.Text;
+using AStar.Dev.Logging.Extensions;
+using AStar.Dev.Utilities;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using RabbitMQ.Client;
+
+namespace AStar.Dev.Api.Usage.Sdk;
+
+///
+///
+public sealed class Send(IConnection connection, ILogger logger, IOptions usageConfiguration)
+{
+ ///
+ ///
+ ///
+ ///
+ public async Task SendUsageEventAsync(ApiUsageEvent usageEvent, CancellationToken cancellationToken)
+ {
+ try
+ {
+ LogMessage.Debug(logger, "Send", "In Send > SendUsageEventAsync");
+
+ var config = usageConfiguration.Value;
+ await using var channel = await connection.CreateChannelAsync(cancellationToken: cancellationToken);
+
+ _ = await channel.QueueDeclareAsync(config.QueueName, true, false, false, cancellationToken: cancellationToken);
+
+ var body = Encoding.UTF8.GetBytes(usageEvent.ToJson());
+
+ await channel.BasicPublishAsync(string.Empty, config.QueueName, body, cancellationToken);
+ }
+ catch(Exception ex)
+ {
+ LogMessage.LogException(logger, nameof(Send), ex.GetType().Name, ex.GetBaseException().Message, ex.StackTrace ?? string.Empty);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AStar.Dev.Api.Usage.Sdk/ServiceCollectionExtensions.cs b/src/AStar.Dev.Api.Usage.Sdk/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..4b1b598
--- /dev/null
+++ b/src/AStar.Dev.Api.Usage.Sdk/ServiceCollectionExtensions.cs
@@ -0,0 +1,37 @@
+using System.Reflection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AStar.Dev.Api.Usage.Sdk;
+
+///
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+
+ // ReSharper disable once UnusedParameter.Global
+#pragma warning disable IDE0060
+ public static IServiceCollection AddUsageServices(this IServiceCollection services, ConfigurationManager configurationManager, Assembly executingAssembly)
+#pragma warning restore IDE0060
+ {
+ // services.AddMediatR(cfg =>
+ // {
+ // cfg.RegisterServicesFromAssembly(executingAssembly)
+ // .AddOpenBehavior(typeof(UsageMetricHandler<,>));
+ // });
+
+ _ = services.AddSingleton();
+
+ _ = services
+ .AddOptions()
+ .Bind(configurationManager.GetSection(ApiUsageConfiguration.ConfigurationSectionName));
+
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/src/AStar.Dev.Example.ClassLib/AStar.Dev.Example.ClassLib.csproj b/src/AStar.Dev.Example.ClassLib/AStar.Dev.Example.ClassLib.csproj
deleted file mode 100644
index 125f4c9..0000000
--- a/src/AStar.Dev.Example.ClassLib/AStar.Dev.Example.ClassLib.csproj
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
- net9.0
- enable
- enable
-
-
-
diff --git a/src/AStar.Dev.Example.ClassLib/Class1.cs b/src/AStar.Dev.Example.ClassLib/Class1.cs
deleted file mode 100644
index 27e9361..0000000
--- a/src/AStar.Dev.Example.ClassLib/Class1.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace AStar.Dev.Example.ClassLib;
-
-public class Class1
-{
-
-}
diff --git a/test/AStar.Dev.Api.Usage.Sdk.Tests.Unit/AStar.Dev.Api.Usage.Sdk.Tests.Unit.csproj b/test/AStar.Dev.Api.Usage.Sdk.Tests.Unit/AStar.Dev.Api.Usage.Sdk.Tests.Unit.csproj
new file mode 100644
index 0000000..f3f6c4e
--- /dev/null
+++ b/test/AStar.Dev.Api.Usage.Sdk.Tests.Unit/AStar.Dev.Api.Usage.Sdk.Tests.Unit.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net9.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/AStar.Dev.Api.Usage.Sdk.Tests.Unit/ApiUsageConfigurationShould.ContainTheExpectedProperties.approved.txt b/test/AStar.Dev.Api.Usage.Sdk.Tests.Unit/ApiUsageConfigurationShould.ContainTheExpectedProperties.approved.txt
new file mode 100644
index 0000000..4716220
--- /dev/null
+++ b/test/AStar.Dev.Api.Usage.Sdk.Tests.Unit/ApiUsageConfigurationShould.ContainTheExpectedProperties.approved.txt
@@ -0,0 +1,6 @@
+{
+ "hostName": "Mock host name",
+ "userName": "Mock User name",
+ "password": null,
+ "queueName": "Mock Queue name"
+}
\ No newline at end of file
diff --git a/test/AStar.Dev.Api.Usage.Sdk.Tests.Unit/ApiUsageConfigurationShould.cs b/test/AStar.Dev.Api.Usage.Sdk.Tests.Unit/ApiUsageConfigurationShould.cs
new file mode 100644
index 0000000..c4e4833
--- /dev/null
+++ b/test/AStar.Dev.Api.Usage.Sdk.Tests.Unit/ApiUsageConfigurationShould.cs
@@ -0,0 +1,14 @@
+using AStar.Dev.Utilities;
+using JetBrains.Annotations;
+
+namespace AStar.Dev.Api.Usage.Sdk.Tests.Unit;
+
+[TestSubject(typeof(ApiUsageConfiguration))]
+public class ApiUsageConfigurationShould
+{
+ [Fact]
+ public void ContainTheExpectedProperties()
+ => new ApiUsageConfiguration { HostName = "Mock host name", Password = null!, QueueName = "Mock Queue name", UserName = "Mock User name" }
+ .ToJson()
+ .ShouldMatchApproved();
+}
\ No newline at end of file
diff --git a/test/unit/AStar.Dev.Example.ClassLib.Tests.Unit/AStar.Dev.Example.ClassLib.Tests.Unit.csproj b/test/unit/AStar.Dev.Example.ClassLib.Tests.Unit/AStar.Dev.Example.ClassLib.Tests.Unit.csproj
deleted file mode 100644
index 6fbda4a..0000000
--- a/test/unit/AStar.Dev.Example.ClassLib.Tests.Unit/AStar.Dev.Example.ClassLib.Tests.Unit.csproj
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
- net9.0
- enable
- enable
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/unit/AStar.Dev.Example.ClassLib.Tests.Unit/UnitTest1.cs b/test/unit/AStar.Dev.Example.ClassLib.Tests.Unit/UnitTest1.cs
deleted file mode 100644
index 3928e91..0000000
--- a/test/unit/AStar.Dev.Example.ClassLib.Tests.Unit/UnitTest1.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace AStar.Dev.Example.ClassLib.Tests.Unit;
-
-public class UnitTest1
-{
- [Fact]
- public void Test1()
- {
-
- }
-}