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..971c679 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,15 +17,19 @@ 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: |
+ 8.x
+ 9.x
- name: Install ReportGenerator
run: dotnet tool install dotnet-reportgenerator-globaltool --tool-path tools
- 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:
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/Panner.AspNetCore.sln b/Panner.AspNetCore.sln
index fffad7a..ca9ab8e 100644
--- a/Panner.AspNetCore.sln
+++ b/Panner.AspNetCore.sln
@@ -9,9 +9,15 @@ 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}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNet9MinimalFluent", "samples\WebApiNet9MinimalFluent\WebApiNet9MinimalFluent.csproj", "{84F94171-2814-4993-9BC6-43D7F9535027}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet5_0", "samples\AspNet5_0\AspNet5_0.csproj", "{00CBFE9C-C93E-4DC9-BDE5-87A10E170CF5}"
+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
@@ -27,21 +33,36 @@ 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
+ {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
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}
+ {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/samples/AspNet5_0/EFModel/BlogContext.cs b/sample/FastEndpoints/Source/EFModel/BlogContext.cs
similarity index 95%
rename from samples/AspNet5_0/EFModel/BlogContext.cs
rename to sample/FastEndpoints/Source/EFModel/BlogContext.cs
index cecad33..1015a9a 100644
--- a/samples/AspNet5_0/EFModel/BlogContext.cs
+++ b/sample/FastEndpoints/Source/EFModel/BlogContext.cs
@@ -1,4 +1,4 @@
-namespace Panner.AspNetCore.Samples.AspNet5_0.EFModel
+namespace Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel
{
using Microsoft.EntityFrameworkCore;
using System;
diff --git a/samples/AspNetCore3_1/EFModel/Post.cs b/sample/FastEndpoints/Source/EFModel/Post.cs
similarity index 84%
rename from samples/AspNetCore3_1/EFModel/Post.cs
rename to sample/FastEndpoints/Source/EFModel/Post.cs
index 123523b..10f85ab 100644
--- a/samples/AspNetCore3_1/EFModel/Post.cs
+++ b/sample/FastEndpoints/Source/EFModel/Post.cs
@@ -1,4 +1,4 @@
-namespace Panner.AspNetCore.Samples.AspNetCore3_1.EFModel
+namespace Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel
{
using System;
diff --git a/sample/FastEndpoints/Source/FastEndpointsNet9.csproj b/sample/FastEndpoints/Source/FastEndpointsNet9.csproj
new file mode 100644
index 0000000..f940e73
--- /dev/null
+++ b/sample/FastEndpoints/Source/FastEndpointsNet9.csproj
@@ -0,0 +1,27 @@
+
+
+
+ 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/Posts/Endpoint.cs b/sample/FastEndpoints/Source/Features/Posts/Endpoint.cs
new file mode 100644
index 0000000..934eaf5
--- /dev/null
+++ b/sample/FastEndpoints/Source/Features/Posts/Endpoint.cs
@@ -0,0 +1,41 @@
+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;
+
+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..5afaf57
--- /dev/null
+++ b/sample/FastEndpoints/Source/Features/Posts/Request.cs
@@ -0,0 +1,16 @@
+using Microsoft.AspNetCore.Mvc;
+
+using Panner;
+using Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel;
+using FastEndpoints;
+
+namespace Posts;
+
+sealed class Request
+{
+ [FastEndpoints.FromQuery]
+ public IReadOnlyCollection>? Sorts { get; set; }
+
+ [FastEndpoints.FromQuery]
+ public IReadOnlyCollection>? Filters { get; set; }
+}
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/samples/AspNet5_0/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs b/sample/FastEndpoints/Source/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
similarity index 75%
rename from samples/AspNet5_0/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
rename to sample/FastEndpoints/Source/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
index f122f53..9db989b 100644
--- a/samples/AspNet5_0/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
+++ b/sample/FastEndpoints/Source/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
@@ -1,6 +1,6 @@
-namespace Panner.AspNetCore.Samples.AspNet5_0.PannerExtensions
+namespace Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions
{
- using global::Panner.AspNetCore.Samples.AspNet5_0.EFModel;
+ using global::Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel;
using global::Panner.Builders;
public static partial class PEntityBuilderExtensions
diff --git a/samples/AspNetCore3_1/PannerExtensions/SortPostByPopularityParticle.cs b/sample/FastEndpoints/Source/PannerExtensions/SortPostByPopularityParticle.cs
similarity index 82%
rename from samples/AspNetCore3_1/PannerExtensions/SortPostByPopularityParticle.cs
rename to sample/FastEndpoints/Source/PannerExtensions/SortPostByPopularityParticle.cs
index f95b229..43b78fc 100644
--- a/samples/AspNetCore3_1/PannerExtensions/SortPostByPopularityParticle.cs
+++ b/sample/FastEndpoints/Source/PannerExtensions/SortPostByPopularityParticle.cs
@@ -1,6 +1,6 @@
-namespace Panner.AspNetCore.Samples.AspNetCore3_1.PannerExtensions
+namespace Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions
{
- using global::Panner.AspNetCore.Samples.AspNetCore3_1.EFModel;
+ using global::Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel;
using System.Linq;
public class SortPostByPopularityParticle : ISortParticle
diff --git a/samples/AspNetCore3_1/PannerExtensions/SortPostsByPopularityParticleGenerator.cs b/sample/FastEndpoints/Source/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
similarity index 82%
rename from samples/AspNetCore3_1/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
rename to sample/FastEndpoints/Source/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
index 3ae0837..7430d17 100644
--- a/samples/AspNetCore3_1/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
+++ b/sample/FastEndpoints/Source/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
@@ -1,6 +1,6 @@
-using Panner.AspNetCore.Samples.AspNetCore3_1.EFModel;
+using Panner.AspNetCore.Samples.FastEndpointsNet9.EFModel;
-namespace Panner.AspNetCore.Samples.AspNetCore3_1.PannerExtensions
+namespace Panner.AspNetCore.Samples.FastEndpointsNet9.PannerExtensions
{
public class SortPostsByPopularityParticleGenerator : ISortParticleGenerator
{
diff --git a/sample/FastEndpoints/Source/Program.cs b/sample/FastEndpoints/Source/Program.cs
new file mode 100644
index 0000000..9b39955
--- /dev/null
+++ b/sample/FastEndpoints/Source/Program.cs
@@ -0,0 +1,44 @@
+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);
+
+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()
+ .UseFastEndpoints(c =>
+ {
+ c.Errors.UseProblemDetails();
+ })
+ .UseSwaggerGen();
+app.Run();
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/samples/AspNetCore3_1/Views/Post.cs b/sample/FastEndpoints/Source/Views/Post.cs
similarity index 78%
rename from samples/AspNetCore3_1/Views/Post.cs
rename to sample/FastEndpoints/Source/Views/Post.cs
index 254ad6e..b0ef8bb 100644
--- a/samples/AspNetCore3_1/Views/Post.cs
+++ b/sample/FastEndpoints/Source/Views/Post.cs
@@ -1,4 +1,4 @@
-namespace Panner.AspNetCore.Samples.AspNetCore3_1.Views
+namespace Panner.AspNetCore.Samples.FastEndpointsNet9.Views
{
using System;
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/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/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/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/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/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/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/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/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/EFModel/BlogContext.cs b/samples/WebAPINet8MinimalFluent/EFModel/BlogContext.cs
similarity index 94%
rename from samples/AspNetCore3_1/EFModel/BlogContext.cs
rename to samples/WebAPINet8MinimalFluent/EFModel/BlogContext.cs
index b92ad6f..c1d1b8e 100644
--- a/samples/AspNetCore3_1/EFModel/BlogContext.cs
+++ b/samples/WebAPINet8MinimalFluent/EFModel/BlogContext.cs
@@ -1,4 +1,4 @@
-namespace Panner.AspNetCore.Samples.AspNetCore3_1.EFModel
+namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel
{
using Microsoft.EntityFrameworkCore;
using System;
diff --git a/samples/AspNet5_0/EFModel/Post.cs b/samples/WebAPINet8MinimalFluent/EFModel/Post.cs
similarity index 83%
rename from samples/AspNet5_0/EFModel/Post.cs
rename to samples/WebAPINet8MinimalFluent/EFModel/Post.cs
index 4674b54..bdc63c0 100644
--- a/samples/AspNet5_0/EFModel/Post.cs
+++ b/samples/WebAPINet8MinimalFluent/EFModel/Post.cs
@@ -1,4 +1,4 @@
-namespace Panner.AspNetCore.Samples.AspNet5_0.EFModel
+namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel
{
using System;
diff --git a/samples/AspNetCore3_1/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs b/samples/WebAPINet8MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
similarity index 73%
rename from samples/AspNetCore3_1/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
rename to samples/WebAPINet8MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
index 7eaab68..c45b930 100644
--- a/samples/AspNetCore3_1/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
+++ b/samples/WebAPINet8MinimalFluent/PannerExtensions/PEntityBuilder.Post.IsSortableByPopularity.cs
@@ -1,6 +1,6 @@
-namespace Panner.AspNetCore.Samples.AspNetCore3_1.PannerExtensions
+namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.PannerExtensions
{
- using global::Panner.AspNetCore.Samples.AspNetCore3_1.EFModel;
+ using global::Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel;
using global::Panner.Builders;
public static partial class PEntityBuilderExtensions
diff --git a/samples/AspNet5_0/PannerExtensions/SortPostByPopularityParticle.cs b/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs
similarity index 81%
rename from samples/AspNet5_0/PannerExtensions/SortPostByPopularityParticle.cs
rename to samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs
index 9eda449..591e528 100644
--- a/samples/AspNet5_0/PannerExtensions/SortPostByPopularityParticle.cs
+++ b/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostByPopularityParticle.cs
@@ -1,6 +1,6 @@
-namespace Panner.AspNetCore.Samples.AspNet5_0.PannerExtensions
+namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.PannerExtensions
{
- using global::Panner.AspNetCore.Samples.AspNet5_0.EFModel;
+ using global::Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel;
using System.Linq;
public class SortPostByPopularityParticle : ISortParticle
diff --git a/samples/AspNet5_0/PannerExtensions/SortPostsByPopularityParticleGenerator.cs b/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
similarity index 81%
rename from samples/AspNet5_0/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
rename to samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
index 5a163a6..945a8b7 100644
--- a/samples/AspNet5_0/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
+++ b/samples/WebAPINet8MinimalFluent/PannerExtensions/SortPostsByPopularityParticleGenerator.cs
@@ -1,6 +1,6 @@
-using Panner.AspNetCore.Samples.AspNet5_0.EFModel;
+using Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.EFModel;
-namespace Panner.AspNetCore.Samples.AspNet5_0.PannerExtensions
+namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.PannerExtensions
{
public class SortPostsByPopularityParticleGenerator : ISortParticleGenerator
{
diff --git a/samples/WebAPINet8MinimalFluent/Program.cs b/samples/WebAPINet8MinimalFluent/Program.cs
new file mode 100644
index 0000000..0efff1a
--- /dev/null
+++ b/samples/WebAPINet8MinimalFluent/Program.cs
@@ -0,0 +1,67 @@
+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);
+
+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();
+
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+
+app.MapGet("/posts", async (
+ [FromServices] BlogContext blogContext,
+ SortParticlesMinimalBinder sorts,
+ FilterParticlesMinimalBinder filters) =>
+{
+ 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();
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/AspNet5_0/Views/Post.cs b/samples/WebAPINet8MinimalFluent/Views/Post.cs
similarity index 76%
rename from samples/AspNet5_0/Views/Post.cs
rename to samples/WebAPINet8MinimalFluent/Views/Post.cs
index 96cf581..b216412 100644
--- a/samples/AspNet5_0/Views/Post.cs
+++ b/samples/WebAPINet8MinimalFluent/Views/Post.cs
@@ -1,4 +1,4 @@
-namespace Panner.AspNetCore.Samples.AspNet5_0.Views
+namespace Panner.AspNetCore.Samples.WebApiNet8MinimalFluent.Views
{
using System;
diff --git a/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj b/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj
new file mode 100644
index 0000000..812e7ee
--- /dev/null
+++ b/samples/WebAPINet8MinimalFluent/WebAPINet8MinimalFluent.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Panner.AspNetCore.Samples.WebApiNet8MinimalFluent
+ Panner.AspNetCore.Samples.WebApiNet8MinimalFluent
+ 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/AspNetCore3_1/appsettings.json b/samples/WebAPINet8MinimalFluent/appsettings.json
similarity index 56%
rename from samples/AspNetCore3_1/appsettings.json
rename to samples/WebAPINet8MinimalFluent/appsettings.json
index d9d9a9b..10f68b8 100644
--- a/samples/AspNetCore3_1/appsettings.json
+++ b/samples/WebAPINet8MinimalFluent/appsettings.json
@@ -2,8 +2,7 @@
"Logging": {
"LogLevel": {
"Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
diff --git a/samples/AspNet5_0/Controllers/PostsController.cs b/samples/WebApi8ControllersFluent/Controllers/PostsController.cs
similarity index 91%
rename from samples/AspNet5_0/Controllers/PostsController.cs
rename to samples/WebApi8ControllersFluent/Controllers/PostsController.cs
index d0aa944..cf7c3dd 100644
--- a/samples/AspNet5_0/Controllers/PostsController.cs
+++ b/samples/WebApi8ControllersFluent/Controllers/PostsController.cs
@@ -1,9 +1,9 @@
-namespace Panner.AspNetCore.Samples.AspNet5_0.Controllers
+namespace Panner.AspNetCore.Samples.WebApi8ControllersFluent.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
- using Panner.AspNetCore.Samples.AspNet5_0.EFModel;
+ using Panner.AspNetCore.Samples.WebApi8ControllersFluent.EFModel;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
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
new file mode 100644
index 0000000..5b06885
--- /dev/null
+++ b/samples/WebApi8ControllersFluent/Program.cs
@@ -0,0 +1,49 @@
+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();
+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();
+
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+app.UseAuthorization();
+app.MapControllers();
+
+app.Run();
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/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
new file mode 100644
index 0000000..d4aeee2
--- /dev/null
+++ b/samples/WebApi8ControllersFluent/WebApi8ControllersFluent.csproj
@@ -0,0 +1,19 @@
+
+
+
+ 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/AspNet5_0/appsettings.json b/samples/WebApi8ControllersFluent/appsettings.json
similarity index 56%
rename from samples/AspNet5_0/appsettings.json
rename to samples/WebApi8ControllersFluent/appsettings.json
index d9d9a9b..10f68b8 100644
--- a/samples/AspNet5_0/appsettings.json
+++ b/samples/WebApi8ControllersFluent/appsettings.json
@@ -2,8 +2,7 @@
"Logging": {
"LogLevel": {
"Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
diff --git a/samples/AspNetCore3_1/Controllers/PostsController.cs b/samples/WebApiNet9ControllersFluent/Controllers/PostsController.cs
similarity index 90%
rename from samples/AspNetCore3_1/Controllers/PostsController.cs
rename to samples/WebApiNet9ControllersFluent/Controllers/PostsController.cs
index 46e9670..c5be6b3 100644
--- a/samples/AspNetCore3_1/Controllers/PostsController.cs
+++ b/samples/WebApiNet9ControllersFluent/Controllers/PostsController.cs
@@ -1,9 +1,9 @@
-namespace Panner.AspNetCore.Samples.AspNetCore3_1.Controllers
+namespace Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
- using Panner.AspNetCore.Samples.AspNetCore3_1.EFModel;
+ using Panner.AspNetCore.Samples.WebApiNet9ControllersFluent.EFModel;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
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
new file mode 100644
index 0000000..6eb0e70
--- /dev/null
+++ b/samples/WebApiNet9ControllersFluent/Program.cs
@@ -0,0 +1,47 @@
+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);
+
+builder.Services.AddControllers();
+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();
+
+if (app.Environment.IsDevelopment())
+{
+ app.MapOpenApi();
+}
+
+app.UseHttpsRedirection();
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
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/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/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..990755f
--- /dev/null
+++ b/samples/WebApiNet9ControllersFluent/WebApiNet9ControllersFluent.csproj
@@ -0,0 +1,22 @@
+
+
+
+ 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/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
new file mode 100644
index 0000000..9183ec1
--- /dev/null
+++ b/samples/WebApiNet9MinimalFluent/Program.cs
@@ -0,0 +1,66 @@
+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);
+
+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();
+
+if (app.Environment.IsDevelopment())
+{
+ app.MapOpenApi();
+}
+
+app.UseHttpsRedirection();
+
+app.MapGet("/posts", async (
+ [FromServices] BlogContext blogContext,
+ SortParticlesMinimalBinder sorts,
+ FilterParticlesMinimalBinder filters
+) =>
+{
+ 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();
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/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/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..f8462fa
--- /dev/null
+++ b/samples/WebApiNet9MinimalFluent/WebApiNet9MinimalFluent.csproj
@@ -0,0 +1,22 @@
+
+
+
+ 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": "*"
+}
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/**
diff --git a/src/Binders/FilterParticlesMinimalBinder.cs b/src/Binders/FilterParticlesMinimalBinder.cs
new file mode 100644
index 0000000..4233b76
--- /dev/null
+++ b/src/Binders/FilterParticlesMinimalBinder.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 FilterParticlesMinimalBinder : IReadOnlyCollection>
+ where TEntity : class
+ {
+ private readonly IReadOnlyCollection> _particles;
+
+ private FilterParticlesMinimalBinder(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 FilterParticlesMinimalBinder(Array.Empty>()));
+ }
+
+ if (!pContext.TryParseCsv(value.ToString(), out IEnumerable> particles))
+ {
+ throw new InvalidOperationException("Could not parse provided filters.");
+ }
+
+ return ValueTask.FromResult(new FilterParticlesMinimalBinder(particles.ToArray()));
+ }
+
+ public int Count => _particles.Count;
+ public IEnumerator> GetEnumerator() => _particles.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public IReadOnlyCollection> Value => _particles;
+ }
+}
diff --git a/src/Binders/SortParticlesMinimalBinder.cs b/src/Binders/SortParticlesMinimalBinder.cs
new file mode 100644
index 0000000..4b25e98
--- /dev/null
+++ b/src/Binders/SortParticlesMinimalBinder.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 SortParticlesMinimalBinder : IReadOnlyCollection>
+ where TEntity : class
+ {
+ private readonly IReadOnlyCollection> _particles;
+
+ private SortParticlesMinimalBinder(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 SortParticlesMinimalBinder(Array.Empty>()));
+ }
+
+ if (!pContext.TryParseCsv(value.ToString(), out IEnumerable> particles))
+ {
+ throw new InvalidOperationException("Could not parse provided sorts.");
+ }
+
+ return ValueTask.FromResult(new SortParticlesMinimalBinder(particles.ToArray()));
+ }
+
+ public int Count => _particles.Count;
+ public IEnumerator> GetEnumerator() => _particles.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public IReadOnlyCollection> Value => _particles;
+ }
+}
diff --git a/src/Panner.AspNetCore.csproj b/src/Panner.AspNetCore.csproj
index ddcc438..96eaa75 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);
});
}