Generating Swagger example requests with Swashbuckle

This is a follow on from my post from last year about Generating example Swagger responses.

It can also be useful to generate example requests, and in this post I will show you how.

Create a new SwaggerRequestExamplesAttribute

[AttributeUsage(AttributeTargets.Method)]
public sealed class SwaggerRequestExamplesAttribute : Attribute
{
    public SwaggerRequestExamplesAttribute(Type responseType, Type examplesProviderType)
    {
        ResponseType = responseType;
        ExamplesProviderType = examplesProviderType;
    }

    public Type ExamplesProviderType { get; private set; }

    public Type ResponseType { get; private set; }
}

Decorate your controller methods with it:

[Route(RouteTemplates.DeliveryOptionsSearchByAddress)]
[SwaggerRequestExamples(typeof(DeliveryOptionsSearchModel), typeof(DeliveryOptionsSearchModelExample))]
[SwaggerResponse(HttpStatusCode.OK, Type = typeof(DeliveryOptionsModel), Description = "Delivery options for the country found and returned successfully")]
[SwaggerResponseExamples(typeof(DeliveryOptionsModel), typeof(DeliveryOptionsModelExample))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(ErrorsModel), Description = "An invalid or missing input parameter will result in a bad request")]
[SwaggerResponse(HttpStatusCode.InternalServerError, Type = typeof(ErrorsModel), Description = "An unexpected error occurred, should not return sensitive information")]
public async Task<IHttpActionResult> DeliveryOptionsForAddress(DeliveryOptionsSearchModel search)
{

Now implement it, in this case via a DeliveryOptionsSearchModelExample, which will generate the example data. It should return the type you specified when you called SwaggerRequestExamples.

public class DeliveryOptionsSearchModelExample : IExamplesProvider
{
    public object GetExamples()
    {
        return new DeliveryOptionsSearchModel
        {
            Lang = "en-GB",
            Currency = "GBP",
            Address = new AddressModel
            {
                Address1 = "1 Gwalior Road",
                Locality = "London",
                Country = "GB",
                PostalCode = "SW15 1NP"
            },
            Items = new[]
            {
                new ItemModel
                {
                    ItemId = "ABCD",
                    ItemType = ItemType.Product,
                    Price = 20,
                    Quantity = 1,
                    RestrictedCountries = new[] { "US" }
                }
            }
        };
    }

Finally, you’ll need to change the ExamplesOperationFilter we implemented in my previous post:

public class ExamplesOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
    SetRequestModelExamples(operation, schemaRegistry, apiDescription);
    SetResponseModelExamples(operation, schemaRegistry, apiDescription);
}

private static void SetRequestModelExamples(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
    var requestAttributes = apiDescription.GetControllerAndActionAttributes<SwaggerRequestExamplesAttribute>();

    foreach (var attr in requestAttributes)
    {
        var schema = schemaRegistry.GetOrRegister(attr.ResponseType);

        var request = operation.parameters.FirstOrDefault(p => p.@in == "body" && p.schema.@ref == schema.@ref);

        if (request != null)
        {
            var provider = (IExamplesProvider)Activator.CreateInstance(attr.ExamplesProviderType);

            var parts = schema.@ref.Split('/');
            var name = parts.Last();

            var definitionToUpdate = schemaRegistry.Definitions[name];

            if (definitionToUpdate != null)
            {
                definitionToUpdate.example = ((dynamic)FormatAsJson(provider))["application/json"];
            }
        }
    }
}

private static void SetResponseModelExamples(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
    var responseAttributes = apiDescription.GetControllerAndActionAttributes<SwaggerResponseExamplesAttribute>();

    foreach (var attr in responseAttributes)
    {
        var schema = schemaRegistry.GetOrRegister(attr.ResponseType);

        var response =
            operation.responses.FirstOrDefault(
                x => x.Value != null && x.Value.schema != null && x.Value.schema.@ref == schema.@ref);

        if (response.Equals(default(KeyValuePair<string, Response>)) == false)
        {
            if (response.Value != null)
            {
                var provider = (IExamplesProvider)Activator.CreateInstance(attr.ExamplesProviderType);
                response.Value.examples = FormatAsJson(provider);
            }
        }
    }
}

private static object ConvertToCamelCase(Dictionary<string, object> examples)
{
    var jsonString = JsonConvert.SerializeObject(examples, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
    return JsonConvert.DeserializeObject(jsonString);
}

private static object FormatAsJson(IExamplesProvider provider)
{
    var examples = new Dictionary<string, object>
    {
        {
            "application/json", provider.GetExamples()
        }
    };

    return ConvertToCamelCase(examples);
}
}

Don’t forget to configure the ExamplesOperationFilter when you enable Swagger, as before:

configuration
    .EnableSwagger(c =>
    {
        c.OperationFilter<ExamplesOperationFilter>();
    })
    .EnableSwaggerUi();

Now that we’ve done all that, we should see the examples output in our swagger.json file, which you can get to by starting your solution and navigating to /swagger/docs/v1.

Capture

And the best part is, when you’re using swagger-ui, now when you click the example request in order to populate the form, instead of getting an autogenerated request like this:

Untitled

You’ll get your desired example, like this:

Capture2

 

Advertisements

25 thoughts on “Generating Swagger example requests with Swashbuckle

  1. line 38 of SetResponseModelExamples
    should be
    var responseAttributes = apiDescription.GetControllerAndActionAttributes();

    of you could rename SwaggerRequestExamplesAttribute class to SwaggerResponseExamplesAttribute, which is what I did when implementing your sample

    thanks again.

  2. Great article! And of course, a question:

    I have an API with a polymorphic service endpoint: /api/v1/vehicles. The endpoint accepts a parameter of type Vehicle, which is an abstract class with shared vehicle properties and a VehicleType enumeration property (enumeration values of Car, Truck, ..). Internally, the API uses a JsonConverter class to resolve the concrete Vehicle based on the VehicleType property that is passed in.

    The Swashbuckle documentation shows the Vehicle schema as my input parameter, but I would like to replace it with examples of the various concrete Vehicles that can be passed in. Using the pattern you describe in this post produces a run-time javascript/swagger exception. I assume it’s because the abstract Vehicle and concrete classes have different schemas. Any idea on whether it is possible to do what I want to do, or if there is a better solution for my problem?

    Thanks in advance.

    • I wouldn’t expect that to be an issue – I’m not the author of SwaggerResponse but I would think you should be able to specify the typeof one of your concrete classes as the Type parameter and it would be happy. Did you try that?

      Maybe ask your question in the Swashbuckle github page.

      • Matt, thanks for your response. Using typeof of my concrete class in the attribute declaration does not work because my concrete class does not implement IExampleProvider. The issue is in the schema replacement. As far as support from swashbuckle, the package doesn’t offer official support for documenting polymorphic endpoints (https://github.com/domaindrivendev/Swashbuckle/issues/313), so I may have to change my implementation.

  3. Thanks, Are you able to update example to latest version (6.0.0-beta902) it looks like IOperationFilter has changed since this article was posted.

    Thanks again.

  4. Thanks, Are you able to update example to latest version (6.0.0-beta902) it looks like IOperationFilter has changed since this article was posted.

    Thanks again

      • Hi Matt,

        Could you please help me out in implementing default request data to the controller action of the model schema on the Swagger UI with Swagger 6.0.0-rc1-final with C#, same like your above implementation. As i’m using Swagger 6.0.0-rc1-final the functions which are used at above link are not found in this swagger which i’m using.

      • Sorry Kumar, but I haven’t looked at Swashbuckle 6 – my work project is on Swashbuckle 5 and it works, so I’m not likely to upgrade it if there’s breaking changes. If you need support you could try posting an issue on the Swashbuckle github. Alas I don’t have any spare time to play around with this outside of work.

  5. Hi Matt,

    Thank you for the example, very useful.

    For me to get it working I used Newtonsoft.Json (otherwise the Swagger UI show nothing)

    Newtonsoft.Json.JsonConvert.SerializeObject(provider.GetExamples(), Formatting.Indented);
    // ((dynamic)FormatAsJson(provider))[“application/json”];

    and also had to comment out some parts of the following

    var response =
    operation.responses.FirstOrDefault(
    x => x.Value != null); // && x.Value.schema.@ref == schema.@ref && x.Value.schema != null); //

    Any idea what I might be missing?

    Thanks,
    Gavin

    Full code below.

    private static void SetRequestModelExamples(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
    var requestAttributes = apiDescription.GetControllerAndActionAttributes();

    foreach (var attr in requestAttributes)
    {
    var schema = schemaRegistry.GetOrRegister(attr.ResponseType);

    var request = operation.parameters.FirstOrDefault(p => p.@in == “body” && p.schema.@ref == schema.@ref);

    if (request != null)
    {
    var provider = (IProvideExamples)Activator.CreateInstance(attr.ExamplesType);

    var parts = schema.@ref.Split(‘/’);
    var name = parts.Last();

    var definitionToUpdate = schemaRegistry.Definitions[name];

    if (definitionToUpdate != null)
    {
    definitionToUpdate.example = Newtonsoft.Json.JsonConvert.SerializeObject(provider.GetExamples(), Formatting.Indented);
    // ((dynamic)FormatAsJson(provider))[“application/json”];

    }
    }
    }
    }
    private static void SetResponseModelExamples(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
    var responseAttributes = apiDescription.GetControllerAndActionAttributes();

    foreach (var attr in responseAttributes)
    {
    var schema = schemaRegistry.GetOrRegister(attr.ResponseType);

    var response =
    operation.responses.FirstOrDefault(
    x => x.Value != null); // && x.Value.schema.@ref == schema.@ref && x.Value.schema != null); //

    if (response.Equals(default(KeyValuePair)) == false)
    {
    if (response.Value != null)
    {
    var provider = (IProvideExamples)Activator.CreateInstance(attr.ExamplesType);
    response.Value.examples = FormatAsJson(provider);
    }
    }
    }
    }

    • I think if you had to comment out that line to get it to work, then you haven’t defined the [SwaggerRequestExamples] attribute above your controller’s method, like I do above:

      [SwaggerRequestExamples(typeof(DeliveryOptionsSearchModel), typeof(DeliveryOptionsSearchModelExample))]

  6. Excelent post! Very useful.
    Just one question about the displayed screen capture. How do you get the Authorization (access token) input field?
    Is it some customization?

    Thanks!

  7. Is it possible to manage multiple default values when the endpoint request model has more than one parameter?

    i.e.:
    public async Task GetDocument (RequestBase request, int Id)

    Can I post default values for request object, but also for Id ?

    How can I handle this…?

    Thanks

    • C# allows you to pass default parameters at compile time so you should be able to do:
      public async Task GetDocument (RequestBase request, int id = 3)

      To set defaults on your RequestBase object, you could do it a number of ways. The easiest place is on the RequestBase’s constructor.

      Another way would be to have a static “default” RequestBase object and set it to that in your controller action method. e.g.
      public async Task GetDocument (RequestBase request = null, int id = 3)
      {
      request = request ?? DefaultRequestBase;
      }

      Another way would be to take advantage of ASP.NET’s model binding and explicitly pass through the RequestBase’s properties to your controller action and default them there e.g. assuming your RequestBase looks something like this:
      public class RequestBase
      {
      public int Blah { get; set; }
      }

      public async Task GetDocument (int blah = 7, int id = 3)

      My favourite approach is another way – have a specific GetDocument request object which inherits from RequestBase and use that in your action method. Then have the default values in the GetDocumentRequest constructor.

      public async Task GetDocument (GetDocumentRequest documentRequest)

      • Thanks! I think I didn’t explain myself correctly.

        What I need to know if it’s possible is how can I define a the decorator SwaggerRequestExamples when I have more than one parameter in my endpoint.

        i.e.:
        [SwaggerRequestExamples(typeof(RequestBase), typeof(RequestBaseExamples))]
        public async Task GetDocument (RequestBase request, int Id)

        In this case, I can implement the GetExamples method for RequestBase class, but I can’t define the example value for Id parameter.

        Did I explain my self this time?

        Thanks again!

      • Oh, OK. You should be able to define the attribute multiple times:

        [SwaggerRequestExamples(typeof(RequestBase), typeof(RequestBaseExamples))]
        [SwaggerRequestExamples(typeof(int), typeof(IntExample))]
        public async Task GetDocument (RequestBase request, int Id)

        But I think it would be better to create a specific GetDocumentRequest object which inherits your RequestBase.
        [SwaggerRequestExamples(typeof(GetDocumentRequest), typeof(GetDocumentRequestExample))]
        public async Task GetDocument (GetDocumentRequest request)

      • Thanks mattfrear!
        I tried with the first approach. But it doesn’t work. It says:
        Duplicate ‘SwaggerRequestExamples’ attribute. I think it’s not possible.

        The thing with your 2nd approach, is that my endpoint has some body parameters (RequestBase) and some uri parameters (Id). That’s why I don’t have all the endpoint parameters encapsulated in a single class inheriting RequestBase.

        Do you think is it possible to decorate my endpoint in such a way it would be possible to define default values for my request parameters?

        Thanks again!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s