From 191c5bb8f1ebeb096464e3aab82fe378bbc4060b Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 00:32:12 -0400 Subject: [PATCH 01/13] Revert sample projects --- src/Panner.AspNetCore.csproj | 8 ++++---- .../Panner.AspNetCore.Tests.csproj | 16 ++++++++-------- .../ParticlesModelBinderWorks.cs | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Panner.AspNetCore.csproj b/src/Panner.AspNetCore.csproj index ddcc438..cca3b70 100644 --- a/src/Panner.AspNetCore.csproj +++ b/src/Panner.AspNetCore.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;net5.0 + net8.0;net9.0 Panner.AspNetCore Panner.AspNetCore OSDKDev @@ -15,7 +15,7 @@ Copyright (c) 2019 OSDKDev Sorting and filtering made easy for your ASP.NET Core project! Go from a CSV input to a filtered/sorted IQueryable with no effort. true - 8.0 + 12.0 @@ -25,8 +25,8 @@ - - + + diff --git a/tests/Panner.AspNetCore.Tests/Panner.AspNetCore.Tests.csproj b/tests/Panner.AspNetCore.Tests/Panner.AspNetCore.Tests.csproj index 1371a55..a4404c5 100644 --- a/tests/Panner.AspNetCore.Tests/Panner.AspNetCore.Tests.csproj +++ b/tests/Panner.AspNetCore.Tests/Panner.AspNetCore.Tests.csproj @@ -1,18 +1,18 @@  - netcoreapp3.1;net5.0 - 8.0 + net8.0;net9.0 + 12.0 false - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Panner.AspNetCore.Tests/ParticlesModelBinderWorks.cs b/tests/Panner.AspNetCore.Tests/ParticlesModelBinderWorks.cs index bf420c0..561eb14 100644 --- a/tests/Panner.AspNetCore.Tests/ParticlesModelBinderWorks.cs +++ b/tests/Panner.AspNetCore.Tests/ParticlesModelBinderWorks.cs @@ -41,13 +41,13 @@ public void NullIPContextThrows() } [Fact] - public void NullBindingContextThrows() + public async Task NullBindingContextThrows() { var x = Create(GetPannerContext()); - Assert.Throws(() => + await Assert.ThrowsAsync(async () => { - x.BindModelAsync(null); + await x.BindModelAsync(null); }); } From 68eb786c3056439f3e0259fd470846d2dabe457a Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 00:39:54 -0400 Subject: [PATCH 02/13] Specify 9.x SDK in CI --- .github/workflows/build-and-package.yml | 2 ++ .github/workflows/test-and-report.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build-and-package.yml b/.github/workflows/build-and-package.yml index 7a783be..6826a20 100644 --- a/.github/workflows/build-and-package.yml +++ b/.github/workflows/build-and-package.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Install Dotnet uses: actions/setup-dotnet@v1 + with: + dotnet-version: 9.x - name: Check-out uses: actions/checkout@master - name: Building (Release) diff --git a/.github/workflows/test-and-report.yml b/.github/workflows/test-and-report.yml index bba5294..cc323a4 100644 --- a/.github/workflows/test-and-report.yml +++ b/.github/workflows/test-and-report.yml @@ -18,6 +18,8 @@ jobs: uses: actions/checkout@master - name: Install Dotnet uses: actions/setup-dotnet@v1 + with: + dotnet-version: 9.x - name: Install ReportGenerator run: dotnet tool install dotnet-reportgenerator-globaltool --tool-path tools - name: Test From ea7e70c599108895873aa705053230f93b4bd712 Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 00:55:47 -0400 Subject: [PATCH 03/13] Use floating Panner 3.* --- src/Panner.AspNetCore.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Panner.AspNetCore.csproj b/src/Panner.AspNetCore.csproj index cca3b70..96eaa75 100644 --- a/src/Panner.AspNetCore.csproj +++ b/src/Panner.AspNetCore.csproj @@ -26,7 +26,7 @@ - + From 1db6a54bd954e62d0c30311ad578938ecc944527 Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 01:47:43 -0400 Subject: [PATCH 04/13] Added sample projects --- .../.idea.Panner.AspNetCore/.idea/.gitignore | 13 ++ .../.idea/indexLayout.xml | 8 + .idea/.idea.Panner.AspNetCore/.idea/vcs.xml | 7 + Panner.AspNetCore.sln | 35 ++++ sample/FastEndpoints/.editorconfig | 188 ++++++++++++++++++ sample/FastEndpoints/FastEndpoints.sln | 31 +++ .../Source/FastEndpointsNet9.csproj | 21 ++ .../Source/Features/SayHello/Endpoint.cs | 18 ++ .../Source/Features/SayHello/Request.cs | 18 ++ .../Source/Features/SayHello/Response.cs | 6 + sample/FastEndpoints/Source/Meta.cs | 9 + sample/FastEndpoints/Source/Program.cs | 17 ++ .../Source/Properties/launchSettings.json | 14 ++ .../Source/appsettings.Development.json | 11 + .../Source/appsettings.Testing.json | 3 + sample/FastEndpoints/Source/appsettings.json | 8 + samples/WebAPINet8MinimalFluent/Program.cs | 44 ++++ .../Properties/launchSettings.json | 41 ++++ .../WebAPINet8MinimalFluent.csproj | 17 ++ .../WebAPINet8MinimalFluent.http | 6 + .../appsettings.Development.json | 8 + .../WebAPINet8MinimalFluent/appsettings.json | 9 + .../Controllers/WeatherForecastController.cs | 32 +++ samples/WebApi8ControllersFluent/Program.cs | 25 +++ .../Properties/launchSettings.json | 41 ++++ .../WeatherForecast.cs | 12 ++ .../WebApi8ControllersFluent.csproj | 13 ++ .../WebApi8ControllersFluent.http | 6 + .../appsettings.Development.json | 8 + .../WebApi8ControllersFluent/appsettings.json | 9 + .../Controllers/WeatherForecastController.cs | 32 +++ .../WebApiNet9ControllersFluent/Program.cs | 23 +++ .../Properties/launchSettings.json | 23 +++ .../WeatherForecast.cs | 12 ++ .../WebApiNet9ControllersFluent.csproj | 16 ++ .../WebApiNet9ControllersFluent.http | 6 + .../appsettings.Development.json | 8 + .../appsettings.json | 9 + samples/WebApiNet9MinimalFluent/Program.cs | 41 ++++ .../Properties/launchSettings.json | 23 +++ .../WebApiNet9MinimalFluent/WebApiNet9.http | 6 + .../WebApiNet9MinimalFluent.csproj | 16 ++ .../appsettings.Development.json | 8 + .../WebApiNet9MinimalFluent/appsettings.json | 9 + 44 files changed, 910 insertions(+) create mode 100644 .idea/.idea.Panner.AspNetCore/.idea/.gitignore create mode 100644 .idea/.idea.Panner.AspNetCore/.idea/indexLayout.xml create mode 100644 .idea/.idea.Panner.AspNetCore/.idea/vcs.xml create mode 100644 sample/FastEndpoints/.editorconfig create mode 100644 sample/FastEndpoints/FastEndpoints.sln create mode 100644 sample/FastEndpoints/Source/FastEndpointsNet9.csproj create mode 100644 sample/FastEndpoints/Source/Features/SayHello/Endpoint.cs create mode 100644 sample/FastEndpoints/Source/Features/SayHello/Request.cs create mode 100644 sample/FastEndpoints/Source/Features/SayHello/Response.cs create mode 100644 sample/FastEndpoints/Source/Meta.cs create mode 100644 sample/FastEndpoints/Source/Program.cs create mode 100644 sample/FastEndpoints/Source/Properties/launchSettings.json create mode 100644 sample/FastEndpoints/Source/appsettings.Development.json create mode 100644 sample/FastEndpoints/Source/appsettings.Testing.json create mode 100644 sample/FastEndpoints/Source/appsettings.json create mode 100644 samples/WebAPINet8MinimalFluent/Program.cs create mode 100644 samples/WebAPINet8MinimalFluent/Properties/launchSettings.json create mode 100644 samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj create mode 100644 samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.http create mode 100644 samples/WebAPINet8MinimalFluent/appsettings.Development.json create mode 100644 samples/WebAPINet8MinimalFluent/appsettings.json create mode 100644 samples/WebApi8ControllersFluent/Controllers/WeatherForecastController.cs create mode 100644 samples/WebApi8ControllersFluent/Program.cs create mode 100644 samples/WebApi8ControllersFluent/Properties/launchSettings.json create mode 100644 samples/WebApi8ControllersFluent/WeatherForecast.cs create mode 100644 samples/WebApi8ControllersFluent/WebApi8ControllersFluent.csproj create mode 100644 samples/WebApi8ControllersFluent/WebApi8ControllersFluent.http create mode 100644 samples/WebApi8ControllersFluent/appsettings.Development.json create mode 100644 samples/WebApi8ControllersFluent/appsettings.json create mode 100644 samples/WebApiNet9ControllersFluent/Controllers/WeatherForecastController.cs create mode 100644 samples/WebApiNet9ControllersFluent/Program.cs create mode 100644 samples/WebApiNet9ControllersFluent/Properties/launchSettings.json create mode 100644 samples/WebApiNet9ControllersFluent/WeatherForecast.cs create mode 100644 samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.csproj create mode 100644 samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.http create mode 100644 samples/WebApiNet9ControllersFluent/appsettings.Development.json create mode 100644 samples/WebApiNet9ControllersFluent/appsettings.json create mode 100644 samples/WebApiNet9MinimalFluent/Program.cs create mode 100644 samples/WebApiNet9MinimalFluent/Properties/launchSettings.json create mode 100644 samples/WebApiNet9MinimalFluent/WebApiNet9.http create mode 100644 samples/WebApiNet9MinimalFluent/WebApiNet9MinimalFluent.csproj create mode 100644 samples/WebApiNet9MinimalFluent/appsettings.Development.json create mode 100644 samples/WebApiNet9MinimalFluent/appsettings.json diff --git a/.idea/.idea.Panner.AspNetCore/.idea/.gitignore b/.idea/.idea.Panner.AspNetCore/.idea/.gitignore new file mode 100644 index 0000000..b054589 --- /dev/null +++ b/.idea/.idea.Panner.AspNetCore/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/contentModel.xml +/.idea.Panner.AspNetCore.iml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.Panner.AspNetCore/.idea/indexLayout.xml b/.idea/.idea.Panner.AspNetCore/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.Panner.AspNetCore/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.Panner.AspNetCore/.idea/vcs.xml b/.idea/.idea.Panner.AspNetCore/.idea/vcs.xml new file mode 100644 index 0000000..8306744 --- /dev/null +++ b/.idea/.idea.Panner.AspNetCore/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Panner.AspNetCore.sln b/Panner.AspNetCore.sln index fffad7a..f8314b9 100644 --- a/Panner.AspNetCore.sln +++ b/Panner.AspNetCore.sln @@ -13,6 +13,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore3_1", "samples\As EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet5_0", "samples\AspNet5_0\AspNet5_0.csproj", "{00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNet9MinimalFluent", "samples\WebApiNet9MinimalFluent\WebApiNet9MinimalFluent.csproj", "{84F94171-2814-4993-9BC6-43D7F9535027}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNet9ControllersFluent", "samples\WebApiNet9ControllersFluent\WebApiNet9ControllersFluent.csproj", "{D7BE4E4E-38E0-4DCC-A8CB-7D9CE22994DB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAPINet8MinimalFluent", "samples\WebAPINet8MinimalFluent\WebAPINet8MinimalFluent.csproj", "{39BD3C99-BBB1-48BA-B015-92C30A8813CC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi8ControllersFluent", "samples\WebApi8ControllersFluent\WebApi8ControllersFluent.csproj", "{8580601A-A694-4656-B72C-B56C3BEBF81B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastEndpointsNet9", "sample\FastEndpoints\Source\FastEndpointsNet9.csproj", "{D155BED8-7ABE-4EA4-8C75-3E3CFFDCDF5C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +45,26 @@ Global {00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}.Debug|Any CPU.Build.0 = Debug|Any CPU {00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}.Release|Any CPU.ActiveCfg = Release|Any CPU {00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}.Release|Any CPU.Build.0 = Release|Any CPU + {84F94171-2814-4993-9BC6-43D7F9535027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84F94171-2814-4993-9BC6-43D7F9535027}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84F94171-2814-4993-9BC6-43D7F9535027}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84F94171-2814-4993-9BC6-43D7F9535027}.Release|Any CPU.Build.0 = Release|Any CPU + {D7BE4E4E-38E0-4DCC-A8CB-7D9CE22994DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7BE4E4E-38E0-4DCC-A8CB-7D9CE22994DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7BE4E4E-38E0-4DCC-A8CB-7D9CE22994DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7BE4E4E-38E0-4DCC-A8CB-7D9CE22994DB}.Release|Any CPU.Build.0 = Release|Any CPU + {39BD3C99-BBB1-48BA-B015-92C30A8813CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39BD3C99-BBB1-48BA-B015-92C30A8813CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39BD3C99-BBB1-48BA-B015-92C30A8813CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39BD3C99-BBB1-48BA-B015-92C30A8813CC}.Release|Any CPU.Build.0 = Release|Any CPU + {8580601A-A694-4656-B72C-B56C3BEBF81B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8580601A-A694-4656-B72C-B56C3BEBF81B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8580601A-A694-4656-B72C-B56C3BEBF81B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8580601A-A694-4656-B72C-B56C3BEBF81B}.Release|Any CPU.Build.0 = Release|Any CPU + {D155BED8-7ABE-4EA4-8C75-3E3CFFDCDF5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D155BED8-7ABE-4EA4-8C75-3E3CFFDCDF5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D155BED8-7ABE-4EA4-8C75-3E3CFFDCDF5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D155BED8-7ABE-4EA4-8C75-3E3CFFDCDF5C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -42,6 +72,11 @@ Global GlobalSection(NestedProjects) = preSolution {FC10AD84-66D7-4C98-9A3B-620D6ACC1357} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} {00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} + {84F94171-2814-4993-9BC6-43D7F9535027} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} + {D7BE4E4E-38E0-4DCC-A8CB-7D9CE22994DB} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} + {39BD3C99-BBB1-48BA-B015-92C30A8813CC} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} + {8580601A-A694-4656-B72C-B56C3BEBF81B} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} + {D155BED8-7ABE-4EA4-8C75-3E3CFFDCDF5C} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D12812E7-06E1-4FFB-A601-D656FE2A0075} diff --git a/sample/FastEndpoints/.editorconfig b/sample/FastEndpoints/.editorconfig new file mode 100644 index 0000000..7b81c04 --- /dev/null +++ b/sample/FastEndpoints/.editorconfig @@ -0,0 +1,188 @@ +[*] +charset = utf-8 +end_of_line = crlf +insert_final_newline = false +indent_style = space +indent_size = 4 + +[{*.har,*.json}] +indent_size = 2 + +[*.{received,verified}.{txt,xml,json}] +charset = "utf-8-bom" +end_of_line = lf +indent_size = unset +indent_style = unset +insert_final_newline = false +tab_width = unset +trim_trailing_whitespace = false + +[{*Request.cs,*Response.cs,*Model*.cs,*Endpoint.cs}] +dotnet_diagnostic.CS8618.severity = none + +[*.cs] +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_between_query_expression_clauses = false +csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +dotnet_naming_rule.parameters_rule.import_to_resharper = as_predefined +dotnet_naming_rule.parameters_rule.resharper_style = aaBb, AaBb +dotnet_naming_rule.parameters_rule.severity = warning +dotnet_naming_rule.parameters_rule.style = lower_camel_case_style_1 +dotnet_naming_rule.parameters_rule.symbols = parameters_symbols +dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_constants_rule.severity = warning +dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_readonly_rule.severity = warning +dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.lower_camel_case_style.required_prefix = _ +dotnet_naming_style.lower_camel_case_style_1.capitalization = camel_case +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.parameters_symbols.applicable_accessibilities = * +dotnet_naming_symbols.parameters_symbols.applicable_kinds = parameter +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = never:error +resharper_align_linq_query = true +resharper_align_multiline_binary_patterns = true +resharper_align_multiline_calls_chain = true +resharper_align_multiline_expression = true +resharper_align_multiline_extends_list = true +resharper_align_multiline_parameter = true +resharper_align_multiple_declaration = true +resharper_align_multline_type_parameter_constrains = true +resharper_align_multline_type_parameter_list = true +resharper_align_tuple_components = true +resharper_apply_auto_detected_rules = false +resharper_autodetect_indent_settings = true +resharper_blank_lines_after_block_statements = 0 +resharper_blank_lines_after_control_transfer_statements = 1 +resharper_blank_lines_before_block_statements = 1 +resharper_blank_lines_before_control_transfer_statements = 1 +resharper_blank_lines_before_single_line_comment = 1 +resharper_braces_for_dowhile = required_for_multiline +resharper_braces_for_fixed = required_for_multiline +resharper_braces_for_for = required_for_multiline +resharper_braces_for_foreach = required_for_multiline +resharper_braces_for_ifelse = required_for_multiline +resharper_braces_for_lock = required_for_multiline +resharper_braces_for_using = required_for_multiline +resharper_braces_for_while = required_for_multiline +resharper_csharp_empty_block_style = together_same_line +resharper_csharp_int_align_comments = true +resharper_csharp_keep_blank_lines_in_code = 1 +resharper_csharp_keep_blank_lines_in_declarations = 1 +resharper_csharp_stick_comment = false +resharper_csharp_wrap_after_invocation_lpar = true +resharper_csharp_wrap_arguments_style = chop_if_long +resharper_csharp_wrap_before_first_type_parameter_constraint = true +resharper_csharp_wrap_multiple_declaration_style = chop_always +resharper_csharp_wrap_parameters_style = chop_if_long +resharper_enforce_line_ending_style = true +resharper_force_attribute_style = join +resharper_formatter_off_tag = @formatter:off +resharper_formatter_on_tag = @formatter:on +resharper_formatter_tags_enabled = true +resharper_indent_nested_fixed_stmt = true +resharper_indent_nested_foreach_stmt = true +resharper_indent_nested_for_stmt = true +resharper_indent_nested_lock_stmt = true +resharper_indent_nested_usings_stmt = true +resharper_indent_nested_while_stmt = true +resharper_indent_preprocessor_if = outdent +resharper_indent_preprocessor_other = outdent +resharper_indent_preprocessor_region = outdent +resharper_keep_existing_declaration_parens_arrangement = false +resharper_keep_existing_embedded_arrangement = false +resharper_keep_existing_embedded_block_arrangement = true +resharper_keep_existing_expr_member_arrangement = false +resharper_keep_existing_invocation_parens_arrangement = false +resharper_keep_existing_property_patterns_arrangement = false +resharper_keep_existing_switch_expression_arrangement = false +resharper_local_function_body = expression_body +resharper_max_array_initializer_elements_on_line = 10 +resharper_max_enum_members_on_line = 1 +resharper_max_formal_parameters_on_line = 10 +resharper_max_invocation_arguments_on_line = 10 +resharper_method_or_operator_body = expression_body +resharper_object_creation_when_type_not_evident = target_typed +resharper_place_accessorholder_attribute_on_same_line = false +resharper_place_accessor_with_attrs_holder_on_single_line = true +resharper_place_expr_method_on_single_line = false +resharper_place_linq_into_on_new_line = false +resharper_place_simple_embedded_statement_on_same_line = false +resharper_place_simple_method_on_single_line = false +resharper_show_autodetect_configure_formatting_tip = false +resharper_space_within_slice_pattern = false +resharper_use_continuous_indent_inside_initializer_braces = false +resharper_use_indent_from_vs = false +resharper_wrap_after_invocation_lpar = false +resharper_wrap_after_primary_constructor_declaration_lpar = false +resharper_wrap_array_initializer_style = chop_if_long +resharper_wrap_before_arrow_with_expressions = true +resharper_wrap_before_binary_pattern_op = false +resharper_wrap_chained_binary_expressions = chop_if_long +resharper_wrap_chained_binary_patterns = chop_if_long +resharper_xmldoc_blank_line_after_pi = false +resharper_xmldoc_indent_text = ZeroIndent +resharper_arrange_attributes_highlighting = suggestion +resharper_arrange_constructor_or_destructor_body_highlighting = suggestion +resharper_arrange_default_value_when_type_not_evident_highlighting = suggestion +resharper_arrange_local_function_body_highlighting = suggestion +resharper_arrange_method_or_operator_body_highlighting = hint +resharper_arrange_null_checking_pattern_highlighting = suggestion +resharper_arrange_object_creation_when_type_not_evident_highlighting = suggestion +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_auto_property_can_be_made_get_only_global_highlighting = none +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_check_namespace_highlighting = none +resharper_class_never_instantiated_global_highlighting = none +resharper_class_with_virtual_members_never_inherited_global_highlighting = none +resharper_collection_never_updated_global_highlighting = none +resharper_co_variant_array_conversion_highlighting = hint +resharper_empty_for_statement_highlighting = none +resharper_for_can_be_converted_to_foreach_highlighting = none +resharper_loop_can_be_converted_to_query_highlighting = none +resharper_member_can_be_private_global_highlighting = none +resharper_parameter_hides_member_highlighting = none +resharper_possible_multiple_enumeration_highlighting = suggestion +resharper_redundant_base_qualifier_highlighting = warning +resharper_return_value_of_pure_method_is_not_used_highlighting = none +resharper_separate_local_functions_with_jump_statement_highlighting = none +resharper_static_member_in_generic_type_highlighting = none +resharper_suggest_base_type_for_parameter_in_constructor_highlighting = suggestion +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint +resharper_switch_expression_handles_some_known_enum_values_with_exception_in_default_highlighting = suggestion +resharper_switch_statement_handles_some_known_enum_values_with_default_highlighting = suggestion +resharper_switch_statement_missing_some_enum_cases_no_default_highlighting = suggestion +resharper_unused_auto_property_accessor_global_highlighting = none +resharper_unused_auto_property_accessor_local_highlighting = none +resharper_unused_member_global_highlighting = none +resharper_unused_method_return_value_global_highlighting = none +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning \ No newline at end of file diff --git a/sample/FastEndpoints/FastEndpoints.sln b/sample/FastEndpoints/FastEndpoints.sln new file mode 100644 index 0000000..155fafb --- /dev/null +++ b/sample/FastEndpoints/FastEndpoints.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34009.444 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastEndpoints", "Source\FastEndpoints.csproj", "{1B47FD0B-2CA9-4851-B71A-871393926B53}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{41828E0E-ACFA-4F22-A01E-D519694A9CC5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1B47FD0B-2CA9-4851-B71A-871393926B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B47FD0B-2CA9-4851-B71A-871393926B53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B47FD0B-2CA9-4851-B71A-871393926B53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B47FD0B-2CA9-4851-B71A-871393926B53}.Release|Any CPU.Build.0 = Release|Any CPU + {41828E0E-ACFA-4F22-A01E-D519694A9CC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41828E0E-ACFA-4F22-A01E-D519694A9CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41828E0E-ACFA-4F22-A01E-D519694A9CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41828E0E-ACFA-4F22-A01E-D519694A9CC5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CFD734D0-8B11-4787-ACD0-4942B53BDD70} + EndGlobalSection +EndGlobal diff --git a/sample/FastEndpoints/Source/FastEndpointsNet9.csproj b/sample/FastEndpoints/Source/FastEndpointsNet9.csproj new file mode 100644 index 0000000..9187540 --- /dev/null +++ b/sample/FastEndpoints/Source/FastEndpointsNet9.csproj @@ -0,0 +1,21 @@ + + + + net9.0 + enable + enable + Exe + CS1591;CA2016 + true + Panner.AspNetCore.Samples.FastEndpointsNet9 + Panner.AspNetCore.Samples.FastEndpointsNet9 + + + + + + + + + + \ No newline at end of file diff --git a/sample/FastEndpoints/Source/Features/SayHello/Endpoint.cs b/sample/FastEndpoints/Source/Features/SayHello/Endpoint.cs new file mode 100644 index 0000000..ab4a687 --- /dev/null +++ b/sample/FastEndpoints/Source/Features/SayHello/Endpoint.cs @@ -0,0 +1,18 @@ +namespace SayHello; + +sealed class Endpoint : Endpoint +{ + public override void Configure() + { + Post("/api/hello"); + AllowAnonymous(); + } + + public override async Task HandleAsync(Request r, CancellationToken c) + { + await SendAsync(new() + { + Message = $"Hello {r.FirstName} {r.LastName}..." + }); + } +} \ No newline at end of file diff --git a/sample/FastEndpoints/Source/Features/SayHello/Request.cs b/sample/FastEndpoints/Source/Features/SayHello/Request.cs new file mode 100644 index 0000000..f28b0a1 --- /dev/null +++ b/sample/FastEndpoints/Source/Features/SayHello/Request.cs @@ -0,0 +1,18 @@ +using FluentValidation; + +namespace SayHello; + +sealed class Request +{ + public string FirstName { get; set; } + public string LastName { get; set; } + + internal sealed class Validator : Validator + { + public Validator() + { + RuleFor(x => x.FirstName).MinimumLength(3); + RuleFor(x => x.LastName).MinimumLength(5); + } + } +} \ No newline at end of file diff --git a/sample/FastEndpoints/Source/Features/SayHello/Response.cs b/sample/FastEndpoints/Source/Features/SayHello/Response.cs new file mode 100644 index 0000000..8ae48e0 --- /dev/null +++ b/sample/FastEndpoints/Source/Features/SayHello/Response.cs @@ -0,0 +1,6 @@ +namespace SayHello; + +sealed class Response +{ + public string Message { get; set; } +} \ No newline at end of file diff --git a/sample/FastEndpoints/Source/Meta.cs b/sample/FastEndpoints/Source/Meta.cs new file mode 100644 index 0000000..f26bf9e --- /dev/null +++ b/sample/FastEndpoints/Source/Meta.cs @@ -0,0 +1,9 @@ +global using FastEndpoints; +global using FastEndpoints.Security; +global using FastEndpoints.Swagger; +global using FastEndpoints; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Tests")] + +public partial class Program; \ No newline at end of file diff --git a/sample/FastEndpoints/Source/Program.cs b/sample/FastEndpoints/Source/Program.cs new file mode 100644 index 0000000..ab6ba47 --- /dev/null +++ b/sample/FastEndpoints/Source/Program.cs @@ -0,0 +1,17 @@ +var bld = WebApplication.CreateBuilder(args); +bld.Services + .AddAuthenticationJwtBearer(s => s.SigningKey = bld.Configuration["Auth:JwtKey"]) + .AddAuthorization() + .AddFastEndpoints(o => o.SourceGeneratorDiscoveredTypes = DiscoveredTypes.All) + .SwaggerDocument(); + +var app = bld.Build(); +app.UseAuthentication() + .UseAuthorization() + .UseFastEndpoints(c => + { + c.Binding.ReflectionCache.AddFromFastEndpoints(); + c.Errors.UseProblemDetails(); + }) + .UseSwaggerGen(); +app.Run(); \ No newline at end of file diff --git a/sample/FastEndpoints/Source/Properties/launchSettings.json b/sample/FastEndpoints/Source/Properties/launchSettings.json new file mode 100644 index 0000000..f2fb237 --- /dev/null +++ b/sample/FastEndpoints/Source/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/sample/FastEndpoints/Source/appsettings.Development.json b/sample/FastEndpoints/Source/appsettings.Development.json new file mode 100644 index 0000000..833a367 --- /dev/null +++ b/sample/FastEndpoints/Source/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Auth": { + "JwtKey": "a long secret string used to sign jwts. usually set via matching environment variable." + } +} \ No newline at end of file diff --git a/sample/FastEndpoints/Source/appsettings.Testing.json b/sample/FastEndpoints/Source/appsettings.Testing.json new file mode 100644 index 0000000..0e0dcd2 --- /dev/null +++ b/sample/FastEndpoints/Source/appsettings.Testing.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/sample/FastEndpoints/Source/appsettings.json b/sample/FastEndpoints/Source/appsettings.json new file mode 100644 index 0000000..1b2d3ba --- /dev/null +++ b/sample/FastEndpoints/Source/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} \ No newline at end of file diff --git a/samples/WebAPINet8MinimalFluent/Program.cs b/samples/WebAPINet8MinimalFluent/Program.cs new file mode 100644 index 0000000..161f695 --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/Program.cs @@ -0,0 +1,44 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", () => + { + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; + }) + .WithName("GetWeatherForecast") + .WithOpenApi(); + +app.Run(); + +record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} \ No newline at end of file diff --git a/samples/WebAPINet8MinimalFluent/Properties/launchSettings.json b/samples/WebAPINet8MinimalFluent/Properties/launchSettings.json new file mode 100644 index 0000000..f733bab --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:8658", + "sslPort": 44331 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5277", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7160;http://localhost:5277", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj b/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj new file mode 100644 index 0000000..5293a8e --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + Panner.AspNetCore.Samples.WebApiNet8MinimalFluent + Panner.AspNetCore.Samples.WebApiNet89MinimalFluent + false + + + + + + + + diff --git a/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.http b/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.http new file mode 100644 index 0000000..9562c0d --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.http @@ -0,0 +1,6 @@ +@WebAPINet8MinimalFluent_HostAddress = http://localhost:5277 + +GET {{WebAPINet8MinimalFluent_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/samples/WebAPINet8MinimalFluent/appsettings.Development.json b/samples/WebAPINet8MinimalFluent/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/WebAPINet8MinimalFluent/appsettings.json b/samples/WebAPINet8MinimalFluent/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/WebApi8ControllersFluent/Controllers/WeatherForecastController.cs b/samples/WebApi8ControllersFluent/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..d095ac4 --- /dev/null +++ b/samples/WebApi8ControllersFluent/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WebApi8ControllersFluent.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} \ No newline at end of file diff --git a/samples/WebApi8ControllersFluent/Program.cs b/samples/WebApi8ControllersFluent/Program.cs new file mode 100644 index 0000000..8264bac --- /dev/null +++ b/samples/WebApi8ControllersFluent/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/samples/WebApi8ControllersFluent/Properties/launchSettings.json b/samples/WebApi8ControllersFluent/Properties/launchSettings.json new file mode 100644 index 0000000..1f65c51 --- /dev/null +++ b/samples/WebApi8ControllersFluent/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:34778", + "sslPort": 44332 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5274", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7209;http://localhost:5274", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/WebApi8ControllersFluent/WeatherForecast.cs b/samples/WebApi8ControllersFluent/WeatherForecast.cs new file mode 100644 index 0000000..789421e --- /dev/null +++ b/samples/WebApi8ControllersFluent/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace WebApi8ControllersFluent; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.csproj b/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.csproj new file mode 100644 index 0000000..e89945e --- /dev/null +++ b/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.http b/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.http new file mode 100644 index 0000000..eb08849 --- /dev/null +++ b/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.http @@ -0,0 +1,6 @@ +@WebApi8ControllersFluent_HostAddress = http://localhost:5274 + +GET {{WebApi8ControllersFluent_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/samples/WebApi8ControllersFluent/appsettings.Development.json b/samples/WebApi8ControllersFluent/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/WebApi8ControllersFluent/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/WebApi8ControllersFluent/appsettings.json b/samples/WebApi8ControllersFluent/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/WebApi8ControllersFluent/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/WebApiNet9ControllersFluent/Controllers/WeatherForecastController.cs b/samples/WebApiNet9ControllersFluent/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..4174ac7 --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WebApiNet9ControllersFluent.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} \ No newline at end of file diff --git a/samples/WebApiNet9ControllersFluent/Program.cs b/samples/WebApiNet9ControllersFluent/Program.cs new file mode 100644 index 0000000..7688653 --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/Program.cs @@ -0,0 +1,23 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/samples/WebApiNet9ControllersFluent/Properties/launchSettings.json b/samples/WebApiNet9ControllersFluent/Properties/launchSettings.json new file mode 100644 index 0000000..b2b4448 --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5170", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7140;http://localhost:5170", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/WebApiNet9ControllersFluent/WeatherForecast.cs b/samples/WebApiNet9ControllersFluent/WeatherForecast.cs new file mode 100644 index 0000000..5cf7dd6 --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace WebApiNet9ControllersFluent; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.csproj b/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.csproj new file mode 100644 index 0000000..b3984ca --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.csproj @@ -0,0 +1,16 @@ + + + + net9.0 + enable + enable + Panner.AspNetCore.Samples.WebApiNet9ControllersFluent + Panner.AspNetCore.Samples.WebApiNet9ControllersFluent + false + + + + + + + diff --git a/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.http b/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.http new file mode 100644 index 0000000..5825eff --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.http @@ -0,0 +1,6 @@ +@WebApiNet9ControllersFluent_HostAddress = http://localhost:5170 + +GET {{WebApiNet9ControllersFluent_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/samples/WebApiNet9ControllersFluent/appsettings.Development.json b/samples/WebApiNet9ControllersFluent/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/WebApiNet9ControllersFluent/appsettings.json b/samples/WebApiNet9ControllersFluent/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/WebApiNet9MinimalFluent/Program.cs b/samples/WebApiNet9MinimalFluent/Program.cs new file mode 100644 index 0000000..d5e0ef3 --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/Program.cs @@ -0,0 +1,41 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", () => + { + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; + }) + .WithName("GetWeatherForecast"); + +app.Run(); + +record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} \ No newline at end of file diff --git a/samples/WebApiNet9MinimalFluent/Properties/launchSettings.json b/samples/WebApiNet9MinimalFluent/Properties/launchSettings.json new file mode 100644 index 0000000..024e3d4 --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5254", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7104;http://localhost:5254", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/WebApiNet9MinimalFluent/WebApiNet9.http b/samples/WebApiNet9MinimalFluent/WebApiNet9.http new file mode 100644 index 0000000..6f2eeb2 --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/WebApiNet9.http @@ -0,0 +1,6 @@ +@WebApiNet9_HostAddress = http://localhost:5254 + +GET {{WebApiNet9_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/samples/WebApiNet9MinimalFluent/WebApiNet9MinimalFluent.csproj b/samples/WebApiNet9MinimalFluent/WebApiNet9MinimalFluent.csproj new file mode 100644 index 0000000..dc51723 --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/WebApiNet9MinimalFluent.csproj @@ -0,0 +1,16 @@ + + + + net9.0 + enable + enable + Panner.AspNetCore.Samples.WebApiNet9MinimalFluent + Panner.AspNetCore.Samples.WebApiNet9MinimalFluent + false + + + + + + + diff --git a/samples/WebApiNet9MinimalFluent/appsettings.Development.json b/samples/WebApiNet9MinimalFluent/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/WebApiNet9MinimalFluent/appsettings.json b/samples/WebApiNet9MinimalFluent/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 8be2dc75b5d9e84a83e63aae9d8b88185aa760e6 Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 02:05:23 -0400 Subject: [PATCH 05/13] Boilerplate for samples --- .../Source/EFModel/BlogContext.cs | 47 +++++++++++++ sample/FastEndpoints/Source/EFModel/Post.cs | 15 ++++ .../Source/FastEndpointsNet9.csproj | 18 +++-- .../Source/Features/Posts/Endpoint.cs | 39 +++++++++++ .../Source/Features/Posts/Request.cs | 14 ++++ ...tityBuilder.Post.IsSortableByPopularity.cs | 15 ++++ .../SortPostByPopularityParticle.cs | 27 ++++++++ .../SortPostsByPopularityParticleGenerator.cs | 22 ++++++ sample/FastEndpoints/Source/Program.cs | 27 +++++++- sample/FastEndpoints/Source/Views/Post.cs | 12 ++++ .../EFModel/BlogContext.cs | 47 +++++++++++++ .../WebAPINet8MinimalFluent/EFModel/Post.cs | 15 ++++ ...tityBuilder.Post.IsSortableByPopularity.cs | 15 ++++ .../SortPostByPopularityParticle.cs | 27 ++++++++ .../SortPostsByPopularityParticleGenerator.cs | 22 ++++++ samples/WebAPINet8MinimalFluent/Program.cs | 69 ++++++++++++------- samples/WebAPINet8MinimalFluent/Views/Post.cs | 12 ++++ .../WebAPINet8MinimalFluent.csproj | 12 +++- .../Controllers/PostsController.cs | 48 +++++++++++++ .../EFModel/BlogContext.cs | 47 +++++++++++++ .../WebApi8ControllersFluent/EFModel/Post.cs | 15 ++++ ...tityBuilder.Post.IsSortableByPopularity.cs | 15 ++++ .../SortPostByPopularityParticle.cs | 27 ++++++++ .../SortPostsByPopularityParticleGenerator.cs | 22 ++++++ samples/WebApi8ControllersFluent/Program.cs | 35 ++++++++-- .../WebApi8ControllersFluent/Views/Post.cs | 12 ++++ .../WebApi8ControllersFluent.csproj | 8 ++- .../Controllers/PostsController.cs | 48 +++++++++++++ .../EFModel/BlogContext.cs | 47 +++++++++++++ .../EFModel/Post.cs | 15 ++++ ...tityBuilder.Post.IsSortableByPopularity.cs | 15 ++++ .../SortPostByPopularityParticle.cs | 27 ++++++++ .../SortPostsByPopularityParticleGenerator.cs | 22 ++++++ .../WebApiNet9ControllersFluent/Program.cs | 34 +++++++-- .../WebApiNet9ControllersFluent/Views/Post.cs | 12 ++++ .../WebApiNet9ControllersFluent.csproj | 10 ++- .../EFModel/BlogContext.cs | 47 +++++++++++++ .../WebApiNet9MinimalFluent/EFModel/Post.cs | 15 ++++ ...tityBuilder.Post.IsSortableByPopularity.cs | 15 ++++ .../SortPostByPopularityParticle.cs | 27 ++++++++ .../SortPostsByPopularityParticleGenerator.cs | 22 ++++++ samples/WebApiNet9MinimalFluent/Program.cs | 68 +++++++++++------- samples/WebApiNet9MinimalFluent/Views/Post.cs | 12 ++++ .../WebApiNet9MinimalFluent.csproj | 10 ++- 44 files changed, 1051 insertions(+), 79 deletions(-) create mode 100644 sample/FastEndpoints/Source/EFModel/BlogContext.cs create mode 100644 sample/FastEndpoints/Source/EFModel/Post.cs create mode 100644 sample/FastEndpoints/Source/Features/Posts/Endpoint.cs create mode 100644 sample/FastEndpoints/Source/Features/Posts/Request.cs create mode 100644 sample/FastEndpoints/Source/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs create mode 100644 sample/FastEndpoints/Source/PannerExtensions/SortPostByPopularityParticle.cs create mode 100644 sample/FastEndpoints/Source/PannerExtensions/SortPostsByPopularityParticleGenerator.cs create mode 100644 sample/FastEndpoints/Source/Views/Post.cs create mode 100644 samples/WebAPINet8MinimalFluent/EFModel/BlogContext.cs create mode 100644 samples/WebAPINet8MinimalFluent/EFModel/Post.cs create mode 100644 samples/WebAPINet8MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs create mode 100644 samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs create mode 100644 samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs create mode 100644 samples/WebAPINet8MinimalFluent/Views/Post.cs create mode 100644 samples/WebApi8ControllersFluent/Controllers/PostsController.cs create mode 100644 samples/WebApi8ControllersFluent/EFModel/BlogContext.cs create mode 100644 samples/WebApi8ControllersFluent/EFModel/Post.cs create mode 100644 samples/WebApi8ControllersFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs create mode 100644 samples/WebApi8ControllersFluent/PannerExtensions/SortPostByPopularityParticle.cs create mode 100644 samples/WebApi8ControllersFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs create mode 100644 samples/WebApi8ControllersFluent/Views/Post.cs create mode 100644 samples/WebApiNet9ControllersFluent/Controllers/PostsController.cs create mode 100644 samples/WebApiNet9ControllersFluent/EFModel/BlogContext.cs create mode 100644 samples/WebApiNet9ControllersFluent/EFModel/Post.cs create mode 100644 samples/WebApiNet9ControllersFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs create mode 100644 samples/WebApiNet9ControllersFluent/PannerExtensions/SortPostByPopularityParticle.cs create mode 100644 samples/WebApiNet9ControllersFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs create mode 100644 samples/WebApiNet9ControllersFluent/Views/Post.cs create mode 100644 samples/WebApiNet9MinimalFluent/EFModel/BlogContext.cs create mode 100644 samples/WebApiNet9MinimalFluent/EFModel/Post.cs create mode 100644 samples/WebApiNet9MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs create mode 100644 samples/WebApiNet9MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs create mode 100644 samples/WebApiNet9MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs create mode 100644 samples/WebApiNet9MinimalFluent/Views/Post.cs diff --git a/sample/FastEndpoints/Source/EFModel/BlogContext.cs b/sample/FastEndpoints/Source/EFModel/BlogContext.cs new file mode 100644 index 0000000..1015a9a --- /dev/null +++ b/sample/FastEndpoints/Source/EFModel/BlogContext.cs @@ -0,0 +1,47 @@ +namespace Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel +{ + using Microsoft.EntityFrameworkCore; + using System; + + public class BlogContext : DbContext + { + public DbSet Posts { get; set; } + + public BlogContext() : base() + { + } + + public BlogContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasData(new Post() + { + Id = 1, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }, new Post() + { + Id = 2, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = false + }, new Post() + { + Id = 3, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }); + } + } +} diff --git a/sample/FastEndpoints/Source/EFModel/Post.cs b/sample/FastEndpoints/Source/EFModel/Post.cs new file mode 100644 index 0000000..10f85ab --- /dev/null +++ b/sample/FastEndpoints/Source/EFModel/Post.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public bool IsVisible { get; set; } + public int AmtLikes { get; set; } + public int AmtComments { get; set; } + public DateTime CreatedOn { get; set; } + } +} diff --git a/sample/FastEndpoints/Source/FastEndpointsNet9.csproj b/sample/FastEndpoints/Source/FastEndpointsNet9.csproj index 9187540..f940e73 100644 --- a/sample/FastEndpoints/Source/FastEndpointsNet9.csproj +++ b/sample/FastEndpoints/Source/FastEndpointsNet9.csproj @@ -11,11 +11,17 @@ Panner.AspNetCore.Samples.FastEndpointsNet9 - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/FastEndpoints/Source/Features/Posts/Endpoint.cs b/sample/FastEndpoints/Source/Features/Posts/Endpoint.cs new file mode 100644 index 0000000..4fd2bfc --- /dev/null +++ b/sample/FastEndpoints/Source/Features/Posts/Endpoint.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel; +using Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions; + +namespace Posts; + +sealed class Endpoint : Endpoint> +{ + private readonly BlogContext _context; + + public Endpoint(BlogContext context) + { + _context = context; + } + + public override void Configure() + { + Get("/posts"); + AllowAnonymous(); + } + + public override async Task HandleAsync(Request r, CancellationToken c) + { + _context.Database.EnsureCreated(); + var result = await _context.Posts + .Apply(r.Filters ?? Array.Empty>()) + .Apply(r.Sorts ?? Array.Empty>()) + .Select(x => new Views.Post + { + Id = x.Id, + Title = x.Title, + Content = x.Content, + Creation = x.CreatedOn + }) + .ToArrayAsync(c); + + await SendAsync(result, cancellation: c); + } +} diff --git a/sample/FastEndpoints/Source/Features/Posts/Request.cs b/sample/FastEndpoints/Source/Features/Posts/Request.cs new file mode 100644 index 0000000..e881281 --- /dev/null +++ b/sample/FastEndpoints/Source/Features/Posts/Request.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; +using Panner; +using Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel; + +namespace Posts; + +sealed class Request +{ + [FromQuery] + public IReadOnlyCollection>? Sorts { get; set; } + + [FromQuery] + public IReadOnlyCollection>? Filters { get; set; } +} diff --git a/sample/FastEndpoints/Source/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs b/sample/FastEndpoints/Source/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs new file mode 100644 index 0000000..9db989b --- /dev/null +++ b/sample/FastEndpoints/Source/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel; + using global::Panner.Builders; + + public static partial class PEntityBuilderExtensions + { + /// Marks the entity as sortable by popularity. + public static PEntityBuilder IsSortableByPopularity(this PEntityBuilder builder) + { + builder.GetOrCreateGenerator, SortPostsByPopularityParticleGenerator>(); + return builder; + } + } +} diff --git a/sample/FastEndpoints/Source/PannerExtensions/SortPostByPopularityParticle.cs b/sample/FastEndpoints/Source/PannerExtensions/SortPostByPopularityParticle.cs new file mode 100644 index 0000000..43b78fc --- /dev/null +++ b/sample/FastEndpoints/Source/PannerExtensions/SortPostByPopularityParticle.cs @@ -0,0 +1,27 @@ +namespace Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel; + using System.Linq; + + public class SortPostByPopularityParticle : ISortParticle + { + readonly bool Descending; + + public SortPostByPopularityParticle(bool descending) + { + this.Descending = descending; + } + + public IOrderedQueryable ApplyTo(IOrderedQueryable source) + { + if (this.Descending) + return source + .ThenByDescending(x => x.AmtLikes) + .ThenByDescending(x => x.AmtComments); + else + return source + .ThenBy(x => x.AmtLikes) + .ThenBy(x => x.AmtComments); + } + } +} diff --git a/sample/FastEndpoints/Source/PannerExtensions/SortPostsByPopularityParticleGenerator.cs b/sample/FastEndpoints/Source/PannerExtensions/SortPostsByPopularityParticleGenerator.cs new file mode 100644 index 0000000..7430d17 --- /dev/null +++ b/sample/FastEndpoints/Source/PannerExtensions/SortPostsByPopularityParticleGenerator.cs @@ -0,0 +1,22 @@ +using Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel; + +namespace Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions +{ + public class SortPostsByPopularityParticleGenerator : ISortParticleGenerator + { + public bool TryGenerate(IPContext context, string input, out ISortParticle particle) + { + var descending = input.StartsWith('-'); + var remaining = descending ? input.Substring(1) : input; + + if (!remaining.Trim().Equals("Popularity", System.StringComparison.OrdinalIgnoreCase)) + { + particle = null; + return false; + } + + particle = new SortPostByPopularityParticle(descending); + return true; + } + } +} diff --git a/sample/FastEndpoints/Source/Program.cs b/sample/FastEndpoints/Source/Program.cs index ab6ba47..d3a7f06 100644 --- a/sample/FastEndpoints/Source/Program.cs +++ b/sample/FastEndpoints/Source/Program.cs @@ -1,10 +1,35 @@ +using Microsoft.EntityFrameworkCore; +using Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel; +using Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions; +using Panner.Builders; + var bld = WebApplication.CreateBuilder(args); + bld.Services .AddAuthenticationJwtBearer(s => s.SigningKey = bld.Configuration["Auth:JwtKey"]) .AddAuthorization() .AddFastEndpoints(o => o.SourceGeneratorDiscoveredTypes = DiscoveredTypes.All) .SwaggerDocument(); +bld.Services.UsePanner(c => +{ + c.Entity() + .IsSortableByPopularity() + .Property(x => x.Id, o => o + .IsSortableAs(nameof(Views.Post.Id)) + .IsFilterableAs(nameof(Views.Post.Id)) + ) + .Property(x => x.Title, o => o + .IsSortableAs(nameof(Views.Post.Title)) + ) + .Property(x => x.CreatedOn, o => o + .IsSortableAs(nameof(Views.Post.Creation)) + .IsFilterableAs(nameof(Views.Post.Creation)) + ); +}); + +bld.Services.AddDbContext(o => o.UseInMemoryDatabase("BlogDb")); + var app = bld.Build(); app.UseAuthentication() .UseAuthorization() @@ -14,4 +39,4 @@ c.Errors.UseProblemDetails(); }) .UseSwaggerGen(); -app.Run(); \ No newline at end of file +app.Run(); diff --git a/sample/FastEndpoints/Source/Views/Post.cs b/sample/FastEndpoints/Source/Views/Post.cs new file mode 100644 index 0000000..b0ef8bb --- /dev/null +++ b/sample/FastEndpoints/Source/Views/Post.cs @@ -0,0 +1,12 @@ +namespace Panner.AspNetCore.Samples.FastEndpointsNet9.Views +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime Creation { get; set; } + } +} diff --git a/samples/WebAPINet8MinimalFluent/EFModel/BlogContext.cs b/samples/WebAPINet8MinimalFluent/EFModel/BlogContext.cs new file mode 100644 index 0000000..c1d1b8e --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/EFModel/BlogContext.cs @@ -0,0 +1,47 @@ +namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel +{ + using Microsoft.EntityFrameworkCore; + using System; + + public class BlogContext : DbContext + { + public DbSet Posts { get; set; } + + public BlogContext() : base() + { + } + + public BlogContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasData(new Post() + { + Id = 1, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }, new Post() + { + Id = 2, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = false + }, new Post() + { + Id = 3, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }); + } + } +} diff --git a/samples/WebAPINet8MinimalFluent/EFModel/Post.cs b/samples/WebAPINet8MinimalFluent/EFModel/Post.cs new file mode 100644 index 0000000..bdc63c0 --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/EFModel/Post.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public bool IsVisible { get; set; } + public int AmtLikes { get; set; } + public int AmtComments { get; set; } + public DateTime CreatedOn { get; set; } + } +} diff --git a/samples/WebAPINet8MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs b/samples/WebAPINet8MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs new file mode 100644 index 0000000..c45b930 --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel; + using global::Panner.Builders; + + public static partial class PEntityBuilderExtensions + { + /// Marks the entity as sortable by popularity. + public static PEntityBuilder IsSortableByPopularity(this PEntityBuilder builder) + { + builder.GetOrCreateGenerator, SortPostsByPopularityParticleGenerator>(); + return builder; + } + } +} diff --git a/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs b/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs new file mode 100644 index 0000000..591e528 --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs @@ -0,0 +1,27 @@ +namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel; + using System.Linq; + + public class SortPostByPopularityParticle : ISortParticle + { + readonly bool Descending; + + public SortPostByPopularityParticle(bool descending) + { + this.Descending = descending; + } + + public IOrderedQueryable ApplyTo(IOrderedQueryable source) + { + if (this.Descending) + return source + .ThenByDescending(x => x.AmtLikes) + .ThenByDescending(x => x.AmtComments); + else + return source + .ThenBy(x => x.AmtLikes) + .ThenBy(x => x.AmtComments); + } + } +} diff --git a/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs b/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs new file mode 100644 index 0000000..945a8b7 --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs @@ -0,0 +1,22 @@ +using Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel; + +namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.PannerExtensions +{ + public class SortPostsByPopularityParticleGenerator : ISortParticleGenerator + { + public bool TryGenerate(IPContext context, string input, out ISortParticle particle) + { + var descending = input.StartsWith('-'); + var remaining = descending ? input.Substring(1) : input; + + if (!remaining.Trim().Equals("Popularity", System.StringComparison.OrdinalIgnoreCase)) + { + particle = null; + return false; + } + + particle = new SortPostByPopularityParticle(descending); + return true; + } + } +} diff --git a/samples/WebAPINet8MinimalFluent/Program.cs b/samples/WebAPINet8MinimalFluent/Program.cs index 161f695..f71ccda 100644 --- a/samples/WebAPINet8MinimalFluent/Program.cs +++ b/samples/WebAPINet8MinimalFluent/Program.cs @@ -1,13 +1,38 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel; +using Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.PannerExtensions; +using Panner.Builders; + var builder = WebApplication.CreateBuilder(args); -// Add services to the container. -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.UsePanner(c => +{ + c.Entity() + .IsSortableByPopularity() + .Property(x => x.Id, o => o + .IsSortableAs(nameof(Views.Post.Id)) + .IsFilterableAs(nameof(Views.Post.Id)) + ) + .Property(x => x.Title, o => o + .IsSortableAs(nameof(Views.Post.Title)) + ) + .Property(x => x.CreatedOn, o => o + .IsSortableAs(nameof(Views.Post.Creation)) + .IsFilterableAs(nameof(Views.Post.Creation)) + ); +}); + +builder.Services.AddDbContext(options => +{ + options.UseInMemoryDatabase("BlogDb"); +}); + var app = builder.Build(); -// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); @@ -16,29 +41,21 @@ app.UseHttpsRedirection(); -var summaries = new[] +app.MapGet("/posts", async ([FromServices] BlogContext blogContext, [FromQuery] IReadOnlyCollection> sorts, [FromQuery] IReadOnlyCollection> filters) => { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" -}; - -app.MapGet("/weatherforecast", () => - { - var forecast = Enumerable.Range(1, 5).Select(index => - new WeatherForecast - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - return forecast; - }) - .WithName("GetWeatherForecast") - .WithOpenApi(); + blogContext.Database.EnsureCreated(); + var result = await blogContext.Posts + .Apply(filters) + .Apply(sorts) + .Select(x => new Views.Post + { + Id = x.Id, + Title = x.Title, + Content = x.Content, + Creation = x.CreatedOn + }) + .ToArrayAsync(); + return Results.Ok(result); +}).WithOpenApi(); app.Run(); - -record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) -{ - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} \ No newline at end of file diff --git a/samples/WebAPINet8MinimalFluent/Views/Post.cs b/samples/WebAPINet8MinimalFluent/Views/Post.cs new file mode 100644 index 0000000..b216412 --- /dev/null +++ b/samples/WebAPINet8MinimalFluent/Views/Post.cs @@ -0,0 +1,12 @@ +namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.Views +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime Creation { get; set; } + } +} diff --git a/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj b/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj index 5293a8e..812e7ee 100644 --- a/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj +++ b/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj @@ -5,13 +5,19 @@ enable enable Panner.AspNetCore.Samples.WebApiNet8MinimalFluent - Panner.AspNetCore.Samples.WebApiNet89MinimalFluent + Panner.AspNetCore.Samples.WebApiNet8MinimalFluent false - - + + + + + + + + diff --git a/samples/WebApi8ControllersFluent/Controllers/PostsController.cs b/samples/WebApi8ControllersFluent/Controllers/PostsController.cs new file mode 100644 index 0000000..cf7c3dd --- /dev/null +++ b/samples/WebApi8ControllersFluent/Controllers/PostsController.cs @@ -0,0 +1,48 @@ +namespace Panner.AspNetCore.Samples.WebApi8ControllersFluent.Controllers +{ + using Microsoft.AspNetCore.Mvc; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Logging; + using Panner.AspNetCore.Samples.WebApi8ControllersFluent.EFModel; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + [ApiController] + [Route("[controller]")] + public class PostsController : ControllerBase + { + private readonly ILogger _logger; + + public PostsController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public async Task GetAllPosts( + [FromServices] BlogContext blogContext, + [FromQuery] IReadOnlyCollection> sorts, + [FromQuery] IReadOnlyCollection> filters + ) + { + // Workaround for: https://github.com/aspnet/EntityFrameworkCore/issues/11666 + // This is only because we're seeding data OnModelCreation for an InMemoryDatabase + blogContext.Database.EnsureCreated(); + + var result = await blogContext.Posts + .Apply(filters) + .Apply(sorts) + .Select(x => new Views.Post() + { + Id = x.Id, + Title = x.Title, + Content = x.Content, + Creation = x.CreatedOn + }) + .ToArrayAsync(); + + return Ok(result); + } + } +} diff --git a/samples/WebApi8ControllersFluent/EFModel/BlogContext.cs b/samples/WebApi8ControllersFluent/EFModel/BlogContext.cs new file mode 100644 index 0000000..a27f4e5 --- /dev/null +++ b/samples/WebApi8ControllersFluent/EFModel/BlogContext.cs @@ -0,0 +1,47 @@ +namespace Panner.AspNetCore.Samples.WebApi8ControllersFluent.EFModel +{ + using Microsoft.EntityFrameworkCore; + using System; + + public class BlogContext : DbContext + { + public DbSet Posts { get; set; } + + public BlogContext() : base() + { + } + + public BlogContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasData(new Post() + { + Id = 1, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }, new Post() + { + Id = 2, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = false + }, new Post() + { + Id = 3, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }); + } + } +} diff --git a/samples/WebApi8ControllersFluent/EFModel/Post.cs b/samples/WebApi8ControllersFluent/EFModel/Post.cs new file mode 100644 index 0000000..f237d0d --- /dev/null +++ b/samples/WebApi8ControllersFluent/EFModel/Post.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.WebApi8ControllersFluent.EFModel +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public bool IsVisible { get; set; } + public int AmtLikes { get; set; } + public int AmtComments { get; set; } + public DateTime CreatedOn { get; set; } + } +} diff --git a/samples/WebApi8ControllersFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs b/samples/WebApi8ControllersFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs new file mode 100644 index 0000000..c73cb21 --- /dev/null +++ b/samples/WebApi8ControllersFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.WebApi8ControllersFluent.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.WebApi8ControllersFluent.EFModel; + using global::Panner.Builders; + + public static partial class PEntityBuilderExtensions + { + /// Marks the entity as sortable by popularity. + public static PEntityBuilder IsSortableByPopularity(this PEntityBuilder builder) + { + builder.GetOrCreateGenerator, SortPostsByPopularityParticleGenerator>(); + return builder; + } + } +} diff --git a/samples/WebApi8ControllersFluent/PannerExtensions/SortPostByPopularityParticle.cs b/samples/WebApi8ControllersFluent/PannerExtensions/SortPostByPopularityParticle.cs new file mode 100644 index 0000000..4a683aa --- /dev/null +++ b/samples/WebApi8ControllersFluent/PannerExtensions/SortPostByPopularityParticle.cs @@ -0,0 +1,27 @@ +namespace Panner.AspNetCore.Samples.WebApi8ControllersFluent.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.WebApi8ControllersFluent.EFModel; + using System.Linq; + + public class SortPostByPopularityParticle : ISortParticle + { + readonly bool Descending; + + public SortPostByPopularityParticle(bool descending) + { + this.Descending = descending; + } + + public IOrderedQueryable ApplyTo(IOrderedQueryable source) + { + if (this.Descending) + return source + .ThenByDescending(x => x.AmtLikes) + .ThenByDescending(x => x.AmtComments); + else + return source + .ThenBy(x => x.AmtLikes) + .ThenBy(x => x.AmtComments); + } + } +} diff --git a/samples/WebApi8ControllersFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs b/samples/WebApi8ControllersFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs new file mode 100644 index 0000000..f454d2c --- /dev/null +++ b/samples/WebApi8ControllersFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs @@ -0,0 +1,22 @@ +using Panner.AspNetCore.Samples.WebApi8ControllersFluent.EFModel; + +namespace Panner.AspNetCore.Samples.WebApi8ControllersFluent.PannerExtensions +{ + public class SortPostsByPopularityParticleGenerator : ISortParticleGenerator + { + public bool TryGenerate(IPContext context, string input, out ISortParticle particle) + { + var descending = input.StartsWith('-'); + var remaining = descending ? input.Substring(1) : input; + + if (!remaining.Trim().Equals("Popularity", System.StringComparison.OrdinalIgnoreCase)) + { + particle = null; + return false; + } + + particle = new SortPostByPopularityParticle(descending); + return true; + } + } +} diff --git a/samples/WebApi8ControllersFluent/Program.cs b/samples/WebApi8ControllersFluent/Program.cs index 8264bac..f5d22e0 100644 --- a/samples/WebApi8ControllersFluent/Program.cs +++ b/samples/WebApi8ControllersFluent/Program.cs @@ -1,15 +1,38 @@ -var builder = WebApplication.CreateBuilder(args); +using Microsoft.EntityFrameworkCore; +using Panner.AspNetCore.Samples.WebApi8ControllersFluent.EFModel; +using Panner.AspNetCore.Samples.WebApi8ControllersFluent.PannerExtensions; +using Panner.Builders; -// Add services to the container. +var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.UsePanner(c => +{ + c.Entity() + .IsSortableByPopularity() + .Property(x => x.Id, o => o + .IsSortableAs(nameof(Views.Post.Id)) + .IsFilterableAs(nameof(Views.Post.Id)) + ) + .Property(x => x.Title, o => o + .IsSortableAs(nameof(Views.Post.Title)) + ) + .Property(x => x.CreatedOn, o => o + .IsSortableAs(nameof(Views.Post.Creation)) + .IsFilterableAs(nameof(Views.Post.Creation)) + ); +}); + +builder.Services.AddDbContext(options => +{ + options.UseInMemoryDatabase("BlogDb"); +}); + var app = builder.Build(); -// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); @@ -17,9 +40,7 @@ } app.UseHttpsRedirection(); - app.UseAuthorization(); - app.MapControllers(); -app.Run(); \ No newline at end of file +app.Run(); diff --git a/samples/WebApi8ControllersFluent/Views/Post.cs b/samples/WebApi8ControllersFluent/Views/Post.cs new file mode 100644 index 0000000..7725396 --- /dev/null +++ b/samples/WebApi8ControllersFluent/Views/Post.cs @@ -0,0 +1,12 @@ +namespace Panner.AspNetCore.Samples.WebApi8ControllersFluent.Views +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime Creation { get; set; } + } +} diff --git a/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.csproj b/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.csproj index e89945e..d4aeee2 100644 --- a/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.csproj +++ b/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.csproj @@ -7,7 +7,13 @@ - + + + + + + + diff --git a/samples/WebApiNet9ControllersFluent/Controllers/PostsController.cs b/samples/WebApiNet9ControllersFluent/Controllers/PostsController.cs new file mode 100644 index 0000000..c5be6b3 --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/Controllers/PostsController.cs @@ -0,0 +1,48 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.Controllers +{ + using Microsoft.AspNetCore.Mvc; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Logging; + using Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.EFModel; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + [ApiController] + [Route("[controller]")] + public class PostsController : ControllerBase + { + private readonly ILogger _logger; + + public PostsController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public async Task GetAllPosts( + [FromServices] BlogContext blogContext, + [FromQuery] IReadOnlyCollection> sorts, + [FromQuery] IReadOnlyCollection> filters + ) + { + // Workaround for: https://github.com/aspnet/EntityFrameworkCore/issues/11666 + // This is only because we're seeding data OnModelCreation for an InMemoryDatabase + blogContext.Database.EnsureCreated(); + + var result = await blogContext.Posts + .Apply(filters) + .Apply(sorts) + .Select(x => new Views.Post() + { + Id = x.Id, + Title = x.Title, + Content = x.Content, + Creation = x.CreatedOn + }) + .ToArrayAsync(); + + return Ok(result); + } + } +} diff --git a/samples/WebApiNet9ControllersFluent/EFModel/BlogContext.cs b/samples/WebApiNet9ControllersFluent/EFModel/BlogContext.cs new file mode 100644 index 0000000..ae32a42 --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/EFModel/BlogContext.cs @@ -0,0 +1,47 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.EFModel +{ + using Microsoft.EntityFrameworkCore; + using System; + + public class BlogContext : DbContext + { + public DbSet Posts { get; set; } + + public BlogContext() : base() + { + } + + public BlogContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasData(new Post() + { + Id = 1, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }, new Post() + { + Id = 2, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = false + }, new Post() + { + Id = 3, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }); + } + } +} diff --git a/samples/WebApiNet9ControllersFluent/EFModel/Post.cs b/samples/WebApiNet9ControllersFluent/EFModel/Post.cs new file mode 100644 index 0000000..e22de4a --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/EFModel/Post.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.EFModel +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public bool IsVisible { get; set; } + public int AmtLikes { get; set; } + public int AmtComments { get; set; } + public DateTime CreatedOn { get; set; } + } +} diff --git a/samples/WebApiNet9ControllersFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs b/samples/WebApiNet9ControllersFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs new file mode 100644 index 0000000..3e3a3ff --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.EFModel; + using global::Panner.Builders; + + public static partial class PEntityBuilderExtensions + { + /// Marks the entity as sortable by popularity. + public static PEntityBuilder IsSortableByPopularity(this PEntityBuilder builder) + { + builder.GetOrCreateGenerator, SortPostsByPopularityParticleGenerator>(); + return builder; + } + } +} diff --git a/samples/WebApiNet9ControllersFluent/PannerExtensions/SortPostByPopularityParticle.cs b/samples/WebApiNet9ControllersFluent/PannerExtensions/SortPostByPopularityParticle.cs new file mode 100644 index 0000000..6774749 --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/PannerExtensions/SortPostByPopularityParticle.cs @@ -0,0 +1,27 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.EFModel; + using System.Linq; + + public class SortPostByPopularityParticle : ISortParticle + { + readonly bool Descending; + + public SortPostByPopularityParticle(bool descending) + { + this.Descending = descending; + } + + public IOrderedQueryable ApplyTo(IOrderedQueryable source) + { + if (this.Descending) + return source + .ThenByDescending(x => x.AmtLikes) + .ThenByDescending(x => x.AmtComments); + else + return source + .ThenBy(x => x.AmtLikes) + .ThenBy(x => x.AmtComments); + } + } +} diff --git a/samples/WebApiNet9ControllersFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs b/samples/WebApiNet9ControllersFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs new file mode 100644 index 0000000..ef7aa91 --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs @@ -0,0 +1,22 @@ +using Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.EFModel; + +namespace Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.PannerExtensions +{ + public class SortPostsByPopularityParticleGenerator : ISortParticleGenerator + { + public bool TryGenerate(IPContext context, string input, out ISortParticle particle) + { + var descending = input.StartsWith('-'); + var remaining = descending ? input.Substring(1) : input; + + if (!remaining.Trim().Equals("Popularity", System.StringComparison.OrdinalIgnoreCase)) + { + particle = null; + return false; + } + + particle = new SortPostByPopularityParticle(descending); + return true; + } + } +} diff --git a/samples/WebApiNet9ControllersFluent/Program.cs b/samples/WebApiNet9ControllersFluent/Program.cs index 7688653..ef0368d 100644 --- a/samples/WebApiNet9ControllersFluent/Program.cs +++ b/samples/WebApiNet9ControllersFluent/Program.cs @@ -1,23 +1,45 @@ -var builder = WebApplication.CreateBuilder(args); +using Microsoft.EntityFrameworkCore; +using Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.EFModel; +using Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.PannerExtensions; +using Panner.Builders; -// Add services to the container. +var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); +builder.Services.UsePanner(c => +{ + c.Entity() + .IsSortableByPopularity() + .Property(x => x.Id, o => o + .IsSortableAs(nameof(Views.Post.Id)) + .IsFilterableAs(nameof(Views.Post.Id)) + ) + .Property(x => x.Title, o => o + .IsSortableAs(nameof(Views.Post.Title)) + ) + .Property(x => x.CreatedOn, o => o + .IsSortableAs(nameof(Views.Post.Creation)) + .IsFilterableAs(nameof(Views.Post.Creation)) + ); +}); + +builder.Services.AddDbContext(options => +{ + options.UseInMemoryDatabase("BlogDb"); +}); + var app = builder.Build(); -// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.UseHttpsRedirection(); - app.UseAuthorization(); app.MapControllers(); -app.Run(); \ No newline at end of file +app.Run(); diff --git a/samples/WebApiNet9ControllersFluent/Views/Post.cs b/samples/WebApiNet9ControllersFluent/Views/Post.cs new file mode 100644 index 0000000..ffd878b --- /dev/null +++ b/samples/WebApiNet9ControllersFluent/Views/Post.cs @@ -0,0 +1,12 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.Views +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime Creation { get; set; } + } +} diff --git a/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.csproj b/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.csproj index b3984ca..990755f 100644 --- a/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.csproj +++ b/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.csproj @@ -10,7 +10,13 @@ - - + + + + + + + + diff --git a/samples/WebApiNet9MinimalFluent/EFModel/BlogContext.cs b/samples/WebApiNet9MinimalFluent/EFModel/BlogContext.cs new file mode 100644 index 0000000..bb0fd7c --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/EFModel/BlogContext.cs @@ -0,0 +1,47 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.EFModel +{ + using Microsoft.EntityFrameworkCore; + using System; + + public class BlogContext : DbContext + { + public DbSet Posts { get; set; } + + public BlogContext() : base() + { + } + + public BlogContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasData(new Post() + { + Id = 1, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }, new Post() + { + Id = 2, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = false + }, new Post() + { + Id = 3, + Title = Guid.NewGuid().ToString(), + Content = Guid.NewGuid().ToString(), + CreatedOn = DateTime.UtcNow, + IsVisible = true + }); + } + } +} diff --git a/samples/WebApiNet9MinimalFluent/EFModel/Post.cs b/samples/WebApiNet9MinimalFluent/EFModel/Post.cs new file mode 100644 index 0000000..9509e1e --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/EFModel/Post.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.EFModel +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public bool IsVisible { get; set; } + public int AmtLikes { get; set; } + public int AmtComments { get; set; } + public DateTime CreatedOn { get; set; } + } +} diff --git a/samples/WebApiNet9MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs b/samples/WebApiNet9MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs new file mode 100644 index 0000000..d789560 --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs @@ -0,0 +1,15 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.EFModel; + using global::Panner.Builders; + + public static partial class PEntityBuilderExtensions + { + /// Marks the entity as sortable by popularity. + public static PEntityBuilder IsSortableByPopularity(this PEntityBuilder builder) + { + builder.GetOrCreateGenerator, SortPostsByPopularityParticleGenerator>(); + return builder; + } + } +} diff --git a/samples/WebApiNet9MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs b/samples/WebApiNet9MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs new file mode 100644 index 0000000..1c1194e --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs @@ -0,0 +1,27 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.PannerExtensions +{ + using global::Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.EFModel; + using System.Linq; + + public class SortPostByPopularityParticle : ISortParticle + { + readonly bool Descending; + + public SortPostByPopularityParticle(bool descending) + { + this.Descending = descending; + } + + public IOrderedQueryable ApplyTo(IOrderedQueryable source) + { + if (this.Descending) + return source + .ThenByDescending(x => x.AmtLikes) + .ThenByDescending(x => x.AmtComments); + else + return source + .ThenBy(x => x.AmtLikes) + .ThenBy(x => x.AmtComments); + } + } +} diff --git a/samples/WebApiNet9MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs b/samples/WebApiNet9MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs new file mode 100644 index 0000000..a1e3f94 --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs @@ -0,0 +1,22 @@ +using Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.EFModel; + +namespace Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.PannerExtensions +{ + public class SortPostsByPopularityParticleGenerator : ISortParticleGenerator + { + public bool TryGenerate(IPContext context, string input, out ISortParticle particle) + { + var descending = input.StartsWith('-'); + var remaining = descending ? input.Substring(1) : input; + + if (!remaining.Trim().Equals("Popularity", System.StringComparison.OrdinalIgnoreCase)) + { + particle = null; + return false; + } + + particle = new SortPostByPopularityParticle(descending); + return true; + } + } +} diff --git a/samples/WebApiNet9MinimalFluent/Program.cs b/samples/WebApiNet9MinimalFluent/Program.cs index d5e0ef3..5f8854c 100644 --- a/samples/WebApiNet9MinimalFluent/Program.cs +++ b/samples/WebApiNet9MinimalFluent/Program.cs @@ -1,12 +1,37 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.EFModel; +using Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.PannerExtensions; +using Panner.Builders; + var builder = WebApplication.CreateBuilder(args); -// Add services to the container. -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); +builder.Services.UsePanner(c => +{ + c.Entity() + .IsSortableByPopularity() + .Property(x => x.Id, o => o + .IsSortableAs(nameof(Views.Post.Id)) + .IsFilterableAs(nameof(Views.Post.Id)) + ) + .Property(x => x.Title, o => o + .IsSortableAs(nameof(Views.Post.Title)) + ) + .Property(x => x.CreatedOn, o => o + .IsSortableAs(nameof(Views.Post.Creation)) + .IsFilterableAs(nameof(Views.Post.Creation)) + ); +}); + +builder.Services.AddDbContext(options => +{ + options.UseInMemoryDatabase("BlogDb"); +}); + var app = builder.Build(); -// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.MapOpenApi(); @@ -14,28 +39,21 @@ app.UseHttpsRedirection(); -var summaries = new[] +app.MapGet("/posts", async ([FromServices] BlogContext blogContext, [FromQuery] IReadOnlyCollection> sorts, [FromQuery] IReadOnlyCollection> filters) => { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" -}; - -app.MapGet("/weatherforecast", () => - { - var forecast = Enumerable.Range(1, 5).Select(index => - new WeatherForecast - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - return forecast; - }) - .WithName("GetWeatherForecast"); + blogContext.Database.EnsureCreated(); + var result = await blogContext.Posts + .Apply(filters) + .Apply(sorts) + .Select(x => new Views.Post + { + Id = x.Id, + Title = x.Title, + Content = x.Content, + Creation = x.CreatedOn + }) + .ToArrayAsync(); + return Results.Ok(result); +}); app.Run(); - -record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) -{ - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} \ No newline at end of file diff --git a/samples/WebApiNet9MinimalFluent/Views/Post.cs b/samples/WebApiNet9MinimalFluent/Views/Post.cs new file mode 100644 index 0000000..b6a283c --- /dev/null +++ b/samples/WebApiNet9MinimalFluent/Views/Post.cs @@ -0,0 +1,12 @@ +namespace Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.Views +{ + using System; + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime Creation { get; set; } + } +} diff --git a/samples/WebApiNet9MinimalFluent/WebApiNet9MinimalFluent.csproj b/samples/WebApiNet9MinimalFluent/WebApiNet9MinimalFluent.csproj index dc51723..f8462fa 100644 --- a/samples/WebApiNet9MinimalFluent/WebApiNet9MinimalFluent.csproj +++ b/samples/WebApiNet9MinimalFluent/WebApiNet9MinimalFluent.csproj @@ -10,7 +10,13 @@ - - + + + + + + + + From 09a98f4ac294324629ad8d85da92931b257dd1b8 Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 02:23:27 -0400 Subject: [PATCH 06/13] Correct errors and delete old samples --- Panner.AspNetCore.sln | 14 ---- .../Source/Features/Posts/Endpoint.cs | 2 + .../Source/Features/Posts/Request.cs | 6 +- sample/FastEndpoints/Source/Program.cs | 4 +- samples/AspNet5_0/AspNet5_0.csproj | 19 ----- .../AspNet5_0/Controllers/PostsController.cs | 48 ----------- samples/AspNet5_0/EFModel/BlogContext.cs | 47 ----------- samples/AspNet5_0/EFModel/Post.cs | 15 ---- samples/AspNet5_0/Models/ErrorViewModel.cs | 11 --- ...tityBuilder.Post.IsSortableByPopularity.cs | 15 ---- .../SortPostByPopularityParticle.cs | 27 ------ .../SortPostsByPopularityParticleGenerator.cs | 22 ----- samples/AspNet5_0/Program.cs | 26 ------ .../AspNet5_0/Properties/launchSettings.json | 28 ------- samples/AspNet5_0/Startup.cs | 83 ------------------- samples/AspNet5_0/Views/Post.cs | 12 --- .../AspNet5_0/appsettings.Development.json | 9 -- samples/AspNet5_0/appsettings.json | 10 --- samples/AspNetCore3_1/AspNetCore3_1.csproj | 19 ----- .../Controllers/PostsController.cs | 48 ----------- samples/AspNetCore3_1/EFModel/BlogContext.cs | 47 ----------- samples/AspNetCore3_1/EFModel/Post.cs | 15 ---- .../AspNetCore3_1/Models/ErrorViewModel.cs | 11 --- ...tityBuilder.Post.IsSortableByPopularity.cs | 15 ---- .../SortPostByPopularityParticle.cs | 27 ------ .../SortPostsByPopularityParticleGenerator.cs | 22 ----- samples/AspNetCore3_1/Program.cs | 26 ------ .../Properties/launchSettings.json | 27 ------ samples/AspNetCore3_1/Startup.cs | 83 ------------------- samples/AspNetCore3_1/Views/Post.cs | 12 --- .../appsettings.Development.json | 9 -- samples/AspNetCore3_1/appsettings.json | 10 --- samples/WebAPINet8MinimalFluent/Program.cs | 3 + samples/WebApi8ControllersFluent/Program.cs | 3 + .../WebApiNet9ControllersFluent/Program.cs | 2 + samples/WebApiNet9MinimalFluent/Program.cs | 9 +- 36 files changed, 25 insertions(+), 761 deletions(-) delete mode 100644 samples/AspNet5_0/AspNet5_0.csproj delete mode 100644 samples/AspNet5_0/Controllers/PostsController.cs delete mode 100644 samples/AspNet5_0/EFModel/BlogContext.cs delete mode 100644 samples/AspNet5_0/EFModel/Post.cs delete mode 100644 samples/AspNet5_0/Models/ErrorViewModel.cs delete mode 100644 samples/AspNet5_0/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs delete mode 100644 samples/AspNet5_0/PannerExtensions/SortPostByPopularityParticle.cs delete mode 100644 samples/AspNet5_0/PannerExtensions/SortPostsByPopularityParticleGenerator.cs delete mode 100644 samples/AspNet5_0/Program.cs delete mode 100644 samples/AspNet5_0/Properties/launchSettings.json delete mode 100644 samples/AspNet5_0/Startup.cs delete mode 100644 samples/AspNet5_0/Views/Post.cs delete mode 100644 samples/AspNet5_0/appsettings.Development.json delete mode 100644 samples/AspNet5_0/appsettings.json delete mode 100644 samples/AspNetCore3_1/AspNetCore3_1.csproj delete mode 100644 samples/AspNetCore3_1/Controllers/PostsController.cs delete mode 100644 samples/AspNetCore3_1/EFModel/BlogContext.cs delete mode 100644 samples/AspNetCore3_1/EFModel/Post.cs delete mode 100644 samples/AspNetCore3_1/Models/ErrorViewModel.cs delete mode 100644 samples/AspNetCore3_1/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs delete mode 100644 samples/AspNetCore3_1/PannerExtensions/SortPostByPopularityParticle.cs delete mode 100644 samples/AspNetCore3_1/PannerExtensions/SortPostsByPopularityParticleGenerator.cs delete mode 100644 samples/AspNetCore3_1/Program.cs delete mode 100644 samples/AspNetCore3_1/Properties/launchSettings.json delete mode 100644 samples/AspNetCore3_1/Startup.cs delete mode 100644 samples/AspNetCore3_1/Views/Post.cs delete mode 100644 samples/AspNetCore3_1/appsettings.Development.json delete mode 100644 samples/AspNetCore3_1/appsettings.json diff --git a/Panner.AspNetCore.sln b/Panner.AspNetCore.sln index f8314b9..ca9ab8e 100644 --- a/Panner.AspNetCore.sln +++ b/Panner.AspNetCore.sln @@ -9,10 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Panner.AspNetCore", "src\Pa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Panner.AspNetCore.Tests", "tests\Panner.AspNetCore.Tests\Panner.AspNetCore.Tests.csproj", "{2FA57751-5D7A-430D-89F8-CB06B75937FA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore3_1", "samples\AspNetCore3_1\AspNetCore3_1.csproj", "{FC10AD84-66D7-4C98-9A3B-620D6ACC1357}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet5_0", "samples\AspNet5_0\AspNet5_0.csproj", "{00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNet9MinimalFluent", "samples\WebApiNet9MinimalFluent\WebApiNet9MinimalFluent.csproj", "{84F94171-2814-4993-9BC6-43D7F9535027}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNet9ControllersFluent", "samples\WebApiNet9ControllersFluent\WebApiNet9ControllersFluent.csproj", "{D7BE4E4E-38E0-4DCC-A8CB-7D9CE22994DB}" @@ -37,14 +33,6 @@ Global {2FA57751-5D7A-430D-89F8-CB06B75937FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {2FA57751-5D7A-430D-89F8-CB06B75937FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {2FA57751-5D7A-430D-89F8-CB06B75937FA}.Release|Any CPU.Build.0 = Release|Any CPU - {FC10AD84-66D7-4C98-9A3B-620D6ACC1357}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC10AD84-66D7-4C98-9A3B-620D6ACC1357}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC10AD84-66D7-4C98-9A3B-620D6ACC1357}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC10AD84-66D7-4C98-9A3B-620D6ACC1357}.Release|Any CPU.Build.0 = Release|Any CPU - {00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}.Release|Any CPU.Build.0 = Release|Any CPU {84F94171-2814-4993-9BC6-43D7F9535027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {84F94171-2814-4993-9BC6-43D7F9535027}.Debug|Any CPU.Build.0 = Debug|Any CPU {84F94171-2814-4993-9BC6-43D7F9535027}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -70,8 +58,6 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {FC10AD84-66D7-4C98-9A3B-620D6ACC1357} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} - {00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} {84F94171-2814-4993-9BC6-43D7F9535027} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} {D7BE4E4E-38E0-4DCC-A8CB-7D9CE22994DB} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} {39BD3C99-BBB1-48BA-B015-92C30A8813CC} = {D40300B7-1803-48CB-8F7D-6A96AB75F666} diff --git a/sample/FastEndpoints/Source/Features/Posts/Endpoint.cs b/sample/FastEndpoints/Source/Features/Posts/Endpoint.cs index 4fd2bfc..934eaf5 100644 --- a/sample/FastEndpoints/Source/Features/Posts/Endpoint.cs +++ b/sample/FastEndpoints/Source/Features/Posts/Endpoint.cs @@ -1,5 +1,7 @@ using Microsoft.EntityFrameworkCore; +using Panner; using Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel; +using Views = Panner.AspNetCore.Samples.FastEndpointsNet9.Views; using Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions; namespace Posts; diff --git a/sample/FastEndpoints/Source/Features/Posts/Request.cs b/sample/FastEndpoints/Source/Features/Posts/Request.cs index e881281..5afaf57 100644 --- a/sample/FastEndpoints/Source/Features/Posts/Request.cs +++ b/sample/FastEndpoints/Source/Features/Posts/Request.cs @@ -1,14 +1,16 @@ using Microsoft.AspNetCore.Mvc; + using Panner; using Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel; +using FastEndpoints; namespace Posts; sealed class Request { - [FromQuery] + [FastEndpoints.FromQuery] public IReadOnlyCollection>? Sorts { get; set; } - [FromQuery] + [FastEndpoints.FromQuery] public IReadOnlyCollection>? Filters { get; set; } } diff --git a/sample/FastEndpoints/Source/Program.cs b/sample/FastEndpoints/Source/Program.cs index d3a7f06..9b39955 100644 --- a/sample/FastEndpoints/Source/Program.cs +++ b/sample/FastEndpoints/Source/Program.cs @@ -1,6 +1,9 @@ using Microsoft.EntityFrameworkCore; +using Panner.AspNetCore; +using Panner.AspNetCore.Samples.FastEndpointsNet9; using Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel; using Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions; +using Views = Panner.AspNetCore.Samples.FastEndpointsNet9.Views; using Panner.Builders; var bld = WebApplication.CreateBuilder(args); @@ -35,7 +38,6 @@ .UseAuthorization() .UseFastEndpoints(c => { - c.Binding.ReflectionCache.AddFromFastEndpoints(); c.Errors.UseProblemDetails(); }) .UseSwaggerGen(); diff --git a/samples/AspNet5_0/AspNet5_0.csproj b/samples/AspNet5_0/AspNet5_0.csproj deleted file mode 100644 index 9058798..0000000 --- a/samples/AspNet5_0/AspNet5_0.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net5.0 - Panner.AspNetCore.Samples.AspNetCore5_0 - Panner.AspNetCore.Samples.AspNetCore5_0 - false - - - - - - - - - - - - diff --git a/samples/AspNet5_0/Controllers/PostsController.cs b/samples/AspNet5_0/Controllers/PostsController.cs deleted file mode 100644 index d0aa944..0000000 --- a/samples/AspNet5_0/Controllers/PostsController.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNet5_0.Controllers -{ - using Microsoft.AspNetCore.Mvc; - using Microsoft.EntityFrameworkCore; - using Microsoft.Extensions.Logging; - using Panner.AspNetCore.Samples.AspNet5_0.EFModel; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - - [ApiController] - [Route("[controller]")] - public class PostsController : ControllerBase - { - private readonly ILogger _logger; - - public PostsController(ILogger logger) - { - _logger = logger; - } - - [HttpGet] - public async Task GetAllPosts( - [FromServices] BlogContext blogContext, - [FromQuery] IReadOnlyCollection> sorts, - [FromQuery] IReadOnlyCollection> filters - ) - { - // Workaround for: https://github.com/aspnet/EntityFrameworkCore/issues/11666 - // This is only because we're seeding data OnModelCreation for an InMemoryDatabase - blogContext.Database.EnsureCreated(); - - var result = await blogContext.Posts - .Apply(filters) - .Apply(sorts) - .Select(x => new Views.Post() - { - Id = x.Id, - Title = x.Title, - Content = x.Content, - Creation = x.CreatedOn - }) - .ToArrayAsync(); - - return Ok(result); - } - } -} diff --git a/samples/AspNet5_0/EFModel/BlogContext.cs b/samples/AspNet5_0/EFModel/BlogContext.cs deleted file mode 100644 index cecad33..0000000 --- a/samples/AspNet5_0/EFModel/BlogContext.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNet5_0.EFModel -{ - using Microsoft.EntityFrameworkCore; - using System; - - public class BlogContext : DbContext - { - public DbSet Posts { get; set; } - - public BlogContext() : base() - { - } - - public BlogContext(DbContextOptions options) : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity() - .HasData(new Post() - { - Id = 1, - Title = Guid.NewGuid().ToString(), - Content = Guid.NewGuid().ToString(), - CreatedOn = DateTime.UtcNow, - IsVisible = true - }, new Post() - { - Id = 2, - Title = Guid.NewGuid().ToString(), - Content = Guid.NewGuid().ToString(), - CreatedOn = DateTime.UtcNow, - IsVisible = false - }, new Post() - { - Id = 3, - Title = Guid.NewGuid().ToString(), - Content = Guid.NewGuid().ToString(), - CreatedOn = DateTime.UtcNow, - IsVisible = true - }); - } - } -} diff --git a/samples/AspNet5_0/EFModel/Post.cs b/samples/AspNet5_0/EFModel/Post.cs deleted file mode 100644 index 4674b54..0000000 --- a/samples/AspNet5_0/EFModel/Post.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNet5_0.EFModel -{ - using System; - - public class Post - { - public int Id { get; set; } - public string Title { get; set; } - public string Content { get; set; } - public bool IsVisible { get; set; } - public int AmtLikes { get; set; } - public int AmtComments { get; set; } - public DateTime CreatedOn { get; set; } - } -} diff --git a/samples/AspNet5_0/Models/ErrorViewModel.cs b/samples/AspNet5_0/Models/ErrorViewModel.cs deleted file mode 100644 index aa14676..0000000 --- a/samples/AspNet5_0/Models/ErrorViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Panner.AspNetCore.Samples.AspNet5_0.Models -{ - public class ErrorViewModel - { - public string RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - } -} diff --git a/samples/AspNet5_0/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs b/samples/AspNet5_0/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs deleted file mode 100644 index f122f53..0000000 --- a/samples/AspNet5_0/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNet5_0.PannerExtensions -{ - using global::Panner.AspNetCore.Samples.AspNet5_0.EFModel; - using global::Panner.Builders; - - public static partial class PEntityBuilderExtensions - { - /// Marks the entity as sortable by popularity. - public static PEntityBuilder IsSortableByPopularity(this PEntityBuilder builder) - { - builder.GetOrCreateGenerator, SortPostsByPopularityParticleGenerator>(); - return builder; - } - } -} diff --git a/samples/AspNet5_0/PannerExtensions/SortPostByPopularityParticle.cs b/samples/AspNet5_0/PannerExtensions/SortPostByPopularityParticle.cs deleted file mode 100644 index 9eda449..0000000 --- a/samples/AspNet5_0/PannerExtensions/SortPostByPopularityParticle.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNet5_0.PannerExtensions -{ - using global::Panner.AspNetCore.Samples.AspNet5_0.EFModel; - using System.Linq; - - public class SortPostByPopularityParticle : ISortParticle - { - readonly bool Descending; - - public SortPostByPopularityParticle(bool descending) - { - this.Descending = descending; - } - - public IOrderedQueryable ApplyTo(IOrderedQueryable source) - { - if (this.Descending) - return source - .ThenByDescending(x => x.AmtLikes) - .ThenByDescending(x => x.AmtComments); - else - return source - .ThenBy(x => x.AmtLikes) - .ThenBy(x => x.AmtComments); - } - } -} diff --git a/samples/AspNet5_0/PannerExtensions/SortPostsByPopularityParticleGenerator.cs b/samples/AspNet5_0/PannerExtensions/SortPostsByPopularityParticleGenerator.cs deleted file mode 100644 index 5a163a6..0000000 --- a/samples/AspNet5_0/PannerExtensions/SortPostsByPopularityParticleGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Panner.AspNetCore.Samples.AspNet5_0.EFModel; - -namespace Panner.AspNetCore.Samples.AspNet5_0.PannerExtensions -{ - public class SortPostsByPopularityParticleGenerator : ISortParticleGenerator - { - public bool TryGenerate(IPContext context, string input, out ISortParticle particle) - { - var descending = input.StartsWith('-'); - var remaining = descending ? input.Substring(1) : input; - - if (!remaining.Trim().Equals("Popularity", System.StringComparison.OrdinalIgnoreCase)) - { - particle = null; - return false; - } - - particle = new SortPostByPopularityParticle(descending); - return true; - } - } -} diff --git a/samples/AspNet5_0/Program.cs b/samples/AspNet5_0/Program.cs deleted file mode 100644 index 9419da5..0000000 --- a/samples/AspNet5_0/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Panner.AspNetCore.Samples.AspNet5_0 -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/samples/AspNet5_0/Properties/launchSettings.json b/samples/AspNet5_0/Properties/launchSettings.json deleted file mode 100644 index e2f5235..0000000 --- a/samples/AspNet5_0/Properties/launchSettings.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:61036", - "sslPort": 44392 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "AspNet5_0": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/samples/AspNet5_0/Startup.cs b/samples/AspNet5_0/Startup.cs deleted file mode 100644 index c175cda..0000000 --- a/samples/AspNet5_0/Startup.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Panner.AspNetCore.Samples.AspNet5_0.EFModel; -using Panner.AspNetCore.Samples.AspNet5_0.PannerExtensions; -using Panner.Builders; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Panner.AspNetCore.Samples.AspNet5_0 -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.UsePanner(c => - { - c.Entity() - .IsSortableByPopularity() - .Property(x => x.Id, o => o - .IsSortableAs(nameof(Views.Post.Id)) - .IsFilterableAs(nameof(Views.Post.Id)) - ) - .Property(x => x.Title, o => o - .IsSortableAs(nameof(Views.Post.Title)) - ) - .Property(x => x.CreatedOn, o => o - .IsSortableAs(nameof(Views.Post.Creation)) - .IsFilterableAs(nameof(Views.Post.Creation)) - ); - }); - - services.AddDbContext(options => - { - options.UseInMemoryDatabase("BlogDb"); - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - }); - } - } -} diff --git a/samples/AspNet5_0/Views/Post.cs b/samples/AspNet5_0/Views/Post.cs deleted file mode 100644 index 96cf581..0000000 --- a/samples/AspNet5_0/Views/Post.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNet5_0.Views -{ - using System; - - public class Post - { - public int Id { get; set; } - public string Title { get; set; } - public string Content { get; set; } - public DateTime Creation { get; set; } - } -} diff --git a/samples/AspNet5_0/appsettings.Development.json b/samples/AspNet5_0/appsettings.Development.json deleted file mode 100644 index 8983e0f..0000000 --- a/samples/AspNet5_0/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/samples/AspNet5_0/appsettings.json b/samples/AspNet5_0/appsettings.json deleted file mode 100644 index d9d9a9b..0000000 --- a/samples/AspNet5_0/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/samples/AspNetCore3_1/AspNetCore3_1.csproj b/samples/AspNetCore3_1/AspNetCore3_1.csproj deleted file mode 100644 index 75ad524..0000000 --- a/samples/AspNetCore3_1/AspNetCore3_1.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - netcoreapp3.1 - Panner.AspNetCore.Samples.AspNetCore3_1 - Panner.AspNetCore.Samples.AspNetCore3_1 - false - - - - - - - - - - - - diff --git a/samples/AspNetCore3_1/Controllers/PostsController.cs b/samples/AspNetCore3_1/Controllers/PostsController.cs deleted file mode 100644 index 46e9670..0000000 --- a/samples/AspNetCore3_1/Controllers/PostsController.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNetCore3_1.Controllers -{ - using Microsoft.AspNetCore.Mvc; - using Microsoft.EntityFrameworkCore; - using Microsoft.Extensions.Logging; - using Panner.AspNetCore.Samples.AspNetCore3_1.EFModel; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - - [ApiController] - [Route("[controller]")] - public class PostsController : ControllerBase - { - private readonly ILogger _logger; - - public PostsController(ILogger logger) - { - _logger = logger; - } - - [HttpGet] - public async Task GetAllPosts( - [FromServices] BlogContext blogContext, - [FromQuery] IReadOnlyCollection> sorts, - [FromQuery] IReadOnlyCollection> filters - ) - { - // Workaround for: https://github.com/aspnet/EntityFrameworkCore/issues/11666 - // This is only because we're seeding data OnModelCreation for an InMemoryDatabase - blogContext.Database.EnsureCreated(); - - var result = await blogContext.Posts - .Apply(filters) - .Apply(sorts) - .Select(x => new Views.Post() - { - Id = x.Id, - Title = x.Title, - Content = x.Content, - Creation = x.CreatedOn - }) - .ToArrayAsync(); - - return Ok(result); - } - } -} diff --git a/samples/AspNetCore3_1/EFModel/BlogContext.cs b/samples/AspNetCore3_1/EFModel/BlogContext.cs deleted file mode 100644 index b92ad6f..0000000 --- a/samples/AspNetCore3_1/EFModel/BlogContext.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNetCore3_1.EFModel -{ - using Microsoft.EntityFrameworkCore; - using System; - - public class BlogContext : DbContext - { - public DbSet Posts { get; set; } - - public BlogContext() : base() - { - } - - public BlogContext(DbContextOptions options) : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity() - .HasData(new Post() - { - Id = 1, - Title = Guid.NewGuid().ToString(), - Content = Guid.NewGuid().ToString(), - CreatedOn = DateTime.UtcNow, - IsVisible = true - }, new Post() - { - Id = 2, - Title = Guid.NewGuid().ToString(), - Content = Guid.NewGuid().ToString(), - CreatedOn = DateTime.UtcNow, - IsVisible = false - }, new Post() - { - Id = 3, - Title = Guid.NewGuid().ToString(), - Content = Guid.NewGuid().ToString(), - CreatedOn = DateTime.UtcNow, - IsVisible = true - }); - } - } -} diff --git a/samples/AspNetCore3_1/EFModel/Post.cs b/samples/AspNetCore3_1/EFModel/Post.cs deleted file mode 100644 index 123523b..0000000 --- a/samples/AspNetCore3_1/EFModel/Post.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNetCore3_1.EFModel -{ - using System; - - public class Post - { - public int Id { get; set; } - public string Title { get; set; } - public string Content { get; set; } - public bool IsVisible { get; set; } - public int AmtLikes { get; set; } - public int AmtComments { get; set; } - public DateTime CreatedOn { get; set; } - } -} diff --git a/samples/AspNetCore3_1/Models/ErrorViewModel.cs b/samples/AspNetCore3_1/Models/ErrorViewModel.cs deleted file mode 100644 index 735eee0..0000000 --- a/samples/AspNetCore3_1/Models/ErrorViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Panner.AspNetCore.Samples.AspNetCore3_1.Models -{ - public class ErrorViewModel - { - public string RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - } -} diff --git a/samples/AspNetCore3_1/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs b/samples/AspNetCore3_1/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs deleted file mode 100644 index 7eaab68..0000000 --- a/samples/AspNetCore3_1/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNetCore3_1.PannerExtensions -{ - using global::Panner.AspNetCore.Samples.AspNetCore3_1.EFModel; - using global::Panner.Builders; - - public static partial class PEntityBuilderExtensions - { - /// Marks the entity as sortable by popularity. - public static PEntityBuilder IsSortableByPopularity(this PEntityBuilder builder) - { - builder.GetOrCreateGenerator, SortPostsByPopularityParticleGenerator>(); - return builder; - } - } -} diff --git a/samples/AspNetCore3_1/PannerExtensions/SortPostByPopularityParticle.cs b/samples/AspNetCore3_1/PannerExtensions/SortPostByPopularityParticle.cs deleted file mode 100644 index f95b229..0000000 --- a/samples/AspNetCore3_1/PannerExtensions/SortPostByPopularityParticle.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNetCore3_1.PannerExtensions -{ - using global::Panner.AspNetCore.Samples.AspNetCore3_1.EFModel; - using System.Linq; - - public class SortPostByPopularityParticle : ISortParticle - { - readonly bool Descending; - - public SortPostByPopularityParticle(bool descending) - { - this.Descending = descending; - } - - public IOrderedQueryable ApplyTo(IOrderedQueryable source) - { - if (this.Descending) - return source - .ThenByDescending(x => x.AmtLikes) - .ThenByDescending(x => x.AmtComments); - else - return source - .ThenBy(x => x.AmtLikes) - .ThenBy(x => x.AmtComments); - } - } -} diff --git a/samples/AspNetCore3_1/PannerExtensions/SortPostsByPopularityParticleGenerator.cs b/samples/AspNetCore3_1/PannerExtensions/SortPostsByPopularityParticleGenerator.cs deleted file mode 100644 index 3ae0837..0000000 --- a/samples/AspNetCore3_1/PannerExtensions/SortPostsByPopularityParticleGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Panner.AspNetCore.Samples.AspNetCore3_1.EFModel; - -namespace Panner.AspNetCore.Samples.AspNetCore3_1.PannerExtensions -{ - public class SortPostsByPopularityParticleGenerator : ISortParticleGenerator - { - public bool TryGenerate(IPContext context, string input, out ISortParticle particle) - { - var descending = input.StartsWith('-'); - var remaining = descending ? input.Substring(1) : input; - - if (!remaining.Trim().Equals("Popularity", System.StringComparison.OrdinalIgnoreCase)) - { - particle = null; - return false; - } - - particle = new SortPostByPopularityParticle(descending); - return true; - } - } -} diff --git a/samples/AspNetCore3_1/Program.cs b/samples/AspNetCore3_1/Program.cs deleted file mode 100644 index 0d509ad..0000000 --- a/samples/AspNetCore3_1/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Panner.AspNetCore.Samples.AspNetCore3_1 -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/samples/AspNetCore3_1/Properties/launchSettings.json b/samples/AspNetCore3_1/Properties/launchSettings.json deleted file mode 100644 index 8678457..0000000 --- a/samples/AspNetCore3_1/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:41110", - "sslPort": 44361 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "AspNetCore3_1": { - "commandName": "Project", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/samples/AspNetCore3_1/Startup.cs b/samples/AspNetCore3_1/Startup.cs deleted file mode 100644 index ffef553..0000000 --- a/samples/AspNetCore3_1/Startup.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Panner.AspNetCore.Samples.AspNetCore3_1.EFModel; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Panner.Builders; -using Panner.AspNetCore.Samples.AspNetCore3_1.PannerExtensions; - -namespace Panner.AspNetCore.Samples.AspNetCore3_1 -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.UsePanner(c => - { - c.Entity() - .IsSortableByPopularity() - .Property(x => x.Id, o => o - .IsSortableAs(nameof(Views.Post.Id)) - .IsFilterableAs(nameof(Views.Post.Id)) - ) - .Property(x => x.Title, o => o - .IsSortableAs(nameof(Views.Post.Title)) - ) - .Property(x => x.CreatedOn, o => o - .IsSortableAs(nameof(Views.Post.Creation)) - .IsFilterableAs(nameof(Views.Post.Creation)) - ); - }); - - services.AddDbContext(options => - { - options.UseInMemoryDatabase("BlogDb"); - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - }); - } - } -} diff --git a/samples/AspNetCore3_1/Views/Post.cs b/samples/AspNetCore3_1/Views/Post.cs deleted file mode 100644 index 254ad6e..0000000 --- a/samples/AspNetCore3_1/Views/Post.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Panner.AspNetCore.Samples.AspNetCore3_1.Views -{ - using System; - - public class Post - { - public int Id { get; set; } - public string Title { get; set; } - public string Content { get; set; } - public DateTime Creation { get; set; } - } -} diff --git a/samples/AspNetCore3_1/appsettings.Development.json b/samples/AspNetCore3_1/appsettings.Development.json deleted file mode 100644 index 8983e0f..0000000 --- a/samples/AspNetCore3_1/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/samples/AspNetCore3_1/appsettings.json b/samples/AspNetCore3_1/appsettings.json deleted file mode 100644 index d9d9a9b..0000000 --- a/samples/AspNetCore3_1/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/samples/WebAPINet8MinimalFluent/Program.cs b/samples/WebAPINet8MinimalFluent/Program.cs index f71ccda..e764f80 100644 --- a/samples/WebAPINet8MinimalFluent/Program.cs +++ b/samples/WebAPINet8MinimalFluent/Program.cs @@ -1,7 +1,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Panner; +using Panner.AspNetCore; using Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel; using Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.PannerExtensions; +using Views = Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.Views; using Panner.Builders; var builder = WebApplication.CreateBuilder(args); diff --git a/samples/WebApi8ControllersFluent/Program.cs b/samples/WebApi8ControllersFluent/Program.cs index f5d22e0..5b06885 100644 --- a/samples/WebApi8ControllersFluent/Program.cs +++ b/samples/WebApi8ControllersFluent/Program.cs @@ -1,8 +1,11 @@ using Microsoft.EntityFrameworkCore; +using Panner.AspNetCore; using Panner.AspNetCore.Samples.WebApi8ControllersFluent.EFModel; using Panner.AspNetCore.Samples.WebApi8ControllersFluent.PannerExtensions; +using Views = Panner.AspNetCore.Samples.WebApi8ControllersFluent.Views; using Panner.Builders; + var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); diff --git a/samples/WebApiNet9ControllersFluent/Program.cs b/samples/WebApiNet9ControllersFluent/Program.cs index ef0368d..6eb0e70 100644 --- a/samples/WebApiNet9ControllersFluent/Program.cs +++ b/samples/WebApiNet9ControllersFluent/Program.cs @@ -1,6 +1,8 @@ using Microsoft.EntityFrameworkCore; +using Panner.AspNetCore; using Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.EFModel; using Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.PannerExtensions; +using Views = Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.Views; using Panner.Builders; var builder = WebApplication.CreateBuilder(args); diff --git a/samples/WebApiNet9MinimalFluent/Program.cs b/samples/WebApiNet9MinimalFluent/Program.cs index 5f8854c..f18aac2 100644 --- a/samples/WebApiNet9MinimalFluent/Program.cs +++ b/samples/WebApiNet9MinimalFluent/Program.cs @@ -1,7 +1,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Panner; +using Panner.AspNetCore; using Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.EFModel; using Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.PannerExtensions; +using Views = Panner.AspNetCore.Samples.WebApiNet9MinimalFluent.Views; using Panner.Builders; var builder = WebApplication.CreateBuilder(args); @@ -39,7 +42,11 @@ app.UseHttpsRedirection(); -app.MapGet("/posts", async ([FromServices] BlogContext blogContext, [FromQuery] IReadOnlyCollection> sorts, [FromQuery] IReadOnlyCollection> filters) => +app.MapGet("/posts", async ( + [FromServices] BlogContext blogContext, + [FromQuery] IReadOnlyCollection> sorts, + [FromQuery] IReadOnlyCollection> filters +) => { blogContext.Database.EnsureCreated(); var result = await blogContext.Posts From 77165d763b029999ceb7afd4675c365a506ed09c Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 02:57:26 -0400 Subject: [PATCH 07/13] Add minimal API model binding (#6) --- samples/WebAPINet8MinimalFluent/Program.cs | 5 +- samples/WebApiNet9MinimalFluent/Program.cs | 6 +-- src/Minimal/FilterParticles.cs | 58 ++++++++++++++++++++++ src/Minimal/SortParticles.cs | 58 ++++++++++++++++++++++ 4 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 src/Minimal/FilterParticles.cs create mode 100644 src/Minimal/SortParticles.cs diff --git a/samples/WebAPINet8MinimalFluent/Program.cs b/samples/WebAPINet8MinimalFluent/Program.cs index e764f80..ea49861 100644 --- a/samples/WebAPINet8MinimalFluent/Program.cs +++ b/samples/WebAPINet8MinimalFluent/Program.cs @@ -44,7 +44,10 @@ app.UseHttpsRedirection(); -app.MapGet("/posts", async ([FromServices] BlogContext blogContext, [FromQuery] IReadOnlyCollection> sorts, [FromQuery] IReadOnlyCollection> filters) => +app.MapGet("/posts", async ( + [FromServices] BlogContext blogContext, + SortParticles sorts, + FilterParticles filters) => { blogContext.Database.EnsureCreated(); var result = await blogContext.Posts diff --git a/samples/WebApiNet9MinimalFluent/Program.cs b/samples/WebApiNet9MinimalFluent/Program.cs index f18aac2..7acef1d 100644 --- a/samples/WebApiNet9MinimalFluent/Program.cs +++ b/samples/WebApiNet9MinimalFluent/Program.cs @@ -43,9 +43,9 @@ app.UseHttpsRedirection(); app.MapGet("/posts", async ( - [FromServices] BlogContext blogContext, - [FromQuery] IReadOnlyCollection> sorts, - [FromQuery] IReadOnlyCollection> filters + [FromServices] BlogContext blogContext, + SortParticles sorts, + FilterParticles filters ) => { blogContext.Database.EnsureCreated(); diff --git a/src/Minimal/FilterParticles.cs b/src/Minimal/FilterParticles.cs new file mode 100644 index 0000000..ed6d999 --- /dev/null +++ b/src/Minimal/FilterParticles.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Panner.AspNetCore +{ + public sealed class FilterParticles : IReadOnlyCollection> + where TEntity : class + { + private readonly IReadOnlyCollection> _particles; + + private FilterParticles(IReadOnlyCollection> particles) + { + _particles = particles; + } + + public static ValueTask> BindAsync(HttpContext context, ParameterInfo parameter) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (parameter == null) + { + throw new ArgumentNullException(nameof(parameter)); + } + + var queryName = parameter.Name ?? "filters"; + StringValues value = context.Request.Query[queryName]; + + var pContext = context.RequestServices.GetRequiredService(); + + if (StringValues.IsNullOrEmpty(value)) + { + return ValueTask.FromResult(new FilterParticles(Array.Empty>())); + } + + if (!pContext.TryParseCsv(value.ToString(), out IEnumerable> particles)) + { + throw new InvalidOperationException("Could not parse provided filters."); + } + + return ValueTask.FromResult(new FilterParticles(particles.ToArray())); + } + + public int Count => _particles.Count; + public IEnumerator> GetEnumerator() => _particles.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IReadOnlyCollection> Value => _particles; + } +} diff --git a/src/Minimal/SortParticles.cs b/src/Minimal/SortParticles.cs new file mode 100644 index 0000000..b0e6682 --- /dev/null +++ b/src/Minimal/SortParticles.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Panner.AspNetCore +{ + public sealed class SortParticles : IReadOnlyCollection> + where TEntity : class + { + private readonly IReadOnlyCollection> _particles; + + private SortParticles(IReadOnlyCollection> particles) + { + _particles = particles; + } + + public static ValueTask> BindAsync(HttpContext context, ParameterInfo parameter) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (parameter == null) + { + throw new ArgumentNullException(nameof(parameter)); + } + + var queryName = parameter.Name ?? "sorts"; + StringValues value = context.Request.Query[queryName]; + + var pContext = context.RequestServices.GetRequiredService(); + + if (StringValues.IsNullOrEmpty(value)) + { + return ValueTask.FromResult(new SortParticles(Array.Empty>())); + } + + if (!pContext.TryParseCsv(value.ToString(), out IEnumerable> particles)) + { + throw new InvalidOperationException("Could not parse provided sorts."); + } + + return ValueTask.FromResult(new SortParticles(particles.ToArray())); + } + + public int Count => _particles.Count; + public IEnumerator> GetEnumerator() => _particles.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IReadOnlyCollection> Value => _particles; + } +} From 2f9bf00d34cba149a15e481830dc75edf0c485fa Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 03:07:38 -0400 Subject: [PATCH 08/13] Clean up --- .../Controllers/WeatherForecastController.cs | 32 ------------------- .../Controllers/WeatherForecastController.cs | 32 ------------------- sonar-project.properties | 2 ++ 3 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 samples/WebApi8ControllersFluent/Controllers/WeatherForecastController.cs delete mode 100644 samples/WebApiNet9ControllersFluent/Controllers/WeatherForecastController.cs create mode 100644 sonar-project.properties diff --git a/samples/WebApi8ControllersFluent/Controllers/WeatherForecastController.cs b/samples/WebApi8ControllersFluent/Controllers/WeatherForecastController.cs deleted file mode 100644 index d095ac4..0000000 --- a/samples/WebApi8ControllersFluent/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace WebApi8ControllersFluent.Controllers; - -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} \ No newline at end of file diff --git a/samples/WebApiNet9ControllersFluent/Controllers/WeatherForecastController.cs b/samples/WebApiNet9ControllersFluent/Controllers/WeatherForecastController.cs deleted file mode 100644 index 4174ac7..0000000 --- a/samples/WebApiNet9ControllersFluent/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace WebApiNet9ControllersFluent.Controllers; - -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..2f4a93d --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,2 @@ +# Exclude samples from code duplication. We want samples to be self-contained so they might duplicate eachother. +sonar.cpd.exclusions=samples/** From d620928828eac39267f3b6eeb983d5ed4d9781c9 Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 10:16:23 -0400 Subject: [PATCH 09/13] Fix workflow --- .github/workflows/test-and-report.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-and-report.yml b/.github/workflows/test-and-report.yml index cc323a4..31d65b0 100644 --- a/.github/workflows/test-and-report.yml +++ b/.github/workflows/test-and-report.yml @@ -2,6 +2,8 @@ name: test-and-report.yml on: pull_request: push: + branches: + - 'main' paths: - 'samples/**' - 'src/**' @@ -15,11 +17,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Check-out - uses: actions/checkout@master + uses: actions/checkout@v4 - name: Install Dotnet - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.x + dotnet-version: | + 8.x + 9.x - name: Install ReportGenerator run: dotnet tool install dotnet-reportgenerator-globaltool --tool-path tools - name: Test From 4f1fd0d6dee9edf905de5cbc74fa0010b4e8b56d Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 10:26:28 -0400 Subject: [PATCH 10/13] Correct coverage pattern --- .github/workflows/test-and-report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-and-report.yml b/.github/workflows/test-and-report.yml index 31d65b0..971c679 100644 --- a/.github/workflows/test-and-report.yml +++ b/.github/workflows/test-and-report.yml @@ -29,7 +29,7 @@ jobs: - name: Test run: dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov - name: Generate Coverage Report - run: tools/reportgenerator "-reports:tests/**/coverage.info" "-targetdir:./coverage/" -reporttypes:lcov + run: tools/reportgenerator "-reports:tests/**/coverage.*.info" "-targetdir:./coverage/" -reporttypes:lcov - name: Upload to Coveralls uses: coverallsapp/github-action@master with: From d5cccbe2265091b274cdd5ecb85a32a29427863b Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 11:02:15 -0400 Subject: [PATCH 11/13] Rename minimal binders --- samples/WebAPINet8MinimalFluent/Program.cs | 4 ++-- samples/WebApiNet9MinimalFluent/Program.cs | 4 ++-- .../FilterParticlesMinimalBinder.cs} | 10 +++++----- .../SortParticlesMinimalBinder.cs} | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) rename src/{Minimal/FilterParticles.cs => Binders/FilterParticlesMinimalBinder.cs} (73%) rename src/{Minimal/SortParticles.cs => Binders/SortParticlesMinimalBinder.cs} (73%) diff --git a/samples/WebAPINet8MinimalFluent/Program.cs b/samples/WebAPINet8MinimalFluent/Program.cs index ea49861..0efff1a 100644 --- a/samples/WebAPINet8MinimalFluent/Program.cs +++ b/samples/WebAPINet8MinimalFluent/Program.cs @@ -46,8 +46,8 @@ app.MapGet("/posts", async ( [FromServices] BlogContext blogContext, - SortParticles sorts, - FilterParticles filters) => + SortParticlesMinimalBinder sorts, + FilterParticlesMinimalBinder filters) => { blogContext.Database.EnsureCreated(); var result = await blogContext.Posts diff --git a/samples/WebApiNet9MinimalFluent/Program.cs b/samples/WebApiNet9MinimalFluent/Program.cs index 7acef1d..9183ec1 100644 --- a/samples/WebApiNet9MinimalFluent/Program.cs +++ b/samples/WebApiNet9MinimalFluent/Program.cs @@ -44,8 +44,8 @@ app.MapGet("/posts", async ( [FromServices] BlogContext blogContext, - SortParticles sorts, - FilterParticles filters + SortParticlesMinimalBinder sorts, + FilterParticlesMinimalBinder filters ) => { blogContext.Database.EnsureCreated(); diff --git a/src/Minimal/FilterParticles.cs b/src/Binders/FilterParticlesMinimalBinder.cs similarity index 73% rename from src/Minimal/FilterParticles.cs rename to src/Binders/FilterParticlesMinimalBinder.cs index ed6d999..4233b76 100644 --- a/src/Minimal/FilterParticles.cs +++ b/src/Binders/FilterParticlesMinimalBinder.cs @@ -10,17 +10,17 @@ namespace Panner.AspNetCore { - public sealed class FilterParticles : IReadOnlyCollection> + public sealed class FilterParticlesMinimalBinder : IReadOnlyCollection> where TEntity : class { private readonly IReadOnlyCollection> _particles; - private FilterParticles(IReadOnlyCollection> particles) + private FilterParticlesMinimalBinder(IReadOnlyCollection> particles) { _particles = particles; } - public static ValueTask> BindAsync(HttpContext context, ParameterInfo parameter) + public static ValueTask> BindAsync(HttpContext context, ParameterInfo parameter) { if (context == null) { @@ -38,7 +38,7 @@ public static ValueTask> BindAsync(HttpContext context, if (StringValues.IsNullOrEmpty(value)) { - return ValueTask.FromResult(new FilterParticles(Array.Empty>())); + return ValueTask.FromResult(new FilterParticlesMinimalBinder(Array.Empty>())); } if (!pContext.TryParseCsv(value.ToString(), out IEnumerable> particles)) @@ -46,7 +46,7 @@ public static ValueTask> BindAsync(HttpContext context, throw new InvalidOperationException("Could not parse provided filters."); } - return ValueTask.FromResult(new FilterParticles(particles.ToArray())); + return ValueTask.FromResult(new FilterParticlesMinimalBinder(particles.ToArray())); } public int Count => _particles.Count; diff --git a/src/Minimal/SortParticles.cs b/src/Binders/SortParticlesMinimalBinder.cs similarity index 73% rename from src/Minimal/SortParticles.cs rename to src/Binders/SortParticlesMinimalBinder.cs index b0e6682..4b25e98 100644 --- a/src/Minimal/SortParticles.cs +++ b/src/Binders/SortParticlesMinimalBinder.cs @@ -10,17 +10,17 @@ namespace Panner.AspNetCore { - public sealed class SortParticles : IReadOnlyCollection> + public sealed class SortParticlesMinimalBinder : IReadOnlyCollection> where TEntity : class { private readonly IReadOnlyCollection> _particles; - private SortParticles(IReadOnlyCollection> particles) + private SortParticlesMinimalBinder(IReadOnlyCollection> particles) { _particles = particles; } - public static ValueTask> BindAsync(HttpContext context, ParameterInfo parameter) + public static ValueTask> BindAsync(HttpContext context, ParameterInfo parameter) { if (context == null) { @@ -38,7 +38,7 @@ public static ValueTask> BindAsync(HttpContext context, P if (StringValues.IsNullOrEmpty(value)) { - return ValueTask.FromResult(new SortParticles(Array.Empty>())); + return ValueTask.FromResult(new SortParticlesMinimalBinder(Array.Empty>())); } if (!pContext.TryParseCsv(value.ToString(), out IEnumerable> particles)) @@ -46,7 +46,7 @@ public static ValueTask> BindAsync(HttpContext context, P throw new InvalidOperationException("Could not parse provided sorts."); } - return ValueTask.FromResult(new SortParticles(particles.ToArray())); + return ValueTask.FromResult(new SortParticlesMinimalBinder(particles.ToArray())); } public int Count => _particles.Count; From b8c665916ecb78ead95d10c5a677fff5db38204a Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 11:33:30 -0400 Subject: [PATCH 12/13] Clean up --- .gitignore | 2 ++ samples/WebApi8ControllersFluent/WeatherForecast.cs | 12 ------------ 2 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 samples/WebApi8ControllersFluent/WeatherForecast.cs diff --git a/.gitignore b/.gitignore index ab5a00a..f840a53 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ [Bb]in/ [Tt]est[Rr]esults/ nupkgs/ +.idea/ +.editorconfig diff --git a/samples/WebApi8ControllersFluent/WeatherForecast.cs b/samples/WebApi8ControllersFluent/WeatherForecast.cs deleted file mode 100644 index 789421e..0000000 --- a/samples/WebApi8ControllersFluent/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace WebApi8ControllersFluent; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} \ No newline at end of file From 5f3ade04b8725b4469c9dac9dc3c7c71fb5ce378 Mon Sep 17 00:00:00 2001 From: Nacho Orlandoni Date: Fri, 4 Jul 2025 12:20:22 -0400 Subject: [PATCH 13/13] Remove editorconfig from FastEndpoints sample (#7) --- .../.idea.Panner.AspNetCore/.idea/.gitignore | 13 -- .../.idea/indexLayout.xml | 8 - .idea/.idea.Panner.AspNetCore/.idea/vcs.xml | 7 - sample/FastEndpoints/.editorconfig | 188 ------------------ sample/FastEndpoints/FastEndpoints.sln | 31 --- 5 files changed, 247 deletions(-) delete mode 100644 .idea/.idea.Panner.AspNetCore/.idea/.gitignore delete mode 100644 .idea/.idea.Panner.AspNetCore/.idea/indexLayout.xml delete mode 100644 .idea/.idea.Panner.AspNetCore/.idea/vcs.xml delete mode 100644 sample/FastEndpoints/.editorconfig delete mode 100644 sample/FastEndpoints/FastEndpoints.sln diff --git a/.idea/.idea.Panner.AspNetCore/.idea/.gitignore b/.idea/.idea.Panner.AspNetCore/.idea/.gitignore deleted file mode 100644 index b054589..0000000 --- a/.idea/.idea.Panner.AspNetCore/.idea/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Rider ignored files -/modules.xml -/contentModel.xml -/.idea.Panner.AspNetCore.iml -/projectSettingsUpdater.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.idea.Panner.AspNetCore/.idea/indexLayout.xml b/.idea/.idea.Panner.AspNetCore/.idea/indexLayout.xml deleted file mode 100644 index 7b08163..0000000 --- a/.idea/.idea.Panner.AspNetCore/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/.idea.Panner.AspNetCore/.idea/vcs.xml b/.idea/.idea.Panner.AspNetCore/.idea/vcs.xml deleted file mode 100644 index 8306744..0000000 --- a/.idea/.idea.Panner.AspNetCore/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/sample/FastEndpoints/.editorconfig b/sample/FastEndpoints/.editorconfig deleted file mode 100644 index 7b81c04..0000000 --- a/sample/FastEndpoints/.editorconfig +++ /dev/null @@ -1,188 +0,0 @@ -[*] -charset = utf-8 -end_of_line = crlf -insert_final_newline = false -indent_style = space -indent_size = 4 - -[{*.har,*.json}] -indent_size = 2 - -[*.{received,verified}.{txt,xml,json}] -charset = "utf-8-bom" -end_of_line = lf -indent_size = unset -indent_style = unset -insert_final_newline = false -tab_width = unset -trim_trailing_whitespace = false - -[{*Request.cs,*Response.cs,*Model*.cs,*Endpoint.cs}] -dotnet_diagnostic.CS8618.severity = none - -[*.cs] -csharp_new_line_before_members_in_object_initializers = false -csharp_new_line_between_query_expression_clauses = false -csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion -csharp_style_prefer_utf8_string_literals = true:suggestion -csharp_style_var_elsewhere = true:suggestion -csharp_style_var_for_built_in_types = true:suggestion -csharp_style_var_when_type_is_apparent = true:suggestion -dotnet_naming_rule.parameters_rule.import_to_resharper = as_predefined -dotnet_naming_rule.parameters_rule.resharper_style = aaBb, AaBb -dotnet_naming_rule.parameters_rule.severity = warning -dotnet_naming_rule.parameters_rule.style = lower_camel_case_style_1 -dotnet_naming_rule.parameters_rule.symbols = parameters_symbols -dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined -dotnet_naming_rule.private_constants_rule.severity = warning -dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style -dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols -dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined -dotnet_naming_rule.private_static_readonly_rule.severity = warning -dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style -dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols -dotnet_naming_style.lower_camel_case_style.capitalization = camel_case -dotnet_naming_style.lower_camel_case_style.required_prefix = _ -dotnet_naming_style.lower_camel_case_style_1.capitalization = camel_case -dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case -dotnet_naming_symbols.parameters_symbols.applicable_accessibilities = * -dotnet_naming_symbols.parameters_symbols.applicable_kinds = parameter -dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private -dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field -dotnet_naming_symbols.private_constants_symbols.required_modifiers = const -dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private -dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field -dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly -dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none -dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion -dotnet_style_predefined_type_for_member_access = true:suggestion -dotnet_style_qualification_for_event = false:suggestion -dotnet_style_qualification_for_field = false:suggestion -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_property = false:suggestion -dotnet_style_require_accessibility_modifiers = never:error -resharper_align_linq_query = true -resharper_align_multiline_binary_patterns = true -resharper_align_multiline_calls_chain = true -resharper_align_multiline_expression = true -resharper_align_multiline_extends_list = true -resharper_align_multiline_parameter = true -resharper_align_multiple_declaration = true -resharper_align_multline_type_parameter_constrains = true -resharper_align_multline_type_parameter_list = true -resharper_align_tuple_components = true -resharper_apply_auto_detected_rules = false -resharper_autodetect_indent_settings = true -resharper_blank_lines_after_block_statements = 0 -resharper_blank_lines_after_control_transfer_statements = 1 -resharper_blank_lines_before_block_statements = 1 -resharper_blank_lines_before_control_transfer_statements = 1 -resharper_blank_lines_before_single_line_comment = 1 -resharper_braces_for_dowhile = required_for_multiline -resharper_braces_for_fixed = required_for_multiline -resharper_braces_for_for = required_for_multiline -resharper_braces_for_foreach = required_for_multiline -resharper_braces_for_ifelse = required_for_multiline -resharper_braces_for_lock = required_for_multiline -resharper_braces_for_using = required_for_multiline -resharper_braces_for_while = required_for_multiline -resharper_csharp_empty_block_style = together_same_line -resharper_csharp_int_align_comments = true -resharper_csharp_keep_blank_lines_in_code = 1 -resharper_csharp_keep_blank_lines_in_declarations = 1 -resharper_csharp_stick_comment = false -resharper_csharp_wrap_after_invocation_lpar = true -resharper_csharp_wrap_arguments_style = chop_if_long -resharper_csharp_wrap_before_first_type_parameter_constraint = true -resharper_csharp_wrap_multiple_declaration_style = chop_always -resharper_csharp_wrap_parameters_style = chop_if_long -resharper_enforce_line_ending_style = true -resharper_force_attribute_style = join -resharper_formatter_off_tag = @formatter:off -resharper_formatter_on_tag = @formatter:on -resharper_formatter_tags_enabled = true -resharper_indent_nested_fixed_stmt = true -resharper_indent_nested_foreach_stmt = true -resharper_indent_nested_for_stmt = true -resharper_indent_nested_lock_stmt = true -resharper_indent_nested_usings_stmt = true -resharper_indent_nested_while_stmt = true -resharper_indent_preprocessor_if = outdent -resharper_indent_preprocessor_other = outdent -resharper_indent_preprocessor_region = outdent -resharper_keep_existing_declaration_parens_arrangement = false -resharper_keep_existing_embedded_arrangement = false -resharper_keep_existing_embedded_block_arrangement = true -resharper_keep_existing_expr_member_arrangement = false -resharper_keep_existing_invocation_parens_arrangement = false -resharper_keep_existing_property_patterns_arrangement = false -resharper_keep_existing_switch_expression_arrangement = false -resharper_local_function_body = expression_body -resharper_max_array_initializer_elements_on_line = 10 -resharper_max_enum_members_on_line = 1 -resharper_max_formal_parameters_on_line = 10 -resharper_max_invocation_arguments_on_line = 10 -resharper_method_or_operator_body = expression_body -resharper_object_creation_when_type_not_evident = target_typed -resharper_place_accessorholder_attribute_on_same_line = false -resharper_place_accessor_with_attrs_holder_on_single_line = true -resharper_place_expr_method_on_single_line = false -resharper_place_linq_into_on_new_line = false -resharper_place_simple_embedded_statement_on_same_line = false -resharper_place_simple_method_on_single_line = false -resharper_show_autodetect_configure_formatting_tip = false -resharper_space_within_slice_pattern = false -resharper_use_continuous_indent_inside_initializer_braces = false -resharper_use_indent_from_vs = false -resharper_wrap_after_invocation_lpar = false -resharper_wrap_after_primary_constructor_declaration_lpar = false -resharper_wrap_array_initializer_style = chop_if_long -resharper_wrap_before_arrow_with_expressions = true -resharper_wrap_before_binary_pattern_op = false -resharper_wrap_chained_binary_expressions = chop_if_long -resharper_wrap_chained_binary_patterns = chop_if_long -resharper_xmldoc_blank_line_after_pi = false -resharper_xmldoc_indent_text = ZeroIndent -resharper_arrange_attributes_highlighting = suggestion -resharper_arrange_constructor_or_destructor_body_highlighting = suggestion -resharper_arrange_default_value_when_type_not_evident_highlighting = suggestion -resharper_arrange_local_function_body_highlighting = suggestion -resharper_arrange_method_or_operator_body_highlighting = hint -resharper_arrange_null_checking_pattern_highlighting = suggestion -resharper_arrange_object_creation_when_type_not_evident_highlighting = suggestion -resharper_arrange_redundant_parentheses_highlighting = hint -resharper_arrange_this_qualifier_highlighting = hint -resharper_auto_property_can_be_made_get_only_global_highlighting = none -resharper_built_in_type_reference_style_for_member_access_highlighting = hint -resharper_built_in_type_reference_style_highlighting = hint -resharper_check_namespace_highlighting = none -resharper_class_never_instantiated_global_highlighting = none -resharper_class_with_virtual_members_never_inherited_global_highlighting = none -resharper_collection_never_updated_global_highlighting = none -resharper_co_variant_array_conversion_highlighting = hint -resharper_empty_for_statement_highlighting = none -resharper_for_can_be_converted_to_foreach_highlighting = none -resharper_loop_can_be_converted_to_query_highlighting = none -resharper_member_can_be_private_global_highlighting = none -resharper_parameter_hides_member_highlighting = none -resharper_possible_multiple_enumeration_highlighting = suggestion -resharper_redundant_base_qualifier_highlighting = warning -resharper_return_value_of_pure_method_is_not_used_highlighting = none -resharper_separate_local_functions_with_jump_statement_highlighting = none -resharper_static_member_in_generic_type_highlighting = none -resharper_suggest_base_type_for_parameter_in_constructor_highlighting = suggestion -resharper_suggest_var_or_type_built_in_types_highlighting = hint -resharper_suggest_var_or_type_elsewhere_highlighting = hint -resharper_suggest_var_or_type_simple_types_highlighting = hint -resharper_switch_expression_handles_some_known_enum_values_with_exception_in_default_highlighting = suggestion -resharper_switch_statement_handles_some_known_enum_values_with_default_highlighting = suggestion -resharper_switch_statement_missing_some_enum_cases_no_default_highlighting = suggestion -resharper_unused_auto_property_accessor_global_highlighting = none -resharper_unused_auto_property_accessor_local_highlighting = none -resharper_unused_member_global_highlighting = none -resharper_unused_method_return_value_global_highlighting = none -resharper_web_config_module_not_resolved_highlighting = warning -resharper_web_config_type_not_resolved_highlighting = warning -resharper_web_config_wrong_module_highlighting = warning \ No newline at end of file diff --git a/sample/FastEndpoints/FastEndpoints.sln b/sample/FastEndpoints/FastEndpoints.sln deleted file mode 100644 index 155fafb..0000000 --- a/sample/FastEndpoints/FastEndpoints.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.34009.444 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastEndpoints", "Source\FastEndpoints.csproj", "{1B47FD0B-2CA9-4851-B71A-871393926B53}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{41828E0E-ACFA-4F22-A01E-D519694A9CC5}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1B47FD0B-2CA9-4851-B71A-871393926B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B47FD0B-2CA9-4851-B71A-871393926B53}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B47FD0B-2CA9-4851-B71A-871393926B53}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B47FD0B-2CA9-4851-B71A-871393926B53}.Release|Any CPU.Build.0 = Release|Any CPU - {41828E0E-ACFA-4F22-A01E-D519694A9CC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41828E0E-ACFA-4F22-A01E-D519694A9CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41828E0E-ACFA-4F22-A01E-D519694A9CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41828E0E-ACFA-4F22-A01E-D519694A9CC5}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {CFD734D0-8B11-4787-ACD0-4942B53BDD70} - EndGlobalSection -EndGlobal