I have a base controller with some predefined route template:
[Authorize]
[ApiController]
[Produces(MediaTypeNames.Application.Json)]
public abstract class TenantControllerBase : ControllerBase
{
public const string DefaultRoute = "api/s/{serviceId:int}/[controller]";
private readonly ITenantContext _tenantContext; // Extracts the value of serviceId in a middelware
protected int ServiceId => _tenantContext.ServiceId;
protected TenantControllerBase(ITenantContext tenantContext)
{
_tenantContext = tenantContext ?? throw new ArgumentNullException(nameof(tenantContext));
}
}
The idea is to be able to retrieve serviceId
using a middleware and make it available to all controllers inheriting from TenantControllerBase
without having to each time explicitly mention it in the action as input parameter:
[Route(DefaultRoute)]
public class SomeTenantController : TenantControllerBase
{
public SomeTenantController(ITenantContext tenantContext) : base(tenantContext) { }
[HttpGet("{type}")]
public async Task<IActionResult> Get(string type, CancellationToken cancellationToken)
{
var serviceId = this.ServiceId;
// ...
return Ok();
}
}
The problem with this is that the Swagger specification sets the type of serviceId
as string and not as integer as it is specified in the route template:
"/api/s/{serviceId}/SomeTenant/{type}": {
"get": {
"tags": [
"SomeTenant"
],
"parameters": [
{
"name": "type",
"in": "path",
"required": true,
"style": "simple",
"schema": {
"type": "string"
}
},
{
"name": "serviceId",
"in": "path",
"required": true,
"style": "simple",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success"
}
}
}
},
As far as I tried the only way to make Swagger understands that serviceId
is an integer is by explicitly adding it to the action method signature:
[HttpGet("{type}")]
public async Task<IActionResult> Get(
string type,
int serviceId,
CancellationToken cancellationToken)
{
// serviceId is the same as the base field this.ServiceId;
// ...
return Ok();
}
Is there any way to force swagger to set the value type according to the one specified in the route template api/s/{serviceId:int}/[controller]
without having to explicitly add it in the action method signature?
You can patch this up with a simple operation filter to update service id parameter type for all of your endpoints:
public class ServiceIdPathFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var serviceIdParameter = operation.Parameters
.FirstOrDefault(x => x.Name == "serviceId" && x.In == ParameterLocation.Path);
if (serviceIdParameter != null)
{
serviceIdParameter.Schema.Type = "integer";
}
}
}
Registration in startup:
services.AddSwaggerGen(opts => opts.OperationFilter<ServiceIdPathFilter>());
No longer valid for string values: