I have a swashbuckle swaggergen UI output that looks like: [![put request][1]][1]
And (for reasons), I don't want to use a typical validation attribute, instead I validate in the request body. My containername is an Azure Blob Storage container, so it must be 3-63 characters and match a simple regex (no uppercase letters, basically alpha-numeric).
I'd like to modify UI to also show those requirements... so, I wrote an OperationFilter and Attribute. I assumed that I wanted to modify SwaggerParameters, and there I noticed a handy schema with parameters like "MinLength", "MaxLength", and "Pattern" -- in other words, exactly what I want to show on my UI. So I modified that. Here is the output:
"put": {
"tags": [
"Values"
],
"summary": "API Operation – Create & Update\r\n::\r\nCreates a new content file entry in the containername provided.",
"description": "If the container name has the word public in it, then the container\r\nshall be public otherwise the container, identified by the\r\ncontainername, shall be private. If the file, identified by the\r\nfilename parameter on the URI, already exists then the existing blob\r\nentry will be overwritten with the new fileData uploaded.",
"operationId": "Put",
"parameters": [
{
"name": "containername",
"in": "path",
"description": "The container the file resides in.",
"required": true,
"schema": {
"maxLength": 63,
"minLength": 3,
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
"type": "string"
}
},
{
"name": "fileName",
"in": "path",
"description": "The name of the file uploaded. This shall become the block blob Id.",
"required": true,
"schema": {
"maxLength": 75,
"minLength": 1,
"pattern": "\\S",
"type": "string"
}
}
],
The problem is, the UI looks the same. What should I be modifying to get these values to render?
The code to do it:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using static Foo.SwaggerParameterDescriptions;
namespace Foo
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class SwaggerPathParameterDescriptions : Attribute
{
public enum Description
{
Default,
MinLength,
MaxLength,
Pattern
}
public string ParameterName { get; set; }
public Dictionary<Description, dynamic> Settings { get; set; }
public SwaggerPathParameterDescriptions(string parameterName, string json)
{
Dictionary<string, dynamic> dict = JsonSerializer
.Deserialize<Dictionary<string, dynamic>>(json);
Dictionary<Description, dynamic> settings = dict.Entries()
.ToDictionary(entry => (Description)Enum.Parse(typeof(Description), (string)entry.Key),
entry => entry.Value);
ParameterName = parameterName;
Settings = settings;
}
public IEnumerable<SwaggerParameterSchemaDescription> GetSwaggerParameters()
{
return Settings.Keys.Select(key =>
new SwaggerParameterSchemaDescription { ParameterName = key, Value = Settings[key] });
}
}
public class SwaggerParameterSchemaDescription
{
public Description ParameterName { get; set; }
public dynamic Value { get; set; }
public void ApplyTo(OpenApiParameter param)
{
string representation = $"{Value}";
switch (ParameterName)
{
case Description.Default:
param.Schema.Default = new OpenApiString(representation); // Path Parameters must be strings!
break;
case Description.MinLength:
param.Schema.MinLength = Int32.Parse(representation);
break;
case Description.MaxLength:
param.Schema.MaxLength = Int32.Parse(representation);
break;
case Description.Pattern:
param.Schema.Pattern = representation;
break;
default:
throw new InvalidOperationException();
}
}
}
public class AddSettings : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
foreach (var param in operation.Parameters)
{
var actionParam = context.ApiDescription.ActionDescriptor.Parameters.First(p => p.Name == param.Name);
if (actionParam != null)
{
context.MethodInfo
.GetCustomAttributes(true)
.OfType<SwaggerPathParameterDescriptions>()
.Where(p => p.ParameterName == param.Name)
.ToList()
.ForEach(customAttribute =>
{
foreach (SwaggerParameterSchemaDescription description in customAttribute.GetSwaggerParameters())
{
description.ApplyTo(param);
}
});
}
}
}
}
}
and in Startup:
services.AddSwaggerGen(c => {
c.OperationFilter<AddSettings>();
then use like:
[HttpPut("{containername}/contentfiles/{fileName}")]
[SwaggerPathParameterDescriptions("containername", "{\"MinLength\":3,\"MaxLength\":63,\"Pattern\":\"^[a-z0-9]+(-[a-z0-9]+)*$\"}")]
[SwaggerPathParameterDescriptions("fileName", "{\"MinLength\":1,\"MaxLength\":75,\"Pattern\":\"\\\\S\"}")]
[SwaggerResponseHeader(StatusCodes.Status201Created, "Location", "string", "Location of the newly created resource")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status503ServiceUnavailable)]
public ActionResult Put(string containername, string fileName, IFormFile fileData)
my issue is that it isn't rendering. :( I have more work to do? or am I modifying the wrong values?