Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Represents the **NuGet** versions.

## v2.9.0
- *Enhancement:* Added `PagingAttribute` and `PagingOperationFilter` to enable swagger output of `PagingArgs` parameters for an operation.

## v2.8.0
- *Enhancement:* Added `CoreEx.EntityFrameworkCore` support for framework `net7.0`.
- *Enhancement:* Updated `ServiceBusSubscriberInvoker` to improve logging, including opportunities to inherit and add further before and after processing logging and/or monitoring.
Expand Down
2 changes: 1 addition & 1 deletion Common.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.8.0</Version>
<Version>2.9.0</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
1 change: 1 addition & 0 deletions samples/My.Hr/My.Hr.Api/Controllers/EmployeeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public Task<IActionResult> GetAsync(Guid id)
/// <returns>All <see cref="Employee"/>.</returns>
[HttpGet("", Name = "GetAll")]
[ProducesResponseType(typeof(IEnumerable<Employee>), (int)HttpStatusCode.OK)]
[Paging]
public Task<IActionResult> GetAllAsync()
=> _webApi.GetAsync(Request, p => _service.GetAllAsync(p.RequestOptions.Paging));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ public Task<IActionResult> GenderGetAll([FromQuery] IEnumerable<string>? codes =

[HttpGet()]
[ProducesResponseType(typeof(IEnumerable<CoreEx.RefData.ReferenceDataMultiItem>), (int)HttpStatusCode.OK)]
[ApiExplorerSettings(IgnoreApi = true)]
public Task<IActionResult> GetNamed() => _webApi.GetAsync(Request, p => _orchestrator.GetNamedAsync(p.RequestOptions));
}
1 change: 1 addition & 0 deletions samples/My.Hr/My.Hr.Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public void ConfigureServices(IServiceCollection services)
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
options.OperationFilter<AcceptsBodyOperationFilter>(); // Needed to support AcceptsBodyAttribue where body parameter not explicitly defined.
options.OperationFilter<PagingOperationFilter>(); // Needed to support PagingAttribue where PagingArgs parameter not explicitly defined.
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/CoreEx/WebApis/AcceptsBodyAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace CoreEx.WebApis
{
/// <summary>
/// A filter that specifies the expected request <b>body</b> <see cref="Type"/> that the action accepts and the supported request content types.
/// An attribute that specifies the expected request <b>body</b> <see cref="Type"/> that the action/operation accepts and the supported request content types.
/// </summary>
/// <remarks>The is used to enable <i>Swagger/Swashbuckle</i> generated documentation where the operation does not explicitly define the body as a method parameter; i.e. via <see cref="Microsoft.AspNetCore.Mvc.FromBodyAttribute"/>.</remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
Expand Down
2 changes: 1 addition & 1 deletion src/CoreEx/WebApis/AcceptsBodyOperationFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace CoreEx.WebApis
/// <summary>
/// A <i>Swagger/Swashbuckle</i> <see cref="IOperationFilter"/> to infer the <see cref="OpenApiRequestBody"/> from the specification of the <see cref="AcceptsBodyAttribute"/>.
/// </summary>
/// <remarks>This must be added when registering services (DI) during application startup; example as follows:
/// <remarks>The <see cref="AcceptsBodyOperationFilter"/> must be added when registering services (DI) during application startup; example as follows:
/// <code>
/// services.AddSwaggerGen(c =&gt; c.OperationFilter&lt;AcceptsBodyOperationFilter&gt;());
/// </code></remarks>
Expand Down
14 changes: 14 additions & 0 deletions src/CoreEx/WebApis/PagingAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx

using CoreEx.Entities;
using System;

namespace CoreEx.WebApis
{
/// <summary>
/// An attribute that specifies that the action/operation supports <see cref="PagingArgs"/> (not explicitly defined as a parameter).
/// </summary>
/// <remarks>The is used to enable <i>Swagger/Swashbuckle</i> generated documentation where the operation does not explicitly define the <see cref="PagingArgs"/> as a method parameter.</remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class PagingAttribute : Attribute { }
}
51 changes: 51 additions & 0 deletions src/CoreEx/WebApis/PagingOperationFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx

using CoreEx.Entities;
using CoreEx.Http;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Linq;

namespace CoreEx.WebApis
{
/// <summary>
/// A <i>Swagger/Swashbuckle</i> <see cref="IOperationFilter"/> to add the <see cref="PagingArgs"/> paramaters from the specification of the <see cref="PagingAttribute"/>.
/// </summary>
/// <remarks><para>The <see cref="PagingArgs"/> parameter names are sourced from <see cref="HttpConsts.PagingArgsSkipQueryStringName"/>, <see cref="HttpConsts.PagingArgsTakeQueryStringName"/> and <see cref="HttpConsts.PagingArgsCountQueryStringName"/>;
/// and as such, can be overridden.</para>
/// The <see cref="PagingOperationFilter"/> must be added when registering services (DI) during application startup; example as follows:
/// <code>
/// services.AddSwaggerGen(c =&gt; c.OperationFilter&lt;PagingOperationFilter&gt;());
/// </code>
/// </remarks>
public class PagingOperationFilter : IOperationFilter
{
/// <summary>
/// Applies the filter.
/// </summary>
/// <param name="operation">The <see cref="OpenApiOperation"/>.</param>
/// <param name="context">The <see cref="OperationFilterContext"/>.</param>
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var att = context.ApiDescription.CustomAttributes().OfType<PagingAttribute>().FirstOrDefault();
if (att == null)
return;

operation.Parameters.Add(CreateParameter(HttpConsts.PagingArgsSkipQueryStringName, "The specified number of elements in a sequence to bypass.", "number"));
operation.Parameters.Add(CreateParameter(HttpConsts.PagingArgsTakeQueryStringName, "The specified number of contiguous elements from the start of a sequence.", "number"));
operation.Parameters.Add(CreateParameter(HttpConsts.PagingArgsCountQueryStringName, "Indicates whether to get the total count when performing the underlying query.", "boolean"));
}

/// <summary>
/// Create the parameter definition.
/// </summary>
private static OpenApiParameter CreateParameter(string name, string description, string typeName) => new()
{
Name = name,
Description = description,
In = ParameterLocation.Query,
Required = false,
Schema = new OpenApiSchema { Type = typeName }
};
}
}