I had same problem but slightly bigger. In my API methods I use ObjectId:
- as route parameters
public IActionResult Get(ObjectId id)
,
- inside classes as query parameters
public IActionResult Get([FromQuery] ClassWithObjectId filter)
,
- in POST bodies
public IActionResult Post([FromBody] ClassWithObjectId form)
- in response objects
Also I have <summary>
tags alongside with ObjectId properties and I want to show it in swagger description.
So it's hard to force Swashbuckle respect all this cases, but I think I made it!
We need two filters:
- ObjectIdOperationFilter to force Swashbuckle respect ObjectId in properties (route, query)
- ObjectIdSchemaFilter to force Swashbuckle respect ObjectId in bodies (both in requests and responses)
On swagger setup:
//piece of code to add all XML Documentation files. Ignore if you don't need them
var swaggerFiles = new string[] { "SwaggerAPI.xml", "SwaggerApplicationAPI.xml" }
.Select(fileName => Path.Combine(System.AppContext.BaseDirectory, fileName))
.Where(filePath => File.Exists(filePath));
foreach (var filePath in swaggerFiles)
options.IncludeXmlComments(filePath);
//we have to pass swaggerFiles here to add description to ObjectId props
//don't pass them if you won't
options.OperationFilter<ObjectIdOperationFilter>(swaggerFiles);
options.SchemaFilter<ObjectIdSchemaFilter>();
ObjectIdOperationFilter.cs:
(all methods to work with XML I took from Swashbuckle.AspNetCore repository)
public class ObjectIdOperationFilter : IOperationFilter
{
//prop names we want to ignore
private readonly IEnumerable<string> objectIdIgnoreParameters = new[]
{
"Timestamp",
"Machine",
"Pid",
"Increment",
"CreationTime"
};
private readonly IEnumerable<XPathNavigator> xmlNavigators;
public ObjectIdOperationFilter(IEnumerable<string> filePaths)
{
xmlNavigators = filePaths != null
? filePaths.Select(x => new XPathDocument(x).CreateNavigator())
: Array.Empty<XPathNavigator>();
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
//for very parameter in operation check if any fields we want to ignore
//delete them and add ObjectId parameter instead
foreach (var p in operation.Parameters.ToList())
if (objectIdIgnoreParameters.Any(x => p.Name.EndsWith(x)))
{
var parameterIndex = operation.Parameters.IndexOf(p);
operation.Parameters.Remove(p);
var dotIndex = p.Name.LastIndexOf(".");
if (dotIndex > -1)
{
var idName = p.Name.Substring(0, dotIndex);
if (!operation.Parameters.Any(x => x.Name == idName))
{
operation.Parameters.Insert(parameterIndex, new OpenApiParameter()
{
Name = idName,
Schema = new OpenApiSchema()
{
Type = "string",
Format = "24-digit hex string"
},
Description = GetFieldDescription(idName, context),
Example = new OpenApiString(ObjectId.Empty.ToString()),
In = p.In,
});
}
}
}
}
//get description from XML
private string GetFieldDescription(string idName, OperationFilterContext context)
{
var name = char.ToUpperInvariant(idName[0]) + idName.Substring(1);
var classProp = context.MethodInfo.GetParameters().FirstOrDefault()?.ParameterType?.GetProperties().FirstOrDefault(x => x.Name == name);
var typeAttr = classProp != null
? (DescriptionAttribute)classProp.GetCustomAttribute<DescriptionAttribute>()
: null;
if (typeAttr != null)
return typeAttr?.Description;
if (classProp != null)
foreach (var xmlNavigator in xmlNavigators)
{
var propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(classProp);
var propertySummaryNode = xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{propertyMemberName}']/summary");
if (propertySummaryNode != null)
return XmlCommentsTextHelper.Humanize(propertySummaryNode.InnerXml);
}
return null;
}
}
ObjectIdSchemaFilter.cs:
public class ObjectIdSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type == typeof(ObjectId))
{
schema.Type = "string";
schema.Format = "24-digit hex string";
schema.Example = new OpenApiString(ObjectId.Empty.ToString());
}
}
}
And it works!

Repository with all this filters here