From 462394cbc0023159444c363892d6125894b82112 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Mon, 8 Apr 2024 11:21:44 +0200 Subject: [PATCH 01/12] update csproj to .net 8 and Orleans 8 --- src/Example/eShopBySingleTeam/TeamA/Apis/Apis.csproj | 8 ++++---- .../TeamA/BasketService/BasketService.csproj | 6 +++--- .../TeamA/CatalogService/CatalogService.csproj | 6 +++--- .../eShopBySingleTeam/TeamA/Contracts/Contracts.csproj | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Apis.csproj b/src/Example/eShopBySingleTeam/TeamA/Apis/Apis.csproj index 3175bbe..7582f7b 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/Apis.csproj +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Apis.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable @@ -19,9 +19,9 @@ - - - + + + diff --git a/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketService.csproj b/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketService.csproj index c09471f..f66e3a1 100644 --- a/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketService.csproj +++ b/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketService.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable @@ -14,8 +14,8 @@ - - + + diff --git a/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogService.csproj b/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogService.csproj index c09471f..f66e3a1 100644 --- a/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogService.csproj +++ b/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogService.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable @@ -14,8 +14,8 @@ - - + + diff --git a/src/Example/eShopBySingleTeam/TeamA/Contracts/Contracts.csproj b/src/Example/eShopBySingleTeam/TeamA/Contracts/Contracts.csproj index 9861806..bce276a 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Contracts/Contracts.csproj +++ b/src/Example/eShopBySingleTeam/TeamA/Contracts/Contracts.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable @@ -14,7 +14,7 @@ - + From b5eec7bdabb2cf66e5d9d78e09da5f33531645a7 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Mon, 8 Apr 2024 11:22:50 +0200 Subject: [PATCH 02/12] update editorconfig to version 20240405 --- .editorconfig | 468 +++++++++++++++++++++++--------------------------- 1 file changed, 219 insertions(+), 249 deletions(-) diff --git a/.editorconfig b/.editorconfig index 4d5fb95..4ff01a2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,277 +1,249 @@ -# Version 20221025 +# Version 20240405 - C# 12 +# Origin: see https://github.com/Applicita/Modern.CSharp.Templates/blob/main/Editorconfig.md + # This file does not inherit .editorconfig settings from higher directories - where possible, place it at the root of the repository +# It is recommended to include below settings in all .csproj files that are in or below the folder that contains this .editorconfig file: +# +# enable +# true +# preview-All +# true +# +# Note that for .NET 8 the above AnalysisLevel setting is necessary for command-line builds to respect the severity settings in this .editorconfig. In .NET 9+ these are always respected. + # References: # - To learn more about .editorconfig see https://aka.ms/editorconfigdocs # - Set rule severity https://docs.microsoft.com/en-us/visualstudio/code-quality/use-roslyn-analyzers?view=vs-2019#set-rule-severity-in-an-editorconfig-file # - Configure analyzer rules https://github.com/dotnet/roslyn-analyzers/blob/main/docs/Analyzer%20Configuration.md -############################### -# Core EditorConfig Options # -############################### +# Note that choices to deviate from the default are governed by these goals: +# 1) Remove noise +# 2) Increase conciseness + +# Comments "# Default: " indicate where values are changed from the default + root = true # All files [*] +charset = utf-8-bom # Default: not specified indent_style = space -# Line endings are different from Visual Studio for Windows .editorconfig template defaults; see comments in EOL.gitattributes for rationale -end_of_line = lf - -# Code files -[*.{cs,csx,vb,vbx}] -indent_size = 4 -insert_final_newline = true -charset = utf-8-bom +end_of_line = lf # Default: crlf. Line endings are different from Visual Studio for Windows .editorconfig template defaults; see comments in EOL.gitattributes for rationale +# Note that on save VS 2022 17.9.5 adds some keys in this section that are also specified in the [*.cs] section (with the same values) - see https://github.com/dotnet/roslyn/issues/59325 +# These duplicate key-values dont hurt but they can be removed from this section -############################################################### -# Different from VS2022 .NET .editorconfig template defaults # -# Defaults are left in their original location, commented out # -# with comment prefix "# Default: " # -############################################################### - -# Note that choices to deviate from the default are governed by these goals: -# 1) Remove noise -# 2) Increase conciseness +# C# project files +[*.csproj] -dotnet_style_qualification_for_field = false:warning -dotnet_style_qualification_for_property = false:warning -dotnet_style_qualification_for_method = false:warning -dotnet_style_qualification_for_event = false:warning - -dotnet_style_predefined_type_for_locals_parameters_members = true:warning -dotnet_style_predefined_type_for_member_access = true:warning - -dotnet_style_require_accessibility_modifiers = omit_if_default:warning -dotnet_style_readonly_field = true:warning - -csharp_style_expression_bodied_methods = true:warning -csharp_style_expression_bodied_constructors = true:warning -csharp_style_expression_bodied_operators = true:warning -csharp_style_expression_bodied_properties = true:warning -csharp_style_expression_bodied_indexers = true:warning -csharp_style_expression_bodied_accessors = true:warning - -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning -dotnet_style_prefer_inferred_tuple_names = true:warning -dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning -dotnet_style_prefer_auto_properties = true:warning -dotnet_style_prefer_conditional_expression_over_assignment = true # We want true:suggestion but that does not work, so we specify the severity in the next line: -dotnet_diagnostic.IDE0045.severity = suggestion -dotnet_style_prefer_conditional_expression_over_return = true # We want true:suggestion but that does not work, so we specify the severity in the next line: -dotnet_diagnostic.IDE0046.severity = suggestion - -csharp_style_deconstructed_variable_declaration = true:warning -csharp_prefer_simple_default_expression = true:warning -csharp_style_pattern_local_over_anonymous_function = true:warning -csharp_style_inlined_variable_declaration = true:warning - -csharp_using_directive_placement = outside_namespace:warning - # Placing usings outside namespaces lets the closest parent namespace control type name match - # It can also make attribute usage more concise - # See https://stackoverflow.com/questions/125319/should-using-directives-be-inside-or-outside-the-namespace -csharp_style_namespace_declarations = file_scoped:warning -csharp_style_prefer_method_group_conversion = true:warning - -csharp_style_expression_bodied_lambdas = true:warning -csharp_style_expression_bodied_local_functions = false:warning -csharp_style_prefer_null_check_over_type_check = true:warning -csharp_style_prefer_local_over_anonymous_function = true:warning -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning -csharp_style_implicit_object_creation_when_type_is_apparent = true:warning -csharp_style_prefer_tuple_swap = true:warning -csharp_style_unused_value_assignment_preference = discard_variable:warning -csharp_style_unused_value_expression_statement_preference = discard_variable:warning -csharp_prefer_static_local_function = true:warning +# Indentation and spacing +indent_size = 2 +tab_width = 2 -dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning +# C# files +[*.cs] -csharp_style_var_for_built_in_types = false:warning -csharp_style_var_when_type_is_apparent = true:warning -csharp_style_var_elsewhere = true:warning +#### Core EditorConfig Options #### -dotnet_analyzer_diagnostic.severity = error -dotnet_diagnostic.CA1848.severity = suggestion # Not every logging statement needs to be optimized for performance -dotnet_diagnostic.CA1708.severity = none # CA1708 is not relevant because we do not design binary API's for other languages than C#. See https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1708 -dotnet_diagnostic.CA1716.severity = none # CA1716 is not relevant because we do not design binary API's for other languages than C#. See https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1716 -dotnet_diagnostic.IDE0130.severity = suggestion # IDE0130 "Namespace does not match folder structure" can be deviated from to prevent overstructuring -dotnet_diagnostic.IDE0055.severity = silent # Nonstandard whitespace formatting can be used to improve readability -dotnet_diagnostic.IDE0072.severity = suggestion - # Populate switch: switch options should be exhaustive for all expected functionality including expected errors, - # while truly unexpected returns should throw an exception. Since switch automatically throws a SwitchExpressionException, - # it is valid to not explicitly code cases that throw an exception to indicate the return is truly unexpected. - -# Start of NO underscore prefix on private fields -# Define the 'private_fields' symbol group: -dotnet_naming_symbols.private_fields.applicable_kinds = field -dotnet_naming_symbols.private_fields.applicable_accessibilities = private +# Indentation and spacing +indent_size = 4 +tab_width = 4 -# Define the 'notunderscored' naming style -dotnet_naming_style.notunderscored.capitalization = camel_case -dotnet_naming_style.notunderscored.required_prefix = +# New line preferences +insert_final_newline = true # Default: false -# Define the 'private_fields_notunderscored' naming rule -dotnet_naming_rule.private_fields_notunderscored.symbols = private_fields -dotnet_naming_rule.private_fields_notunderscored.style = notunderscored -dotnet_naming_rule.private_fields_notunderscored.severity = error -# End of No underscore prefix on private fields +#### .NET Coding Conventions #### -############################### -# .NET Coding Conventions # -############################### -[*.{cs,vb}] # Organize usings -dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true # Default: false +file_header_template = unset -# this. preferences -# Default: dotnet_style_qualification_for_field = false:silent -# Default: dotnet_style_qualification_for_property = false:silent -# Default: dotnet_style_qualification_for_method = false:silent -# Default: dotnet_style_qualification_for_event = false:silent +# this. and Me. preferences +dotnet_style_qualification_for_event = false:warning # Default: false +dotnet_style_qualification_for_field = false:warning # Default: false +dotnet_style_qualification_for_method = false:warning # Default: false +dotnet_style_qualification_for_property = false:warning # Default: false # Language keywords vs BCL types preferences -# Default: dotnet_style_predefined_type_for_locals_parameters_members = true:silent -# Default: dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_predefined_type_for_locals_parameters_members = true:warning # Default: true +dotnet_style_predefined_type_for_member_access = true:warning # Default: true + # Parentheses preferences dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + # Modifier preferences -# Default: dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent -# Default: dotnet_style_readonly_field = true:suggestion +dotnet_style_require_accessibility_modifiers = omit_if_default:warning # Default: for_non_interface_members + # Expression-level preferences -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_coalesce_expression = true:suggestion -# Default: dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent -# Default: dotnet_style_prefer_inferred_tuple_names = true:suggestion -# Default: dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion -# Default: dotnet_style_prefer_auto_properties = true:silent -# Default: dotnet_style_prefer_conditional_expression_over_assignment = true:silent -# Default: dotnet_style_prefer_conditional_expression_over_return = true:silent - -############################### -# Naming Conventions # -############################### -# Style Definitions -dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# Use PascalCase for constant fields -# Default: dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style -dotnet_naming_symbols.constant_fields.applicable_kinds = field -dotnet_naming_symbols.constant_fields.applicable_accessibilities = * -dotnet_naming_symbols.constant_fields.required_modifiers = const -############################### -# C# Coding Conventions # -############################### -[*.cs] +dotnet_style_coalesce_expression = true:warning # Default: true:suggestion +dotnet_style_collection_initializer = true:warning # Default: true:suggestion +dotnet_style_explicit_tuple_names = true:warning # Default: true:suggestion +dotnet_style_namespace_match_folder = false:silent # Default: true - can be deviated from to prevent overstructuring +dotnet_style_null_propagation = true:warning # Default: true:suggestion +dotnet_style_object_initializer = true:warning # Default: true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:warning # Default: true +dotnet_style_prefer_collection_expression = when_types_loosely_match:warning # Default: when_types_loosely_match:suggestion +dotnet_style_prefer_compound_assignment = true:warning # Default: true +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion # Default: true +dotnet_style_prefer_conditional_expression_over_return = true:suggestion # Default: true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning # Default: true +dotnet_style_prefer_inferred_tuple_names = true:warning # Default: true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning # Default: true +dotnet_style_prefer_simplified_boolean_expressions = true:warning # Default: true +dotnet_style_prefer_simplified_interpolation = true:warning # Default: true + +# Field preferences +dotnet_style_readonly_field = true:warning # Default: true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:warning # Default: all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent + +#### C# Coding Conventions #### + # var preferences -# Default: csharp_style_var_for_built_in_types = true:silent -# Default: csharp_style_var_when_type_is_apparent = true:silent -# Default: csharp_style_var_elsewhere = true:silent +csharp_style_var_elsewhere = true:warning # Default: false:silent +csharp_style_var_for_built_in_types = false:warning # Default: false:silent +csharp_style_var_when_type_is_apparent = true:warning # Default: false:silent # Expression-bodied members -# Default: csharp_style_expression_bodied_methods = false:silent -# Default: csharp_style_expression_bodied_constructors = false:silent -# Default: csharp_style_expression_bodied_operators = false:silent -# Default: csharp_style_expression_bodied_properties = true:silent -# Default: csharp_style_expression_bodied_indexers = true:silent -# Default: csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_accessors = true:warning # Default: true:silent +csharp_style_expression_bodied_constructors = true:warning # Default: false:silent +csharp_style_expression_bodied_indexers = true:warning # Default: true:silent +csharp_style_expression_bodied_lambdas = true:warning # Default: true:silent +csharp_style_expression_bodied_local_functions = true:warning # Default: false:silent +csharp_style_expression_bodied_methods = true:warning # Default: false:silent +csharp_style_expression_bodied_operators = true:warning # Default: false:silent +csharp_style_expression_bodied_properties = true:warning # Default: true:silent # Pattern matching preferences -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:warning # Default: true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:warning # Default: true:suggestion +csharp_style_prefer_extended_property_pattern = true:warning # Default: true +csharp_style_prefer_not_pattern = true:warning # Default: true:suggestion +csharp_style_prefer_pattern_matching = true:warning # Default: true:silent +csharp_style_prefer_switch_expression = true:warning # Default: true:suggestion + # Null-checking preferences -csharp_style_throw_expression = true:suggestion -csharp_style_conditional_delegate_call = true:suggestion +csharp_style_conditional_delegate_call = true:warning # Default: true:suggestion + # Modifier preferences -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +csharp_prefer_static_local_function = true:warning # Default: true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true:warning # Default: true:suggestion +csharp_style_prefer_readonly_struct_member = true:warning # Default: true:suggestion + +# Code-block preferences +csharp_prefer_braces = when_multiline:warning # Default: true:silent +csharp_prefer_simple_using_statement = true:warning # Default: true:suggestion +csharp_style_namespace_declarations = file_scoped:warning # Default: block_scoped:silent +csharp_style_prefer_method_group_conversion = true:warning # Default: true:silent +csharp_style_prefer_primary_constructors = true:warning # Default: true:suggestion +csharp_style_prefer_top_level_statements = true:silent + # Expression-level preferences -csharp_prefer_braces = when_multiline:silent -# Default: csharp_style_deconstructed_variable_declaration = true:suggestion -# Default: csharp_prefer_simple_default_expression = true:suggestion -# Default: csharp_style_pattern_local_over_anonymous_function = true:suggestion -# Default: csharp_style_inlined_variable_declaration = true:suggestion -############################### -# C# Formatting Rules # -############################### +csharp_prefer_simple_default_expression = true:warning # Default: true:suggestion +csharp_style_deconstructed_variable_declaration = true:warning # Default: true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:warning # Default: true:suggestion +csharp_style_inlined_variable_declaration = true:warning # Default: true:suggestion +csharp_style_prefer_index_operator = true:warning # Default: true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:warning # Default: true:suggestion +csharp_style_prefer_null_check_over_type_check = true:warning # Default: true:suggestion +csharp_style_prefer_range_operator = true:warning # Default: true:suggestion +csharp_style_prefer_tuple_swap = true:warning # Default: true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_throw_expression = true:warning # Default: true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:warning # Default: discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:warning # Default: discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:warning # Default: outside_namespace:silent + # Placing usings outside namespaces lets the closest parent namespace control type name match + # It can also make attribute usage more concise + # See https://stackoverflow.com/questions/125319/should-using-directives-be-inside-or-outside-the-namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent + +#### C# Formatting Rules #### + # New line preferences -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = 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_object_initializers = 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 = true +csharp_indent_labels = one_less_than_current csharp_indent_switch_labels = true -csharp_indent_labels = flush_left + # 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 = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +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_before_colon_in_inheritance_clause = true -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_around_binary_operators = before_and_after -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_square_brackets = false + # Wrapping preferences -csharp_preserve_single_line_statements = true csharp_preserve_single_line_blocks = true -# Default: csharp_using_directive_placement = outside_namespace:silent -csharp_prefer_simple_using_statement = true:suggestion -# Default: csharp_style_namespace_declarations = block_scoped:silent -# Default: csharp_style_prefer_method_group_conversion = true:silent -csharp_style_prefer_top_level_statements = true:silent -csharp_style_expression_bodied_methods = true:warning -csharp_style_expression_bodied_constructors = true:warning -csharp_style_expression_bodied_operators = true:warning -csharp_style_expression_bodied_properties = true:warning -csharp_style_expression_bodied_indexers = true:warning -csharp_style_expression_bodied_accessors = true:warning -# Default: csharp_style_expression_bodied_lambdas = true:silent -# Default: csharp_style_expression_bodied_local_functions = false:silent -# Default: csharp_style_prefer_null_check_over_type_check = true:suggestion -# Default: csharp_style_prefer_local_over_anonymous_function = true:suggestion -# Default: csharp_style_prefer_index_operator = true:suggestion -# Default: csharp_style_prefer_range_operator = true:suggestion -# Default: csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion -# Default: csharp_style_prefer_tuple_swap = true:suggestion -# Default: csharp_style_unused_value_assignment_preference = discard_variable:suggestion -# Default: csharp_style_unused_value_expression_statement_preference = discard_variable:silent -# Default: 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 = true:silent -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent -csharp_style_prefer_parameter_null_checking = true:suggestion -############################### -# VB Coding Conventions # -############################### -[*.vb] -# Modifier preferences -visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion -[*.{cs,vb}] +csharp_preserve_single_line_statements = true + #### Naming styles #### # Naming rules dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning + # Default: 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 = warning + # Default: 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 = warning + # Default: 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 @@ -291,56 +263,54 @@ 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_operator_placement_when_wrapping = beginning_of_line -tab_width = 4 -dotnet_style_prefer_simplified_boolean_expressions = true:warning -dotnet_style_prefer_compound_assignment = true:warning -dotnet_style_prefer_simplified_interpolation = true:warning -dotnet_style_namespace_match_folder = false:silent -dotnet_style_allow_multiple_blank_lines_experimental = true:silent -dotnet_style_allow_statement_immediately_after_block_experimental = true:silent -dotnet_code_quality_unused_parameters = all:warning - -# Additional settings exported from VS 17.4.0 Preview 4.0: -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_labels = one_less_than_current - -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_semicolon_in_for_statement = true +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 -csharp_space_around_declaration_statements = false +# Use PascalCase for constant fields (as per https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names) +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const -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 +# Start of NO underscore prefix on private fields +# Define the 'private_fields' symbol group: +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_square_brackets = false +# Define the 'notunderscored' naming style +dotnet_naming_style.notunderscored.capitalization = camel_case +dotnet_naming_style.notunderscored.required_prefix = -csharp_style_prefer_not_pattern = true -csharp_style_prefer_readonly_struct = true -csharp_style_prefer_switch_expression = true -csharp_style_prefer_utf8_string_literals = true +# Define the 'private_fields_notunderscored' naming rule +dotnet_naming_rule.private_fields_notunderscored.symbols = private_fields +dotnet_naming_rule.private_fields_notunderscored.style = notunderscored +dotnet_naming_rule.private_fields_notunderscored.severity = error +# End of No underscore prefix on private fields -dotnet_remove_unnecessary_suppression_exclusions = none -dotnet_separate_import_directive_groups = false +# Severity changes from Default -dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_analyzer_diagnostic.severity = error +dotnet_diagnostic.CA1034.severity = suggestion + # changed from warning because using namespaces as alternative to nested types for logical grouping or for avoiding name collisions, + # as the rule states, either adds noise (repeated local namespace usings) or does not prevent collisions (global namespace usings) + # see https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/CA1034 +dotnet_diagnostic.CA1708.severity = none # CA1708 is not relevant because we do not design binary API's for other languages than C#. See https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1708 +dotnet_diagnostic.CA1716.severity = none # CA1716 is not relevant because we do not design binary API's for other languages than C#. See https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1716 +dotnet_diagnostic.CA1812.severity = suggestion # changed from warning because too many false positives where external libraries create instances (e.g. DI) +dotnet_diagnostic.CA1848.severity = suggestion # Not every logging statement needs to be optimized for performance +dotnet_diagnostic.CA2225.severity = none # is not relevant because we do not design binary API's for other languages than C#. See https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2225 +dotnet_diagnostic.IDE0055.severity = silent # Nonstandard whitespace formatting can be used to improve readability +dotnet_diagnostic.IDE0072.severity = suggestion + # Populate switch: switch options should be exhaustive for all expected functionality including expected errors, + # while truly unexpected returns should throw an exception. Since switch automatically throws a SwitchExpressionException, + # it is valid to not explicitly code cases that throw an exception to indicate the return is truly unexpected. +dotnet_diagnostic.IDE0130.severity = suggestion # IDE0130 "Namespace does not match folder structure" can be deviated from to prevent overstructuring From a3000b013db4d8cfcbcf252bf8b9be14fb90dfb5 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Mon, 8 Apr 2024 13:38:28 +0200 Subject: [PATCH 03/12] fix warnings generated by new C# 12 editorconfig and new Orleans 8 analyzers --- .editorconfig | 3 ++ .../TeamA/Apis/BasketApi/BasketsController.cs | 11 ++---- .../Apis/CatalogApi/CatalogController.cs | 19 ++++++---- .../Apis/Foundation/GlobalSuppressions.cs | 8 ++++ .../TeamA/BasketService/BasketGrain.cs | 4 +- .../TeamA/BasketService/BasketService.csproj | 38 ++++++++++--------- .../CatalogServiceClientGrain.cs | 4 +- .../Foundation/GlobalSuppressions.cs | 8 ++++ .../TeamA/CatalogService/CatalogGrain.cs | 8 +--- .../CatalogService/CatalogService.csproj | 38 ++++++++++--------- .../Foundation/GlobalSuppressions.cs | 8 ++++ .../BasketContract/BasketContract.cs | 6 ++- .../TeamA/Contracts/Contracts.csproj | 30 ++++++++------- 13 files changed, 109 insertions(+), 76 deletions(-) create mode 100644 src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalSuppressions.cs create mode 100644 src/Example/eShopBySingleTeam/TeamA/BasketService/Foundation/GlobalSuppressions.cs create mode 100644 src/Example/eShopBySingleTeam/TeamA/CatalogService/Foundation/GlobalSuppressions.cs diff --git a/.editorconfig b/.editorconfig index 4ff01a2..81662df 100644 --- a/.editorconfig +++ b/.editorconfig @@ -314,3 +314,6 @@ dotnet_diagnostic.IDE0072.severity = suggestion # while truly unexpected returns should throw an exception. Since switch automatically throws a SwitchExpressionException, # it is valid to not explicitly code cases that throw an exception to indicate the return is truly unexpected. dotnet_diagnostic.IDE0130.severity = suggestion # IDE0130 "Namespace does not match folder structure" can be deviated from to prevent overstructuring + +# Solution-specific settings +dotnet_diagnostic.ORLEANS0010.severity = none # adding an alias to every grain contract type and member adds too much noise. This rule should be triggered only during refactoring diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsController.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsController.cs index 215008a..9b346c7 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsController.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsController.cs @@ -4,15 +4,10 @@ namespace Applicita.eShop.Apis.BasketApi; [Route("[controller]")] [ApiController] -public class BasketsController : ControllerBase +public class BasketsController(IClusterClient orleans) : ControllerBase { const string Basket = "{buyerId}"; - readonly IClusterClient orleans; - - public BasketsController(IClusterClient orleans) - => this.orleans = orleans; - /// The basket of buyerId is returned [HttpGet(Basket)] [ProducesResponseType(StatusCodes.Status200OK)] @@ -23,7 +18,9 @@ public async Task> GetBasket(int buyerId) [HttpPut()] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> UpdateBasket(Basket basket) - => Ok(await BasketGrain(basket.BuyerId).UpdateBasket(basket)); + => basket is null + ? BadRequest("basket is required") + : Ok(await BasketGrain(basket.BuyerId).UpdateBasket(basket)); /// The basket of buyerId is emptied [HttpDelete(Basket)] diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs index 74a645a..418f54d 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs @@ -5,15 +5,12 @@ namespace Applicita.eShop.Apis.CatalogApi; [Route("[controller]")] [ApiController] -public class CatalogController : ControllerBase +public class CatalogController(IClusterClient orleans) : ControllerBase { const string Products = "products"; const string Product = Products + "/{id}"; - readonly ICatalogGrain catalog; - - public CatalogController(IClusterClient orleans) - => catalog = orleans.GetGrain(ICatalogGrain.Key); + readonly ICatalogGrain catalog = orleans.GetGrain(ICatalogGrain.Key); /// The new product is created with the returned id [HttpPost(Products)] @@ -28,7 +25,7 @@ public async Task> CreateProduct(Product product) [HttpGet(Products)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetProducts([FromQuery] int[]? id = null) - => await (id is null ? catalog.GetAllProducts() : catalog.GetCurrentProducts(id.ToImmutableArray())); + => await (id is null ? catalog.GetAllProducts() : catalog.GetCurrentProducts([.. id])); /// The product is updated /// The product id is not found @@ -36,7 +33,11 @@ public async Task> GetProducts([FromQuery] int[]? id = n [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task UpdateProduct(Product product) - => (await catalog.UpdateProduct(product)) ? Ok() : NotFound(product.Id); + => product is null + ? BadRequest("product is required") + : (await catalog.UpdateProduct(product)) + ? Ok() + : NotFound(product.Id); /// The product is deleted /// The product id is not found @@ -44,5 +45,7 @@ public async Task UpdateProduct(Product product) [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteProduct(int id) - => (await catalog.DeleteProduct(id)) ? Ok() : NotFound(id); + => (await catalog.DeleteProduct(id)) + ? Ok() + : NotFound(id); } diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalSuppressions.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalSuppressions.cs new file mode 100644 index 0000000..a8fb5df --- /dev/null +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant in ASP.NET Core")] diff --git a/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketGrain.cs b/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketGrain.cs index 1c7d7a5..66cdf04 100644 --- a/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketGrain.cs +++ b/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketGrain.cs @@ -9,7 +9,7 @@ sealed class BasketGrain : Grain, IBasketGrain internal sealed class State { [Id(0)] public bool HasBuyerId { get; set; } - [Id(1)] public Basket Basket { get; set; } = new Basket(-1, ImmutableArray.Empty); + [Id(1)] public Basket Basket { get; set; } = new Basket(-1, []); } readonly IPersistentState state; @@ -46,7 +46,7 @@ public BasketGrain([PersistentState("state")] IPersistentState state) public async Task EmptyBasket() { - Basket = Basket with { Items = ImmutableArray.Empty }; + Basket = Basket with { Items = [] }; await state.WriteStateAsync(); } diff --git a/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketService.csproj b/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketService.csproj index f66e3a1..1c0d82e 100644 --- a/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketService.csproj +++ b/src/Example/eShopBySingleTeam/TeamA/BasketService/BasketService.csproj @@ -1,25 +1,27 @@  - - net8.0 - enable - enable - - true - preview-All - true + + net8.0 + enable + enable - Applicita.eShop.$(MSBuildProjectName) - Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) - + true + preview-All + true - - - - + $(NoWarn);EnableGenerateDocumentationFile - - - + Applicita.eShop.$(MSBuildProjectName) + Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) + + + + + + + + + + diff --git a/src/Example/eShopBySingleTeam/TeamA/BasketService/CatalogServiceClientGrain.cs b/src/Example/eShopBySingleTeam/TeamA/BasketService/CatalogServiceClientGrain.cs index 49681b2..aedfe3f 100644 --- a/src/Example/eShopBySingleTeam/TeamA/BasketService/CatalogServiceClientGrain.cs +++ b/src/Example/eShopBySingleTeam/TeamA/BasketService/CatalogServiceClientGrain.cs @@ -20,7 +20,7 @@ public async Task> UpdateFromCurrentProducts(Immutabl var productIds = basketItems.Select(bi => bi.ProductId).ToImmutableArray(); var products = await catalog.GetCurrentProducts(productIds); - List updatedItems = new(); + List updatedItems = []; foreach (var item in basketItems) { var product = products.SingleOrDefault(p => p.Id == item.ProductId); @@ -32,6 +32,6 @@ public async Task> UpdateFromCurrentProducts(Immutabl UnitPrice = product.Price, }); } - return updatedItems.ToImmutableArray(); + return [.. updatedItems]; } } diff --git a/src/Example/eShopBySingleTeam/TeamA/BasketService/Foundation/GlobalSuppressions.cs b/src/Example/eShopBySingleTeam/TeamA/BasketService/Foundation/GlobalSuppressions.cs new file mode 100644 index 0000000..491297f --- /dev/null +++ b/src/Example/eShopBySingleTeam/TeamA/BasketService/Foundation/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant for Orleans grains")] diff --git a/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogGrain.cs b/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogGrain.cs index 0d45960..7b298c6 100644 --- a/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogGrain.cs +++ b/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogGrain.cs @@ -2,19 +2,15 @@ namespace Applicita.eShop.CatalogService; -sealed class CatalogGrain : Grain, ICatalogGrain +sealed class CatalogGrain([PersistentState("state")] IPersistentState catalog) : Grain, ICatalogGrain { [GenerateSerializer] internal sealed class Catalog { [Id(0)] public int NewProductId { get; set; } = 0; - [Id(1)] public List Products { get; set; } = new(); + [Id(1)] public List Products { get; set; } = []; } - readonly IPersistentState catalog; - - public CatalogGrain([PersistentState("state")] IPersistentState catalog) => this.catalog = catalog; - public async Task CreateProduct(Product product) { int newProductId = catalog.State.NewProductId++; diff --git a/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogService.csproj b/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogService.csproj index f66e3a1..1c0d82e 100644 --- a/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogService.csproj +++ b/src/Example/eShopBySingleTeam/TeamA/CatalogService/CatalogService.csproj @@ -1,25 +1,27 @@  - - net8.0 - enable - enable - - true - preview-All - true + + net8.0 + enable + enable - Applicita.eShop.$(MSBuildProjectName) - Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) - + true + preview-All + true - - - - + $(NoWarn);EnableGenerateDocumentationFile - - - + Applicita.eShop.$(MSBuildProjectName) + Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) + + + + + + + + + + diff --git a/src/Example/eShopBySingleTeam/TeamA/CatalogService/Foundation/GlobalSuppressions.cs b/src/Example/eShopBySingleTeam/TeamA/CatalogService/Foundation/GlobalSuppressions.cs new file mode 100644 index 0000000..491297f --- /dev/null +++ b/src/Example/eShopBySingleTeam/TeamA/CatalogService/Foundation/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant for Orleans grains")] diff --git a/src/Example/eShopBySingleTeam/TeamA/Contracts/BasketContract/BasketContract.cs b/src/Example/eShopBySingleTeam/TeamA/Contracts/BasketContract/BasketContract.cs index 9c55ebe..bf0583a 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Contracts/BasketContract/BasketContract.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Contracts/BasketContract/BasketContract.cs @@ -4,7 +4,11 @@ namespace Applicita.eShop.Contracts.BasketContract; public static class GrainFactoryExtensions { - public static IBasketGrain GetBasketGrain(this IGrainFactory factory, int buyerId) => factory.GetGrain(buyerId.ToString(CultureInfo.InvariantCulture)); + public static IBasketGrain GetBasketGrain(this IGrainFactory factory, int buyerId) + { + ArgumentNullException.ThrowIfNull(factory); + return factory.GetGrain(buyerId.ToString(CultureInfo.InvariantCulture)); + } } public interface IBasketGrain : IGrainWithStringKey diff --git a/src/Example/eShopBySingleTeam/TeamA/Contracts/Contracts.csproj b/src/Example/eShopBySingleTeam/TeamA/Contracts/Contracts.csproj index bce276a..da6ae50 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Contracts/Contracts.csproj +++ b/src/Example/eShopBySingleTeam/TeamA/Contracts/Contracts.csproj @@ -1,20 +1,22 @@  - - net8.0 - enable - enable - - true - preview-All - true + + net8.0 + enable + enable - Applicita.eShop.$(MSBuildProjectName).TeamA - Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) - + true + preview-All + true - - - + $(NoWarn);EnableGenerateDocumentationFile + + Applicita.eShop.$(MSBuildProjectName).TeamA + Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) + + + + + From eee475acb374ef3dd4c3dafce32ba1096c46e0c7 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Mon, 8 Apr 2024 15:10:57 +0200 Subject: [PATCH 04/12] Update two teams example solutions same as single team solution --- .../TeamA/Apis/Foundation/Program.cs | 2 - .../eShopByTwoTeams/TeamA/Apis/Apis.csproj | 8 ++-- .../TeamA/Apis/BasketApi/BasketsController.cs | 11 ++---- .../Apis/Foundation/GlobalSuppressions.cs | 8 ++++ .../TeamA/Apis/Foundation/Program.cs | 2 - .../TeamA/BasketService/BasketGrain.cs | 4 +- .../TeamA/BasketService/BasketService.csproj | 11 +++--- .../CatalogServiceClientGrain.cs | 9 ++--- .../Foundation/GlobalSuppressions.cs | 8 ++++ .../BasketContract/BasketContract.cs | 6 ++- .../TeamA/Contracts/Contracts.csproj | 30 ++++++++------- .../eShopByTwoTeams/TeamB/Apis/Apis.csproj | 8 ++-- .../Apis/CatalogApi/CatalogController.cs | 19 ++++++---- .../Apis/Foundation/GlobalSuppressions.cs | 8 ++++ .../TeamB/Apis/Foundation/Program.cs | 2 - .../TeamB/CatalogService/CatalogGrain.cs | 8 +--- .../CatalogService/CatalogService.csproj | 38 ++++++++++--------- .../Foundation/GlobalSuppressions.cs | 8 ++++ .../CatalogContract/CatalogContract.cs | 2 +- .../TeamB/Contracts/Contracts.csproj | 30 ++++++++------- 20 files changed, 127 insertions(+), 95 deletions(-) create mode 100644 src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalSuppressions.cs create mode 100644 src/Example/eShopByTwoTeams/TeamA/BasketService/Foundation/GlobalSuppressions.cs create mode 100644 src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalSuppressions.cs create mode 100644 src/Example/eShopByTwoTeams/TeamB/CatalogService/Foundation/GlobalSuppressions.cs diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs index 2018e0f..dfc4aec 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs @@ -24,5 +24,3 @@ app.MapControllers(); app.Run(); - -sealed partial class Program { } // Fix CA1852 diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/Apis.csproj b/src/Example/eShopByTwoTeams/TeamA/Apis/Apis.csproj index b6a12f4..deb4610 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Apis/Apis.csproj +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/Apis.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable @@ -19,9 +19,9 @@ - - - + + + diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsController.cs b/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsController.cs index 215008a..9b346c7 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsController.cs +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsController.cs @@ -4,15 +4,10 @@ namespace Applicita.eShop.Apis.BasketApi; [Route("[controller]")] [ApiController] -public class BasketsController : ControllerBase +public class BasketsController(IClusterClient orleans) : ControllerBase { const string Basket = "{buyerId}"; - readonly IClusterClient orleans; - - public BasketsController(IClusterClient orleans) - => this.orleans = orleans; - /// The basket of buyerId is returned [HttpGet(Basket)] [ProducesResponseType(StatusCodes.Status200OK)] @@ -23,7 +18,9 @@ public async Task> GetBasket(int buyerId) [HttpPut()] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> UpdateBasket(Basket basket) - => Ok(await BasketGrain(basket.BuyerId).UpdateBasket(basket)); + => basket is null + ? BadRequest("basket is required") + : Ok(await BasketGrain(basket.BuyerId).UpdateBasket(basket)); /// The basket of buyerId is emptied [HttpDelete(Basket)] diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalSuppressions.cs b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalSuppressions.cs new file mode 100644 index 0000000..a8fb5df --- /dev/null +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant in ASP.NET Core")] diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Program.cs b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Program.cs index 8a099bc..5241234 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Program.cs +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Program.cs @@ -24,5 +24,3 @@ app.MapControllers(); app.Run(); - -sealed partial class Program { } // Fix CA1852 diff --git a/src/Example/eShopByTwoTeams/TeamA/BasketService/BasketGrain.cs b/src/Example/eShopByTwoTeams/TeamA/BasketService/BasketGrain.cs index 1c7d7a5..66cdf04 100644 --- a/src/Example/eShopByTwoTeams/TeamA/BasketService/BasketGrain.cs +++ b/src/Example/eShopByTwoTeams/TeamA/BasketService/BasketGrain.cs @@ -9,7 +9,7 @@ sealed class BasketGrain : Grain, IBasketGrain internal sealed class State { [Id(0)] public bool HasBuyerId { get; set; } - [Id(1)] public Basket Basket { get; set; } = new Basket(-1, ImmutableArray.Empty); + [Id(1)] public Basket Basket { get; set; } = new Basket(-1, []); } readonly IPersistentState state; @@ -46,7 +46,7 @@ public BasketGrain([PersistentState("state")] IPersistentState state) public async Task EmptyBasket() { - Basket = Basket with { Items = ImmutableArray.Empty }; + Basket = Basket with { Items = [] }; await state.WriteStateAsync(); } diff --git a/src/Example/eShopByTwoTeams/TeamA/BasketService/BasketService.csproj b/src/Example/eShopByTwoTeams/TeamA/BasketService/BasketService.csproj index feb1f6a..ef4b382 100644 --- a/src/Example/eShopByTwoTeams/TeamA/BasketService/BasketService.csproj +++ b/src/Example/eShopByTwoTeams/TeamA/BasketService/BasketService.csproj @@ -1,19 +1,20 @@  - net7.0 + net8.0 enable enable true preview-All true + $(NoWarn);EnableGenerateDocumentationFile Applicita.eShop.$(MSBuildProjectName) Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Example/eShopByTwoTeams/TeamA/BasketService/CatalogServiceClientGrain.cs b/src/Example/eShopByTwoTeams/TeamA/BasketService/CatalogServiceClientGrain.cs index 3144f1b..10d1c63 100644 --- a/src/Example/eShopByTwoTeams/TeamA/BasketService/CatalogServiceClientGrain.cs +++ b/src/Example/eShopByTwoTeams/TeamA/BasketService/CatalogServiceClientGrain.cs @@ -12,15 +12,14 @@ interface ICatalogServiceClientGrain : IGrainWithIntegerKey [StatelessWorker] sealed class CatalogServiceClientGrain : Grain, ICatalogServiceClientGrain { - readonly CatalogServiceClient client; - - public CatalogServiceClientGrain() => client = new("http://localhost:5113", new()); + readonly CatalogServiceClient client = new("http://localhost:5113", new()); public async Task> UpdateFromCurrentProducts(ImmutableArray basketItems) { var productIds = basketItems.Select(bi => bi.ProductId).ToImmutableArray(); var products = await client.ProductsAllAsync(productIds); - List updatedItems = new(); + + List updatedItems = []; foreach (var item in basketItems) { var product = products.SingleOrDefault(p => p.Id == item.ProductId); @@ -32,6 +31,6 @@ public async Task> UpdateFromCurrentProducts(Immutabl UnitPrice = (decimal)product.Price, }); } - return updatedItems.ToImmutableArray(); + return [.. updatedItems]; } } diff --git a/src/Example/eShopByTwoTeams/TeamA/BasketService/Foundation/GlobalSuppressions.cs b/src/Example/eShopByTwoTeams/TeamA/BasketService/Foundation/GlobalSuppressions.cs new file mode 100644 index 0000000..491297f --- /dev/null +++ b/src/Example/eShopByTwoTeams/TeamA/BasketService/Foundation/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant for Orleans grains")] diff --git a/src/Example/eShopByTwoTeams/TeamA/Contracts/BasketContract/BasketContract.cs b/src/Example/eShopByTwoTeams/TeamA/Contracts/BasketContract/BasketContract.cs index 9c55ebe..bf0583a 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Contracts/BasketContract/BasketContract.cs +++ b/src/Example/eShopByTwoTeams/TeamA/Contracts/BasketContract/BasketContract.cs @@ -4,7 +4,11 @@ namespace Applicita.eShop.Contracts.BasketContract; public static class GrainFactoryExtensions { - public static IBasketGrain GetBasketGrain(this IGrainFactory factory, int buyerId) => factory.GetGrain(buyerId.ToString(CultureInfo.InvariantCulture)); + public static IBasketGrain GetBasketGrain(this IGrainFactory factory, int buyerId) + { + ArgumentNullException.ThrowIfNull(factory); + return factory.GetGrain(buyerId.ToString(CultureInfo.InvariantCulture)); + } } public interface IBasketGrain : IGrainWithStringKey diff --git a/src/Example/eShopByTwoTeams/TeamA/Contracts/Contracts.csproj b/src/Example/eShopByTwoTeams/TeamA/Contracts/Contracts.csproj index 9861806..da6ae50 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Contracts/Contracts.csproj +++ b/src/Example/eShopByTwoTeams/TeamA/Contracts/Contracts.csproj @@ -1,20 +1,22 @@  - - net7.0 - enable - enable - - true - preview-All - true + + net8.0 + enable + enable - Applicita.eShop.$(MSBuildProjectName).TeamA - Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) - + true + preview-All + true - - - + $(NoWarn);EnableGenerateDocumentationFile + + Applicita.eShop.$(MSBuildProjectName).TeamA + Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) + + + + + diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/Apis.csproj b/src/Example/eShopByTwoTeams/TeamB/Apis/Apis.csproj index 3c45aac..156c935 100644 --- a/src/Example/eShopByTwoTeams/TeamB/Apis/Apis.csproj +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/Apis.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable @@ -19,9 +19,9 @@ - - - + + + diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogController.cs b/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogController.cs index 6d71293..418f54d 100644 --- a/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogController.cs +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogController.cs @@ -5,15 +5,12 @@ namespace Applicita.eShop.Apis.CatalogApi; [Route("[controller]")] [ApiController] -public class CatalogController : ControllerBase +public class CatalogController(IClusterClient orleans) : ControllerBase { const string Products = "products"; const string Product = Products + "/{id}"; - readonly ICatalogGrain catalog; - - public CatalogController(IClusterClient orleans) - => catalog = orleans.GetGrain(ICatalogGrain.Key); + readonly ICatalogGrain catalog = orleans.GetGrain(ICatalogGrain.Key); /// The new product is created with the returned id [HttpPost(Products)] @@ -28,7 +25,7 @@ public async Task> CreateProduct(Product product) [HttpGet(Products)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetProducts([FromQuery] int[]? id = null) - => await (id is null ? catalog.GetAllProducts() : catalog.GetCurrentProducts(id.ToImmutableArray())); + => await (id is null ? catalog.GetAllProducts() : catalog.GetCurrentProducts([.. id])); /// The product is updated /// The product id is not found @@ -36,7 +33,11 @@ public async Task> GetProducts([FromQuery] int[]? id = n [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task UpdateProduct(Product product) - => (await catalog.UpdateProduct(product)) ? Ok() : NotFound(product.Id); + => product is null + ? BadRequest("product is required") + : (await catalog.UpdateProduct(product)) + ? Ok() + : NotFound(product.Id); /// The product is deleted /// The product id is not found @@ -44,5 +45,7 @@ public async Task UpdateProduct(Product product) [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteProduct(int id) - => (await catalog.DeleteProduct(id)) ? Ok() : NotFound(id); + => (await catalog.DeleteProduct(id)) + ? Ok() + : NotFound(id); } diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalSuppressions.cs b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalSuppressions.cs new file mode 100644 index 0000000..a8fb5df --- /dev/null +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant in ASP.NET Core")] diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Program.cs b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Program.cs index 3f9332e..0b9c34f 100644 --- a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Program.cs +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Program.cs @@ -24,5 +24,3 @@ app.MapControllers(); app.Run(); - -sealed partial class Program { } // Fix CA1852 diff --git a/src/Example/eShopByTwoTeams/TeamB/CatalogService/CatalogGrain.cs b/src/Example/eShopByTwoTeams/TeamB/CatalogService/CatalogGrain.cs index 0d45960..7b298c6 100644 --- a/src/Example/eShopByTwoTeams/TeamB/CatalogService/CatalogGrain.cs +++ b/src/Example/eShopByTwoTeams/TeamB/CatalogService/CatalogGrain.cs @@ -2,19 +2,15 @@ namespace Applicita.eShop.CatalogService; -sealed class CatalogGrain : Grain, ICatalogGrain +sealed class CatalogGrain([PersistentState("state")] IPersistentState catalog) : Grain, ICatalogGrain { [GenerateSerializer] internal sealed class Catalog { [Id(0)] public int NewProductId { get; set; } = 0; - [Id(1)] public List Products { get; set; } = new(); + [Id(1)] public List Products { get; set; } = []; } - readonly IPersistentState catalog; - - public CatalogGrain([PersistentState("state")] IPersistentState catalog) => this.catalog = catalog; - public async Task CreateProduct(Product product) { int newProductId = catalog.State.NewProductId++; diff --git a/src/Example/eShopByTwoTeams/TeamB/CatalogService/CatalogService.csproj b/src/Example/eShopByTwoTeams/TeamB/CatalogService/CatalogService.csproj index c09471f..1c0d82e 100644 --- a/src/Example/eShopByTwoTeams/TeamB/CatalogService/CatalogService.csproj +++ b/src/Example/eShopByTwoTeams/TeamB/CatalogService/CatalogService.csproj @@ -1,25 +1,27 @@  - - net7.0 - enable - enable - - true - preview-All - true + + net8.0 + enable + enable - Applicita.eShop.$(MSBuildProjectName) - Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) - + true + preview-All + true - - - - + $(NoWarn);EnableGenerateDocumentationFile - - - + Applicita.eShop.$(MSBuildProjectName) + Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) + + + + + + + + + + diff --git a/src/Example/eShopByTwoTeams/TeamB/CatalogService/Foundation/GlobalSuppressions.cs b/src/Example/eShopByTwoTeams/TeamB/CatalogService/Foundation/GlobalSuppressions.cs new file mode 100644 index 0000000..491297f --- /dev/null +++ b/src/Example/eShopByTwoTeams/TeamB/CatalogService/Foundation/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant for Orleans grains")] diff --git a/src/Example/eShopByTwoTeams/TeamB/Contracts/CatalogContract/CatalogContract.cs b/src/Example/eShopByTwoTeams/TeamB/Contracts/CatalogContract/CatalogContract.cs index 40d1b20..f700289 100644 --- a/src/Example/eShopByTwoTeams/TeamB/Contracts/CatalogContract/CatalogContract.cs +++ b/src/Example/eShopByTwoTeams/TeamB/Contracts/CatalogContract/CatalogContract.cs @@ -1,4 +1,5 @@ namespace Applicita.eShop.Contracts.CatalogContract; + public interface ICatalogGrain : IGrainWithStringKey { const string Key = ""; @@ -23,4 +24,3 @@ public record Product( [property: Id(0)] int Id, [property: Id(1)] string Name, [property: Id(2)] decimal Price); - diff --git a/src/Example/eShopByTwoTeams/TeamB/Contracts/Contracts.csproj b/src/Example/eShopByTwoTeams/TeamB/Contracts/Contracts.csproj index 1e79625..6955089 100644 --- a/src/Example/eShopByTwoTeams/TeamB/Contracts/Contracts.csproj +++ b/src/Example/eShopByTwoTeams/TeamB/Contracts/Contracts.csproj @@ -1,20 +1,22 @@  - - net7.0 - enable - enable - - true - preview-All - true + + net8.0 + enable + enable - Applicita.eShop.$(MSBuildProjectName).TeamB - Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) - + true + preview-All + true + + $(NoWarn);EnableGenerateDocumentationFile - - - + Applicita.eShop.$(MSBuildProjectName).TeamB + Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) + + + + + From 493eb68ee8cebb05a9baf63c3e36fcf300e6e6c3 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Tue, 9 Apr 2024 20:12:52 +0200 Subject: [PATCH 05/12] Add Catalog API's as minimal API with helper to enable DI for a group of endpoints - eliminates init code and eliminates repeated parameters across endpoints --- .../Apis/CatalogApi/CatalogController.cs | 10 ++-- .../TeamA/Apis/CatalogApi/CatalogEndpoints.cs | 52 +++++++++++++++++++ .../TeamA/Apis/Foundation/Extensions.cs | 15 ++++++ .../Apis/Foundation/GlobalSuppressions.cs | 1 + .../TeamA/Apis/Foundation/GlobalUsings.cs | 6 ++- .../TeamA/Apis/Foundation/Program.cs | 7 ++- 6 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs create mode 100644 src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Extensions.cs diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs index 418f54d..ca2934a 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs @@ -1,5 +1,4 @@ -using System.Collections.Immutable; -using Applicita.eShop.Contracts.CatalogContract; +using Applicita.eShop.Contracts.CatalogContract; namespace Applicita.eShop.Apis.CatalogApi; @@ -19,7 +18,8 @@ public async Task> CreateProduct(Product product) => CreatedAtAction(nameof(CreateProduct), await catalog.CreateProduct(product)); /// - /// Products for all 's currently in the catalog are returned; unknown product id's are skipped. + /// Products for all 's currently in the catalog are returned; + /// unknown product id's are skipped. /// If no 's are specified, all products in the catalog are returned /// [HttpGet(Products)] @@ -33,9 +33,7 @@ public async Task> GetProducts([FromQuery] int[]? id = n [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task UpdateProduct(Product product) - => product is null - ? BadRequest("product is required") - : (await catalog.UpdateProduct(product)) + => await catalog.UpdateProduct(product) ? Ok() : NotFound(product.Id); diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs new file mode 100644 index 0000000..7242fd7 --- /dev/null +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs @@ -0,0 +1,52 @@ +using Applicita.eShop.Contracts.CatalogContract; + +namespace Applicita.eShop.Apis.CatalogApi; + +public class CatalogEndpoints(IClusterClient orleans) : IEndpoints +{ + const string Products = "/products"; + const string Product = Products + "/{id}"; + + readonly ICatalogGrain catalog = orleans.GetGrain(ICatalogGrain.Key); + + public void Register(IEndpointRouteBuilder routeBuilder) + { + var group = routeBuilder.MapGroup("/mcatalog"); + _ = group.MapPost (Products, CreateProduct); + _ = group.MapGet (Products, GetProducts ).WithName(nameof(GetProducts)); + _ = group.MapPut (Products, UpdateProduct); + _ = group.MapDelete(Product , DeleteProduct); + } + + /// The new product is created with the returned id + async Task> CreateProduct(Product product) + { + int id = await catalog.CreateProduct(product); + return CreatedAtRoute(id, nameof(GetProducts), new { id }); + } + + /// + /// Products for all 's currently in the catalog are returned; + /// unknown product id's are skipped. + /// If no 's are specified, all products in the catalog are returned + /// + async Task>> GetProducts(int[]? id) => Ok( + id?.Length > 0 + ? await catalog.GetCurrentProducts([.. id]) + : await catalog.GetAllProducts() + ); + + /// The product is updated + /// The product id is not found + public async Task>> UpdateProduct(Product product) + => await catalog.UpdateProduct(product) + ? Ok() + : NotFound(product.Id); + + /// The product is deleted + /// The product id is not found + public async Task>> DeleteProduct(int id) + => await catalog.DeleteProduct(id) + ? Ok() + : NotFound(id); +} diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Extensions.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Extensions.cs new file mode 100644 index 0000000..fb919ba --- /dev/null +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Extensions.cs @@ -0,0 +1,15 @@ +namespace Applicita.eShop.Apis.Foundation; + +public interface IEndpoints +{ + void Register(IEndpointRouteBuilder routeBuilder); +} + +public static class WebApplicationExtensions +{ + public static void RegisterEndpoints(this WebApplication app, params Type[] endpointsTypes) + { + foreach (var endpointsType in endpointsTypes) + ((IEndpoints)ActivatorUtilities.CreateInstance(app.Services, endpointsType)).Register(app); + } +} diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalSuppressions.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalSuppressions.cs index a8fb5df..412f31a 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalSuppressions.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalSuppressions.cs @@ -6,3 +6,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant in ASP.NET Core")] +[assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Public methods are only invoked by ASP.NET Core, which ensures non-null parameter values")] diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalUsings.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalUsings.cs index bfd7588..8943884 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalUsings.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalUsings.cs @@ -1 +1,5 @@ -global using Microsoft.AspNetCore.Mvc; +global using System.Collections.Immutable; +global using Microsoft.AspNetCore.Http.HttpResults; +global using Microsoft.AspNetCore.Mvc; +global using static Microsoft.AspNetCore.Http.TypedResults; +global using Applicita.eShop.Apis.Foundation; diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs index dfc4aec..727a4df 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs @@ -1,4 +1,5 @@ -using Microsoft.OpenApi.Models; +using Applicita.eShop.Apis.CatalogApi; +using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); @@ -21,6 +22,10 @@ app.UseAuthorization(); +app.RegisterEndpoints( + typeof(CatalogEndpoints) +); + app.MapControllers(); app.Run(); From 31e41a38c3f26050aa7f6a4c8d1e0b9bc11f1889 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Thu, 11 Apr 2024 10:27:29 +0200 Subject: [PATCH 06/12] Add Minimal API's for Catalog --- .../TeamA/Apis/BasketApi/BasketsEndpoints.cs | 31 +++++++++++++++++++ .../TeamA/Apis/CatalogApi/CatalogEndpoints.cs | 2 +- .../TeamA/Apis/Foundation/Program.cs | 6 ++-- 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsEndpoints.cs diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsEndpoints.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsEndpoints.cs new file mode 100644 index 0000000..f9945dd --- /dev/null +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsEndpoints.cs @@ -0,0 +1,31 @@ +using Applicita.eShop.Contracts.BasketContract; + +namespace Applicita.eShop.Apis.BasketApi; + +public class BasketsEndpoints(IClusterClient orleans) : IEndpoints +{ + const string Basket = "{buyerId}"; + + public void Register(IEndpointRouteBuilder routeBuilder) + { + var group = routeBuilder.MapGroup("/mbaskets").WithTags("Baskets"); + _ = group.MapGet (Basket, GetBasket); + _ = group.MapPut ("" , UpdateBasket); + _ = group.MapDelete(Basket, EmptyBasket); + } + + /// The basket of buyerId is returned + public async Task> GetBasket(int buyerId) + => Ok(await BasketGrain(buyerId).GetBasket()); + + /// The updated basket is returned, with items updated from the current products in the Catalog service + public async Task> UpdateBasket(Basket basket) + => Ok(await BasketGrain(basket.BuyerId).UpdateBasket(basket)); + + /// The basket of buyerId is emptied + public async Task EmptyBasket(int buyerId) + { await BasketGrain(buyerId).EmptyBasket(); return Ok(); } + + IBasketGrain BasketGrain(int buyerId) + => orleans.GetBasketGrain(buyerId); +} diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs index 7242fd7..5f66b82 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs @@ -11,7 +11,7 @@ public class CatalogEndpoints(IClusterClient orleans) : IEndpoints public void Register(IEndpointRouteBuilder routeBuilder) { - var group = routeBuilder.MapGroup("/mcatalog"); + var group = routeBuilder.MapGroup("/mcatalog").WithTags("Catalog"); _ = group.MapPost (Products, CreateProduct); _ = group.MapGet (Products, GetProducts ).WithName(nameof(GetProducts)); _ = group.MapPut (Products, UpdateProduct); diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs index 727a4df..87a58e2 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs @@ -1,5 +1,4 @@ -using Applicita.eShop.Apis.CatalogApi; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); @@ -23,7 +22,8 @@ app.UseAuthorization(); app.RegisterEndpoints( - typeof(CatalogEndpoints) + typeof(Applicita.eShop.Apis.BasketApi.BasketsEndpoints), + typeof(Applicita.eShop.Apis.CatalogApi.CatalogEndpoints) ); app.MapControllers(); From bdf1bfc1ee216a03fb47d5f2c0301ad1449ba1a0 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Thu, 11 Apr 2024 11:15:48 +0200 Subject: [PATCH 07/12] Remove controllers --- README.md | 23 +++------ .../TeamA/Apis/BasketApi/BasketsController.cs | 33 ------------- .../TeamA/Apis/BasketApi/BasketsEndpoints.cs | 2 +- .../Apis/CatalogApi/CatalogController.cs | 49 ------------------- .../TeamA/Apis/CatalogApi/CatalogEndpoints.cs | 2 +- .../TeamA/Apis/Foundation/GlobalUsings.cs | 1 - .../TeamA/Apis/Foundation/Program.cs | 5 -- src/Example/eShopBySingleTeam/TeamA/Readme.md | 4 +- 8 files changed, 12 insertions(+), 107 deletions(-) delete mode 100644 src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsController.cs delete mode 100644 src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs diff --git a/README.md b/README.md index 0fd9d35..8f8e9dc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Backend ToolkitOrleans.Multiservice -Prevent microservices pain with logical service separation in a modular monolith for [Microsoft Orleans 7](https://learn.microsoft.com/en-us/dotnet/orleans/) +# Backend ToolkitOrleans.Multiservice +Prevent microservices pain with logical service separation in a modular monolith for [Microsoft Orleans 8](https://learn.microsoft.com/en-us/dotnet/orleans/) Orleans.Multiservice is an automated code structuring pattern for logical service separation within a Microsoft Orleans (micro)service. @@ -37,18 +37,18 @@ Orleans.Multiservice consists of: > The code analyzer / unit tests will be added in a future release. Note that the multiservice pattern can be used without the analyzer by following the code structure of the template and the [pattern rules](#pattern-rules) ## Template usage -1) On the command line, ensure that the [mcs-orleans-multiservice template](https://github.com/Applicita/Modern.CSharp.Templates#readme) is installed
(note that below is .NET 7 cli syntax; Orleans 7 requires .NET 7): +1) On the command line, ensure that the [mcs-orleans-multiservice template](https://github.com/Applicita/Modern.CSharp.Templates#readme) is installed: ``` dotnet new install Modern.CSharp.Templates ``` **Note** that the `dotnet new mcs-orleans-multiservice` template requires **PowerShell** to be installed -2) Type this command to read the documentation for the template parameters: +2) Enter this command to read the documentation for the template parameters: ``` dotnet new mcs-orleans-multiservice -h ``` -3) To create a new multiservice with one logical service in it, type e.g.: +3) To create a new multiservice with one logical service in it, enter e.g.: ``` dotnet new mcs-orleans-multiservice --RootNamespace Applicita.eShop --Multiservice TeamA --Logicalservice Catalog --allow-scripts Yes ``` @@ -82,8 +82,8 @@ Single team solution: - Debug [eShopTeamA.sln](https://github.com/Applicita/Orleans.Multiservice/tree/main/src/Example/eShopBySingleTeam/TeamA) Two team solution: -- Ensure you have the [.NET OpenAPI tool](https://learn.microsoft.com/en-us/aspnet/core/web-api/microsoft.dotnet-openapi?view=aspnetcore-7.0) installed for .NET 7: - `dotnet tool install --global Microsoft.dotnet-openapi --version 7.0.0` +- Ensure you have the latest [.NET OpenAPI tool](https://learn.microsoft.com/en-us/aspnet/core/web-api/microsoft.dotnet-openapi?view=aspnetcore-8.0) for .NET 8 installed: + `dotnet tool install --global Microsoft.dotnet-openapi` On build, this will generate the `CatalogServiceClient` from `CatalogService.json` - Debug [eShopTeamAof2.sln](https://github.com/Applicita/Orleans.Multiservice/tree/main/src/Example/eShopByTwoTeams/TeamA) and [eShopTeamBof2.sln](https://github.com/Applicita/Orleans.Multiservice/tree/main/src/Example/eShopByTwoTeams/TeamB) @@ -102,7 +102,7 @@ These rules ensure that the pattern remains intact: `*Service -> Contracts` 2) These types are only allowed in specific namespaces:
- All API controllers must be in or under `Apis.Api`
+ All API endpoints must be in or under `Apis.Api`
All `public` grain contracts must be in or under `Contracts.Contract`
3) References between types in these namespaces are **not** allowed:
@@ -112,10 +112,3 @@ These rules ensure that the pattern remains intact: 4) The `public` keyword in `*Service` projects is *only* used on interface member implementations, grain constructors and serializable members in a type.
This ensures that the only external code access is Orleans instantiating grains. It makes it safe to reference the service implementation projects in the silo host project (Apis) to let Orleans locate the grain implementations; the types in the service implementation projects will not be available in the silo host project. - - -The Roslyn analyzer will allow rules to be configured in `.editorconfig` - - - - diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsController.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsController.cs deleted file mode 100644 index 9b346c7..0000000 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Applicita.eShop.Contracts.BasketContract; - -namespace Applicita.eShop.Apis.BasketApi; - -[Route("[controller]")] -[ApiController] -public class BasketsController(IClusterClient orleans) : ControllerBase -{ - const string Basket = "{buyerId}"; - - /// The basket of buyerId is returned - [HttpGet(Basket)] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetBasket(int buyerId) - => Ok(await BasketGrain(buyerId).GetBasket()); - - /// The updated basket is returned, with items updated from the current products in the Catalog service - [HttpPut()] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> UpdateBasket(Basket basket) - => basket is null - ? BadRequest("basket is required") - : Ok(await BasketGrain(basket.BuyerId).UpdateBasket(basket)); - - /// The basket of buyerId is emptied - [HttpDelete(Basket)] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task EmptyBasket(int buyerId) - { await BasketGrain(buyerId).EmptyBasket(); return Ok(); } - - IBasketGrain BasketGrain(int buyerId) - => orleans.GetBasketGrain(buyerId); -} diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsEndpoints.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsEndpoints.cs index f9945dd..e22f928 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsEndpoints.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/BasketApi/BasketsEndpoints.cs @@ -8,7 +8,7 @@ public class BasketsEndpoints(IClusterClient orleans) : IEndpoints public void Register(IEndpointRouteBuilder routeBuilder) { - var group = routeBuilder.MapGroup("/mbaskets").WithTags("Baskets"); + var group = routeBuilder.MapGroup("/baskets").WithTags("Baskets"); _ = group.MapGet (Basket, GetBasket); _ = group.MapPut ("" , UpdateBasket); _ = group.MapDelete(Basket, EmptyBasket); diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs deleted file mode 100644 index ca2934a..0000000 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogController.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Applicita.eShop.Contracts.CatalogContract; - -namespace Applicita.eShop.Apis.CatalogApi; - -[Route("[controller]")] -[ApiController] -public class CatalogController(IClusterClient orleans) : ControllerBase -{ - const string Products = "products"; - const string Product = Products + "/{id}"; - - readonly ICatalogGrain catalog = orleans.GetGrain(ICatalogGrain.Key); - - /// The new product is created with the returned id - [HttpPost(Products)] - [ProducesResponseType(StatusCodes.Status201Created)] - public async Task> CreateProduct(Product product) - => CreatedAtAction(nameof(CreateProduct), await catalog.CreateProduct(product)); - - /// - /// Products for all 's currently in the catalog are returned; - /// unknown product id's are skipped. - /// If no 's are specified, all products in the catalog are returned - /// - [HttpGet(Products)] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetProducts([FromQuery] int[]? id = null) - => await (id is null ? catalog.GetAllProducts() : catalog.GetCurrentProducts([.. id])); - - /// The product is updated - /// The product id is not found - [HttpPut(Products)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task UpdateProduct(Product product) - => await catalog.UpdateProduct(product) - ? Ok() - : NotFound(product.Id); - - /// The product is deleted - /// The product id is not found - [HttpDelete(Product)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteProduct(int id) - => (await catalog.DeleteProduct(id)) - ? Ok() - : NotFound(id); -} diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs index 5f66b82..e26baae 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/CatalogApi/CatalogEndpoints.cs @@ -11,7 +11,7 @@ public class CatalogEndpoints(IClusterClient orleans) : IEndpoints public void Register(IEndpointRouteBuilder routeBuilder) { - var group = routeBuilder.MapGroup("/mcatalog").WithTags("Catalog"); + var group = routeBuilder.MapGroup("/catalog").WithTags("Catalog"); _ = group.MapPost (Products, CreateProduct); _ = group.MapGet (Products, GetProducts ).WithName(nameof(GetProducts)); _ = group.MapPut (Products, UpdateProduct); diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalUsings.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalUsings.cs index 8943884..e949018 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalUsings.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/GlobalUsings.cs @@ -1,5 +1,4 @@ global using System.Collections.Immutable; global using Microsoft.AspNetCore.Http.HttpResults; -global using Microsoft.AspNetCore.Mvc; global using static Microsoft.AspNetCore.Http.TypedResults; global using Applicita.eShop.Apis.Foundation; diff --git a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs index 87a58e2..694cf6f 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs +++ b/src/Example/eShopBySingleTeam/TeamA/Apis/Foundation/Program.cs @@ -7,7 +7,6 @@ .AddMemoryGrainStorageAsDefault() ); -builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml")); @@ -19,13 +18,9 @@ if (app.Environment.IsDevelopment()) _ = app.UseSwagger().UseSwaggerUI(options => options.EnableTryItOutByDefault()); -app.UseAuthorization(); - app.RegisterEndpoints( typeof(Applicita.eShop.Apis.BasketApi.BasketsEndpoints), typeof(Applicita.eShop.Apis.CatalogApi.CatalogEndpoints) ); -app.MapControllers(); - app.Run(); diff --git a/src/Example/eShopBySingleTeam/TeamA/Readme.md b/src/Example/eShopBySingleTeam/TeamA/Readme.md index fd08317..173db17 100644 --- a/src/Example/eShopBySingleTeam/TeamA/Readme.md +++ b/src/Example/eShopBySingleTeam/TeamA/Readme.md @@ -1,4 +1,4 @@ -# TeamA multiservice +# TeamA multiservice ## The multiservice pattern This solution follows the [Multiservice pattern for Microsoft Orleans](https://github.com/Applicita/Orleans.Multiservice#readme); it was generated with [Modern.CSharp.Templates 1.1.0](https://www.nuget.org/packages/Modern.CSharp.Templates/1.1.0) by this command: @@ -9,6 +9,6 @@ The `BasketService` was added with this PowerShell command: `.\AddLogicalService.ps1 Basket` -See the [pattern rules](https://github.com/Applicita/Orleans.Multiservice#pattern-rules) for how to structure code in this solution (this will be supported by a Roslyn code analyzer in a later template version). +See the [pattern rules](https://github.com/Applicita/Orleans.Multiservice#pattern-rules) for how to structure code in this solution (this will be guarded by automation in a later template version). Use [`AddLogicalService.ps1 `](AddLogicalService.ps1) to add more logical services to the solution. \ No newline at end of file From 3183523cf1b0ad93f29dc419f14488fbdb052beb Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Thu, 11 Apr 2024 12:37:23 +0200 Subject: [PATCH 08/12] Update examples for two teams from controllers to minimal APIs --- .../eShopByTwoTeams/TeamA/Apis/Apis.csproj | 1 + .../TeamA/Apis/BasketApi/BasketsController.cs | 33 ------------ .../TeamA/Apis/BasketApi/BasketsEndpoints.cs | 31 +++++++++++ .../TeamA/Apis/Foundation/Extensions.cs | 15 ++++++ .../Apis/Foundation/GlobalSuppressions.cs | 1 + .../TeamA/Apis/Foundation/GlobalUsings.cs | 4 +- .../TeamA/Apis/Foundation/Program.cs | 7 ++- .../eShopByTwoTeams/TeamB/Apis/Apis.csproj | 3 +- .../Apis/CatalogApi/CatalogController.cs | 51 ------------------ .../TeamB/Apis/CatalogApi/CatalogEndpoints.cs | 52 +++++++++++++++++++ .../TeamB/Apis/Foundation/Extensions.cs | 15 ++++++ .../Apis/Foundation/GlobalSuppressions.cs | 1 + .../TeamB/Apis/Foundation/GlobalUsings.cs | 5 +- .../TeamB/Apis/Foundation/Program.cs | 7 ++- 14 files changed, 131 insertions(+), 95 deletions(-) delete mode 100644 src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsController.cs create mode 100644 src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsEndpoints.cs create mode 100644 src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Extensions.cs delete mode 100644 src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogController.cs create mode 100644 src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogEndpoints.cs create mode 100644 src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Extensions.cs diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/Apis.csproj b/src/Example/eShopByTwoTeams/TeamA/Apis/Apis.csproj index deb4610..fdf5188 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Apis/Apis.csproj +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/Apis.csproj @@ -26,6 +26,7 @@
+ diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsController.cs b/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsController.cs deleted file mode 100644 index 9b346c7..0000000 --- a/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Applicita.eShop.Contracts.BasketContract; - -namespace Applicita.eShop.Apis.BasketApi; - -[Route("[controller]")] -[ApiController] -public class BasketsController(IClusterClient orleans) : ControllerBase -{ - const string Basket = "{buyerId}"; - - /// The basket of buyerId is returned - [HttpGet(Basket)] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetBasket(int buyerId) - => Ok(await BasketGrain(buyerId).GetBasket()); - - /// The updated basket is returned, with items updated from the current products in the Catalog service - [HttpPut()] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> UpdateBasket(Basket basket) - => basket is null - ? BadRequest("basket is required") - : Ok(await BasketGrain(basket.BuyerId).UpdateBasket(basket)); - - /// The basket of buyerId is emptied - [HttpDelete(Basket)] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task EmptyBasket(int buyerId) - { await BasketGrain(buyerId).EmptyBasket(); return Ok(); } - - IBasketGrain BasketGrain(int buyerId) - => orleans.GetBasketGrain(buyerId); -} diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsEndpoints.cs b/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsEndpoints.cs new file mode 100644 index 0000000..e22f928 --- /dev/null +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/BasketApi/BasketsEndpoints.cs @@ -0,0 +1,31 @@ +using Applicita.eShop.Contracts.BasketContract; + +namespace Applicita.eShop.Apis.BasketApi; + +public class BasketsEndpoints(IClusterClient orleans) : IEndpoints +{ + const string Basket = "{buyerId}"; + + public void Register(IEndpointRouteBuilder routeBuilder) + { + var group = routeBuilder.MapGroup("/baskets").WithTags("Baskets"); + _ = group.MapGet (Basket, GetBasket); + _ = group.MapPut ("" , UpdateBasket); + _ = group.MapDelete(Basket, EmptyBasket); + } + + /// The basket of buyerId is returned + public async Task> GetBasket(int buyerId) + => Ok(await BasketGrain(buyerId).GetBasket()); + + /// The updated basket is returned, with items updated from the current products in the Catalog service + public async Task> UpdateBasket(Basket basket) + => Ok(await BasketGrain(basket.BuyerId).UpdateBasket(basket)); + + /// The basket of buyerId is emptied + public async Task EmptyBasket(int buyerId) + { await BasketGrain(buyerId).EmptyBasket(); return Ok(); } + + IBasketGrain BasketGrain(int buyerId) + => orleans.GetBasketGrain(buyerId); +} diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Extensions.cs b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Extensions.cs new file mode 100644 index 0000000..fb919ba --- /dev/null +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Extensions.cs @@ -0,0 +1,15 @@ +namespace Applicita.eShop.Apis.Foundation; + +public interface IEndpoints +{ + void Register(IEndpointRouteBuilder routeBuilder); +} + +public static class WebApplicationExtensions +{ + public static void RegisterEndpoints(this WebApplication app, params Type[] endpointsTypes) + { + foreach (var endpointsType in endpointsTypes) + ((IEndpoints)ActivatorUtilities.CreateInstance(app.Services, endpointsType)).Register(app); + } +} diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalSuppressions.cs b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalSuppressions.cs index a8fb5df..412f31a 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalSuppressions.cs +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalSuppressions.cs @@ -6,3 +6,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant in ASP.NET Core")] +[assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Public methods are only invoked by ASP.NET Core, which ensures non-null parameter values")] diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalUsings.cs b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalUsings.cs index bfd7588..b4c6c37 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalUsings.cs +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/GlobalUsings.cs @@ -1 +1,3 @@ -global using Microsoft.AspNetCore.Mvc; +global using Microsoft.AspNetCore.Http.HttpResults; +global using static Microsoft.AspNetCore.Http.TypedResults; +global using Applicita.eShop.Apis.Foundation; diff --git a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Program.cs b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Program.cs index 5241234..d1e1938 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Program.cs +++ b/src/Example/eShopByTwoTeams/TeamA/Apis/Foundation/Program.cs @@ -7,7 +7,6 @@ .AddMemoryGrainStorageAsDefault() ); -builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml")); @@ -19,8 +18,8 @@ if (app.Environment.IsDevelopment()) _ = app.UseSwagger().UseSwaggerUI(options => options.EnableTryItOutByDefault()); -app.UseAuthorization(); - -app.MapControllers(); +app.RegisterEndpoints( + typeof(Applicita.eShop.Apis.BasketApi.BasketsEndpoints) +); app.Run(); diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/Apis.csproj b/src/Example/eShopByTwoTeams/TeamB/Apis/Apis.csproj index 156c935..4024169 100644 --- a/src/Example/eShopByTwoTeams/TeamB/Apis/Apis.csproj +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/Apis.csproj @@ -9,7 +9,7 @@ preview-All true - Applicita.eShop.$(MSBuildProjectName).TeamB + Applicita.eShop.$(MSBuildProjectName).TeamA Applicita.eShop.$(MSBuildProjectName.Replace(" ", "_")) @@ -26,6 +26,7 @@
+ diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogController.cs b/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogController.cs deleted file mode 100644 index 418f54d..0000000 --- a/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogController.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Immutable; -using Applicita.eShop.Contracts.CatalogContract; - -namespace Applicita.eShop.Apis.CatalogApi; - -[Route("[controller]")] -[ApiController] -public class CatalogController(IClusterClient orleans) : ControllerBase -{ - const string Products = "products"; - const string Product = Products + "/{id}"; - - readonly ICatalogGrain catalog = orleans.GetGrain(ICatalogGrain.Key); - - /// The new product is created with the returned id - [HttpPost(Products)] - [ProducesResponseType(StatusCodes.Status201Created)] - public async Task> CreateProduct(Product product) - => CreatedAtAction(nameof(CreateProduct), await catalog.CreateProduct(product)); - - /// - /// Products for all 's currently in the catalog are returned; unknown product id's are skipped. - /// If no 's are specified, all products in the catalog are returned - /// - [HttpGet(Products)] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetProducts([FromQuery] int[]? id = null) - => await (id is null ? catalog.GetAllProducts() : catalog.GetCurrentProducts([.. id])); - - /// The product is updated - /// The product id is not found - [HttpPut(Products)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task UpdateProduct(Product product) - => product is null - ? BadRequest("product is required") - : (await catalog.UpdateProduct(product)) - ? Ok() - : NotFound(product.Id); - - /// The product is deleted - /// The product id is not found - [HttpDelete(Product)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteProduct(int id) - => (await catalog.DeleteProduct(id)) - ? Ok() - : NotFound(id); -} diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogEndpoints.cs b/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogEndpoints.cs new file mode 100644 index 0000000..e26baae --- /dev/null +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/CatalogApi/CatalogEndpoints.cs @@ -0,0 +1,52 @@ +using Applicita.eShop.Contracts.CatalogContract; + +namespace Applicita.eShop.Apis.CatalogApi; + +public class CatalogEndpoints(IClusterClient orleans) : IEndpoints +{ + const string Products = "/products"; + const string Product = Products + "/{id}"; + + readonly ICatalogGrain catalog = orleans.GetGrain(ICatalogGrain.Key); + + public void Register(IEndpointRouteBuilder routeBuilder) + { + var group = routeBuilder.MapGroup("/catalog").WithTags("Catalog"); + _ = group.MapPost (Products, CreateProduct); + _ = group.MapGet (Products, GetProducts ).WithName(nameof(GetProducts)); + _ = group.MapPut (Products, UpdateProduct); + _ = group.MapDelete(Product , DeleteProduct); + } + + /// The new product is created with the returned id + async Task> CreateProduct(Product product) + { + int id = await catalog.CreateProduct(product); + return CreatedAtRoute(id, nameof(GetProducts), new { id }); + } + + /// + /// Products for all 's currently in the catalog are returned; + /// unknown product id's are skipped. + /// If no 's are specified, all products in the catalog are returned + /// + async Task>> GetProducts(int[]? id) => Ok( + id?.Length > 0 + ? await catalog.GetCurrentProducts([.. id]) + : await catalog.GetAllProducts() + ); + + /// The product is updated + /// The product id is not found + public async Task>> UpdateProduct(Product product) + => await catalog.UpdateProduct(product) + ? Ok() + : NotFound(product.Id); + + /// The product is deleted + /// The product id is not found + public async Task>> DeleteProduct(int id) + => await catalog.DeleteProduct(id) + ? Ok() + : NotFound(id); +} diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Extensions.cs b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Extensions.cs new file mode 100644 index 0000000..fb919ba --- /dev/null +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Extensions.cs @@ -0,0 +1,15 @@ +namespace Applicita.eShop.Apis.Foundation; + +public interface IEndpoints +{ + void Register(IEndpointRouteBuilder routeBuilder); +} + +public static class WebApplicationExtensions +{ + public static void RegisterEndpoints(this WebApplication app, params Type[] endpointsTypes) + { + foreach (var endpointsType in endpointsTypes) + ((IEndpoints)ActivatorUtilities.CreateInstance(app.Services, endpointsType)).Register(app); + } +} diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalSuppressions.cs b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalSuppressions.cs index a8fb5df..412f31a 100644 --- a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalSuppressions.cs +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalSuppressions.cs @@ -6,3 +6,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Not relevant in ASP.NET Core")] +[assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Public methods are only invoked by ASP.NET Core, which ensures non-null parameter values")] diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalUsings.cs b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalUsings.cs index bfd7588..e949018 100644 --- a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalUsings.cs +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/GlobalUsings.cs @@ -1 +1,4 @@ -global using Microsoft.AspNetCore.Mvc; +global using System.Collections.Immutable; +global using Microsoft.AspNetCore.Http.HttpResults; +global using static Microsoft.AspNetCore.Http.TypedResults; +global using Applicita.eShop.Apis.Foundation; diff --git a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Program.cs b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Program.cs index 0b9c34f..3088c19 100644 --- a/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Program.cs +++ b/src/Example/eShopByTwoTeams/TeamB/Apis/Foundation/Program.cs @@ -7,7 +7,6 @@ .AddMemoryGrainStorageAsDefault() ); -builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml")); @@ -19,8 +18,8 @@ if (app.Environment.IsDevelopment()) _ = app.UseSwagger().UseSwaggerUI(options => options.EnableTryItOutByDefault()); -app.UseAuthorization(); - -app.MapControllers(); +app.RegisterEndpoints( + typeof(Applicita.eShop.Apis.CatalogApi.CatalogEndpoints) +); app.Run(); From 5debed63c1b4c25bbdfa3f297cf3ac933913d184 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Thu, 11 Apr 2024 13:08:57 +0200 Subject: [PATCH 09/12] Update readme code screenshot to C# 12 --- ...client-within-and-across-microservices.png | Bin 141427 -> 138871 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/img/Example-service-client-within-and-across-microservices.png b/img/Example-service-client-within-and-across-microservices.png index 935b01ab584fe47d3728611a3c2b94896bd709a3..f1faf55123c5dba99ce50c934e5c955f33ac1d2b 100644 GIT binary patch delta 60579 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 From a1365e15fead48a87a71c068740379ffdc1c16cf Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Mon, 15 Apr 2024 11:50:07 +0200 Subject: [PATCH 10/12] remove explicit serialization Id's from record types since they are supported implicitly by Orleans --- .../TeamA/Contracts/BasketContract/BasketContract.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Example/eShopByTwoTeams/TeamA/Contracts/BasketContract/BasketContract.cs b/src/Example/eShopByTwoTeams/TeamA/Contracts/BasketContract/BasketContract.cs index bf0583a..f84dacf 100644 --- a/src/Example/eShopByTwoTeams/TeamA/Contracts/BasketContract/BasketContract.cs +++ b/src/Example/eShopByTwoTeams/TeamA/Contracts/BasketContract/BasketContract.cs @@ -21,13 +21,7 @@ public interface IBasketGrain : IGrainWithStringKey } [GenerateSerializer, Immutable] -public record Basket( - [property: Id(0)] int BuyerId, - [property: Id(1)] ImmutableArray Items); +public record Basket(int BuyerId, ImmutableArray Items); [GenerateSerializer, Immutable] -public record BasketItem( - [property: Id(0)] int ProductId, - [property: Id(1)] string ProductName, - [property: Id(2)] decimal UnitPrice, - [property: Id(3)] int Quantity); +public record BasketItem(int ProductId, string ProductName, decimal UnitPrice, int Quantity); From a5303d68193c885cfc77a649aeac946711c4e6a5 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Mon, 15 Apr 2024 15:47:00 +0200 Subject: [PATCH 11/12] update AddLogicalService.ps1 in Example to Modern.CSharp.Templates version 2.0.0: adds automatic endpoint registration --- .../TeamA/AddLogicalService.ps1 | 28 +++++++++++++++++-- .../TeamA/AddLogicalService.ps1 | 28 +++++++++++++++++-- .../TeamB/AddLogicalService.ps1 | 28 +++++++++++++++++-- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/src/Example/eShopBySingleTeam/TeamA/AddLogicalService.ps1 b/src/Example/eShopBySingleTeam/TeamA/AddLogicalService.ps1 index eea631e..879db95 100644 --- a/src/Example/eShopBySingleTeam/TeamA/AddLogicalService.ps1 +++ b/src/Example/eShopBySingleTeam/TeamA/AddLogicalService.ps1 @@ -1,6 +1,30 @@ -Param( +Param( [Parameter(Mandatory, HelpMessage="The name (without 'Service' suffix) of the logical service to add to the CoreTeam multiservice solution in the current directory; used in the name of the new service project and in new namespaces + classes in the Apis and Contracts projects")] [string] $Name ) -dotnet new mcs-orleans-multiservice --RootNamespace Applicita.eShop -M . --Logicalservice $Name --allow-scripts Yes \ No newline at end of file + +# Function to update the Program.cs file to add a new parameter to the RegisterEndpoints method +function Update-RegisterEndpoints { + $newParameter = "`n typeof(Applicita.eShop.Apis.${Name}Api.${Name}Endpoints)`n" + $apisDirectory = Join-Path -Path $PWD -ChildPath "Apis" + $programFile = Get-ChildItem -Path $apisDirectory -Recurse -Filter "Program.cs" -ErrorAction SilentlyContinue | Select-Object -First 1 + + if ($programFile -ne $null) { + $programContent = Get-Content -Path $programFile.FullName -Raw + $pattern = "(?s)(app\s*\.RegisterEndpoints\s*\(.+?\))\s*\)" + $modifiedContent = $programContent -replace $pattern, "`$1,$newParameter)" + + if ($modifiedContent -ne $programContent) { + Set-Content -Path $programFile.FullName -Value $modifiedContent + Write-Output "Successfully added new parameter to RegisterEndpoints call in $($programFile.FullName):$newParameter" + return + } + } + + Write-Warning "Could not automatically add below parameter to the RegisterEndpoints(...) call; please add it manually:$newParameter" +} + +dotnet new mcs-orleans-multiservice --RootNamespace Applicita.eShop -M . --Logicalservice $Name --allow-scripts Yes + +Update-RegisterEndpoints diff --git a/src/Example/eShopByTwoTeams/TeamA/AddLogicalService.ps1 b/src/Example/eShopByTwoTeams/TeamA/AddLogicalService.ps1 index eea631e..879db95 100644 --- a/src/Example/eShopByTwoTeams/TeamA/AddLogicalService.ps1 +++ b/src/Example/eShopByTwoTeams/TeamA/AddLogicalService.ps1 @@ -1,6 +1,30 @@ -Param( +Param( [Parameter(Mandatory, HelpMessage="The name (without 'Service' suffix) of the logical service to add to the CoreTeam multiservice solution in the current directory; used in the name of the new service project and in new namespaces + classes in the Apis and Contracts projects")] [string] $Name ) -dotnet new mcs-orleans-multiservice --RootNamespace Applicita.eShop -M . --Logicalservice $Name --allow-scripts Yes \ No newline at end of file + +# Function to update the Program.cs file to add a new parameter to the RegisterEndpoints method +function Update-RegisterEndpoints { + $newParameter = "`n typeof(Applicita.eShop.Apis.${Name}Api.${Name}Endpoints)`n" + $apisDirectory = Join-Path -Path $PWD -ChildPath "Apis" + $programFile = Get-ChildItem -Path $apisDirectory -Recurse -Filter "Program.cs" -ErrorAction SilentlyContinue | Select-Object -First 1 + + if ($programFile -ne $null) { + $programContent = Get-Content -Path $programFile.FullName -Raw + $pattern = "(?s)(app\s*\.RegisterEndpoints\s*\(.+?\))\s*\)" + $modifiedContent = $programContent -replace $pattern, "`$1,$newParameter)" + + if ($modifiedContent -ne $programContent) { + Set-Content -Path $programFile.FullName -Value $modifiedContent + Write-Output "Successfully added new parameter to RegisterEndpoints call in $($programFile.FullName):$newParameter" + return + } + } + + Write-Warning "Could not automatically add below parameter to the RegisterEndpoints(...) call; please add it manually:$newParameter" +} + +dotnet new mcs-orleans-multiservice --RootNamespace Applicita.eShop -M . --Logicalservice $Name --allow-scripts Yes + +Update-RegisterEndpoints diff --git a/src/Example/eShopByTwoTeams/TeamB/AddLogicalService.ps1 b/src/Example/eShopByTwoTeams/TeamB/AddLogicalService.ps1 index eea631e..879db95 100644 --- a/src/Example/eShopByTwoTeams/TeamB/AddLogicalService.ps1 +++ b/src/Example/eShopByTwoTeams/TeamB/AddLogicalService.ps1 @@ -1,6 +1,30 @@ -Param( +Param( [Parameter(Mandatory, HelpMessage="The name (without 'Service' suffix) of the logical service to add to the CoreTeam multiservice solution in the current directory; used in the name of the new service project and in new namespaces + classes in the Apis and Contracts projects")] [string] $Name ) -dotnet new mcs-orleans-multiservice --RootNamespace Applicita.eShop -M . --Logicalservice $Name --allow-scripts Yes \ No newline at end of file + +# Function to update the Program.cs file to add a new parameter to the RegisterEndpoints method +function Update-RegisterEndpoints { + $newParameter = "`n typeof(Applicita.eShop.Apis.${Name}Api.${Name}Endpoints)`n" + $apisDirectory = Join-Path -Path $PWD -ChildPath "Apis" + $programFile = Get-ChildItem -Path $apisDirectory -Recurse -Filter "Program.cs" -ErrorAction SilentlyContinue | Select-Object -First 1 + + if ($programFile -ne $null) { + $programContent = Get-Content -Path $programFile.FullName -Raw + $pattern = "(?s)(app\s*\.RegisterEndpoints\s*\(.+?\))\s*\)" + $modifiedContent = $programContent -replace $pattern, "`$1,$newParameter)" + + if ($modifiedContent -ne $programContent) { + Set-Content -Path $programFile.FullName -Value $modifiedContent + Write-Output "Successfully added new parameter to RegisterEndpoints call in $($programFile.FullName):$newParameter" + return + } + } + + Write-Warning "Could not automatically add below parameter to the RegisterEndpoints(...) call; please add it manually:$newParameter" +} + +dotnet new mcs-orleans-multiservice --RootNamespace Applicita.eShop -M . --Logicalservice $Name --allow-scripts Yes + +Update-RegisterEndpoints From 6e9563edc8fcdff7c6f8f7b309487bae0adaf051 Mon Sep 17 00:00:00 2001 From: Vincent Hoogendoorn Date: Tue, 16 Apr 2024 09:13:27 +0200 Subject: [PATCH 12/12] readme formatting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8f8e9dc..9ff499b 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,8 @@ Single team solution: - Debug [eShopTeamA.sln](https://github.com/Applicita/Orleans.Multiservice/tree/main/src/Example/eShopBySingleTeam/TeamA) Two team solution: -- Ensure you have the latest [.NET OpenAPI tool](https://learn.microsoft.com/en-us/aspnet/core/web-api/microsoft.dotnet-openapi?view=aspnetcore-8.0) for .NET 8 installed: - `dotnet tool install --global Microsoft.dotnet-openapi` +- Ensure you have the latest [.NET OpenAPI tool](https://learn.microsoft.com/en-us/aspnet/core/web-api/microsoft.dotnet-openapi?view=aspnetcore-8.0) for .NET 8 installed:
+ `dotnet tool install --global Microsoft.dotnet-openapi`
On build, this will generate the `CatalogServiceClient` from `CatalogService.json` - Debug [eShopTeamAof2.sln](https://github.com/Applicita/Orleans.Multiservice/tree/main/src/Example/eShopByTwoTeams/TeamA) and [eShopTeamBof2.sln](https://github.com/Applicita/Orleans.Multiservice/tree/main/src/Example/eShopByTwoTeams/TeamB)