diff --git a/CleanArchitecture.Api/CleanArchitecture.Api.csproj b/CleanArchitecture.Api/CleanArchitecture.Api.csproj
index 2a4b57b..345bdf5 100644
--- a/CleanArchitecture.Api/CleanArchitecture.Api.csproj
+++ b/CleanArchitecture.Api/CleanArchitecture.Api.csproj
@@ -7,29 +7,29 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/CleanArchitecture.Api/Controllers/TenantController.cs b/CleanArchitecture.Api/Controllers/TenantController.cs
index a5bf588..4a312eb 100644
--- a/CleanArchitecture.Api/Controllers/TenantController.cs
+++ b/CleanArchitecture.Api/Controllers/TenantController.cs
@@ -1,10 +1,13 @@
using System;
using System.Threading.Tasks;
using CleanArchitecture.Api.Models;
+using CleanArchitecture.Api.Swagger;
using CleanArchitecture.Application.Interfaces;
+using CleanArchitecture.Application.SortProviders;
using CleanArchitecture.Application.ViewModels;
using CleanArchitecture.Application.ViewModels.Sorting;
using CleanArchitecture.Application.ViewModels.Tenants;
+using CleanArchitecture.Domain.Entities;
using CleanArchitecture.Domain.Notifications;
using MediatR;
using Microsoft.AspNetCore.Authorization;
@@ -34,7 +37,7 @@ public sealed class TenantController : ApiController
[FromQuery] PageQuery query,
[FromQuery] string searchTerm = "",
[FromQuery] bool includeDeleted = false,
- [FromQuery] SortQuery? sortQuery = null)
+ [FromQuery, SortableFieldsAttribute] SortQuery? sortQuery = null)
{
var tenants = await _tenantService.GetAllTenantsAsync(
query,
diff --git a/CleanArchitecture.Api/Controllers/UserController.cs b/CleanArchitecture.Api/Controllers/UserController.cs
index 807a9b2..6e12fea 100644
--- a/CleanArchitecture.Api/Controllers/UserController.cs
+++ b/CleanArchitecture.Api/Controllers/UserController.cs
@@ -1,10 +1,13 @@
using System;
using System.Threading.Tasks;
using CleanArchitecture.Api.Models;
+using CleanArchitecture.Api.Swagger;
using CleanArchitecture.Application.Interfaces;
+using CleanArchitecture.Application.SortProviders;
using CleanArchitecture.Application.ViewModels;
using CleanArchitecture.Application.ViewModels.Sorting;
using CleanArchitecture.Application.ViewModels.Users;
+using CleanArchitecture.Domain.Entities;
using CleanArchitecture.Domain.Notifications;
using MediatR;
using Microsoft.AspNetCore.Authorization;
@@ -34,7 +37,7 @@ public sealed class UserController : ApiController
[FromQuery] PageQuery query,
[FromQuery] string searchTerm = "",
[FromQuery] bool includeDeleted = false,
- [FromQuery] SortQuery? sortQuery = null)
+ [FromQuery, SortableFieldsAttribute] SortQuery? sortQuery = null)
{
var users = await _userService.GetAllUsersAsync(
query,
diff --git a/CleanArchitecture.Api/Extensions/ServiceCollectionExtension.cs b/CleanArchitecture.Api/Extensions/ServiceCollectionExtension.cs
index 7224bcc..793d22f 100644
--- a/CleanArchitecture.Api/Extensions/ServiceCollectionExtension.cs
+++ b/CleanArchitecture.Api/Extensions/ServiceCollectionExtension.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Text;
+using CleanArchitecture.Api.Swagger;
using CleanArchitecture.Domain.Settings;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
@@ -34,6 +35,10 @@ public static class ServiceCollectionExtension
Scheme = "bearer"
});
+ c.ParameterFilter();
+
+ c.SupportNonNullableReferenceTypes();
+
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
diff --git a/CleanArchitecture.Api/Swagger/SortableFieldsAttribute.cs b/CleanArchitecture.Api/Swagger/SortableFieldsAttribute.cs
new file mode 100644
index 0000000..83cca2d
--- /dev/null
+++ b/CleanArchitecture.Api/Swagger/SortableFieldsAttribute.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using CleanArchitecture.Application.ViewModels.Sorting;
+
+namespace CleanArchitecture.Api.Swagger;
+
+[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
+public sealed class SortableFieldsAttribute
+ : SwaggerSortableFieldsAttribute
+ where TSortingProvider : ISortingExpressionProvider, new()
+{
+ public override IEnumerable GetFields()
+ {
+ return new TSortingProvider().GetSortingExpressions().Keys;
+ }
+}
diff --git a/CleanArchitecture.Api/Swagger/SortableFieldsAttributeFilter.cs b/CleanArchitecture.Api/Swagger/SortableFieldsAttributeFilter.cs
new file mode 100644
index 0000000..d628229
--- /dev/null
+++ b/CleanArchitecture.Api/Swagger/SortableFieldsAttributeFilter.cs
@@ -0,0 +1,32 @@
+using System.Linq;
+using System.Reflection;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace CleanArchitecture.Api.Swagger;
+
+public sealed class SortableFieldsAttributeFilter : IParameterFilter
+{
+ public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
+ {
+ if (context.ParameterInfo is null)
+ {
+ return;
+ }
+
+ var attribute = context.ParameterInfo
+ .GetCustomAttributes()
+ .SingleOrDefault();
+
+ if (attribute is null)
+ {
+ return;
+ }
+
+ var description = string.Join("
", attribute.GetFields().Order());
+
+ parameter.Description = $"{parameter.Description}
" +
+ $"**Allowed values:**
{description}";
+ }
+}
+
diff --git a/CleanArchitecture.Api/Swagger/SwaggerSortableFieldsAttribute.cs b/CleanArchitecture.Api/Swagger/SwaggerSortableFieldsAttribute.cs
new file mode 100644
index 0000000..1dff29d
--- /dev/null
+++ b/CleanArchitecture.Api/Swagger/SwaggerSortableFieldsAttribute.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Collections.Generic;
+
+namespace CleanArchitecture.Api.Swagger;
+
+public abstract class SwaggerSortableFieldsAttribute : Attribute
+{
+ public abstract IEnumerable GetFields();
+}
diff --git a/CleanArchitecture.Application/ViewModels/Sorting/SortQuery.cs b/CleanArchitecture.Application/ViewModels/Sorting/SortQuery.cs
index 3be687c..bbdc55b 100644
--- a/CleanArchitecture.Application/ViewModels/Sorting/SortQuery.cs
+++ b/CleanArchitecture.Application/ViewModels/Sorting/SortQuery.cs
@@ -10,6 +10,7 @@ public sealed class SortQuery
public ReadOnlyCollection Parameters { get; private set; } = new(Array.Empty());
+
[FromQuery(Name = "order_by")]
public string? Query
{