1

Swagger exposes by default any schema that is used by an exposed controller (API end point). How can a schema (class) be exposed if it is not used by a controller?

For example, Swagger is showing the following Schemas:

Swagger Schemas

But, the Song Schema (below) needs to be exposed. It is not exposed because it is not used by a controller (API end point).

using System;
namespace ExampleNamespace
{
    public class Song
    {
        [Key][Required]
        public int SongID { get; set; }
        [Required]
        public string SongName { get; set; }
        public string SongDescription { get; set; }
        public int SongLength { get; set; } //seconds
        [Required]
        public int AlbumID { get; set; }
    }
}

How can this be accomplished?

Helen
  • 87,344
  • 17
  • 243
  • 314
Cardi DeMonaco Jr
  • 536
  • 1
  • 7
  • 19

1 Answers1

0

You can add a schema using a DocumentFilter

public class AddSongSchemaDocumentFilter : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        var songSchema = new OpenApiSchema {...};
        songSchema.Properties.Add(new KeyValuePair<string, OpenApiSchema>("songName", new OpenApiSchema { ... }));
        ...

        context.SchemaRepository.Schemas.Add("Song", songSchema);
    }
}

The class OpenApiSchema is used for the song schema itself, and for property schemas. This type contains a number of documentation related properties you can set such as Description.

You register AddSongSchemaDocumentFilter like so

public void ConfigureServices(IServiceCollection services)
{
    services.AddSwaggerGen(options =>
    {
        options.DocumentFilter<AddSongSchemaDocumentFilter>();
    });
}

This could be a bit tedious if there are many properties. Using reflection, you can iterate on the properties, and even reflect on associated attributes attached to those properties.

var songSchema = new OpenApiSchema() { };
var song = new Song();
var properties = typeof(Song).GetProperties();

foreach (var p in properties)
    songSchema.Properties.Add(new KeyValuePair<string, OpenApiSchema(
        p.Name,
        new OpenApiSchema()
        {
            Type = p.PropertyType.ToString(),
            Description = // get [Description] attribute from p,
            // ... etc. for other metadata such as an example if desired
        }));

context.SchemaRepository.Schemas.Add("Song", songSchema);

Full Swashbuckle documentation.

Kit
  • 20,354
  • 4
  • 60
  • 103
  • Great! I was able to add a Schema! Is there a way to add the Song class without specifying all of the properties individually (assuming that I want to expose all of the Properties on the Class)? – Cardi DeMonaco Jr May 27 '21 at 16:55
  • 1
    @CardiDeMonacoJrYou could use reflection to do this. Another way might be to document your `Song` class with standard .NET XML doc comments, and then direct Swashbuckle to load these types with `SwaggerGenOptions.IncludeXmlComments`. If those work, I can update the answer... – Kit May 27 '21 at 17:14
  • Adding standard .NET XML doc comments does not appear to load anything additional. I added, for example, the songName property on the song after adding the comments, and I do not see anything additional. Reflection would certainly be an interesting route. I may try that, as there will end up being hundreds of properties. If reflection works, I'll revisit this question and share my code. Thanks for the help! – Cardi DeMonaco Jr May 27 '21 at 17:22
  • Reflection is very slick :) `var songSchema = new OpenApiSchema() { };` `Song song = new Song();` `PropertyInfo[] properties = typeof(Song).GetProperties();` `foreach (var p in properties) songSchema.Properties.Add(new KeyValuePair(p.Name, new OpenApiSchema() { Type = p.PropertyType.ToString() }));` `context.SchemaRepository.Schemas.Add("Song", songSchema);` – Cardi DeMonaco Jr May 27 '21 at 18:12
  • 1
    @CardiDeMonacoJr sure thing! I wasn't sure if the XML route was additional. I think all "normal" schemas are determined based on the endpoint signatures and all types referenced from the types in these signatures. Would be nice if it was additive... perhaps a good feature request to make. – Kit May 27 '21 at 18:14
  • And apparently I have no clue how to properly format code in a comment... so anyone please feel free to edit my last comment if able... – Cardi DeMonaco Jr May 27 '21 at 18:16
  • 1
    @CardiDeMonacoJr I've included your comment inline. Note, you can make the schema definition richer by reflecting attributes that you add to `Song`'s properties such as [`Description("Name of the song")] string SongName { get; set; }` – Kit May 27 '21 at 18:22
  • And for GetProperties, for anyone else looking at this, use `GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)` to not also grab the inherited properties. Or refer to [this question](https://stackoverflow.com/questions/1544979/using-getproperties-with-bindingflags-declaredonly-in-net-reflection). – Cardi DeMonaco Jr May 27 '21 at 18:41