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

 

Generating Swagger example responses with Swashbuckle

Swashbuckle is a tool for generating Swagger, the API description language, from your ASP.NET Web Api solution.
Using Swashbuckle, which provides Swagger-UI, you can create pretty living documentation of your web api, like this:
swagger

Documenting the Response

In this post I am going to show you how to document the Response, and a new way to generate some response examples.

You can specify the type of response for Swashbuckle a number of ways. Consider a simple API endpoint which returns a list of Countries:

public class CountriesController : DefaultController
{
    [HttpGet]
    public async Task<HttpResponseMessage> Get()
    {
        var resource = new List<Country>
        {
            new Country {Code = "AR", Name = "Argentina"},
            new Country {Code = "BR", Name = "Brazil"},
            // etc etc omitted for brevity
        };

        return Request.CreateResponse(HttpStatusCode.OK, resource);
    }
}

One way of describing the response code and content for Swashbuckle is using a combination of XML comments, and the ResponseType attribute, like so:

/// <response code="200">Countries returned OK</response>
[HttpGet]
[ResponseType(typeof(IEnumerable<Country>))]
public async Task<HttpResponseMessage> Get()
{

However, this only allows for one type of response.

If your API method can return multiple types, i.e. in the case of an error, then you can use the new SwaggerResponse attribute:

[HttpGet]
[SwaggerResponse(HttpStatusCode.OK, Type=typeof(IEnumerable<Country>))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(IEnumerable<ErrorResource>))]
public async Task<HttpResponseMessage> Get(string lang)
{

The Swagger 2.0 spec allows for examples to be added to the Response. However, at time of writing Swashbuckle doesn’t support this. Fortunately Swashbuckle is extendible so here is a way of doing it.

Create a new SwaggerResponseExamplesAttribute.

public class SwaggerResponseExamplesAttribute : Attribute
{
    public SwaggerResponseExamplesAttribute(Type responseType, Type examplesType)
    {
        ResponseType = responseType;
        ExamplesType = examplesType;
    }

    public Type ResponseType { get; set; }
    public Type ExamplesType { get; set; }
}

Decorate your methods with it:

[SwaggerResponse(HttpStatusCode.OK, Type=typeof(IEnumerable<Country>))]
[SwaggerResponseExamples(typeof(IEnumerable<Country>), typeof(CountryExamples))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(IEnumerable<ErrorResource>))]
public async Task<HttpResponseMessage> Get(string lang)

Now you’ll need to add an Examples class, which will generate the example data

public interface IProvideExamples
{
    object GetExamples();
}

public class CountryExamples : IProvideExamples
{
    public object GetExamples()
    {
        return new List<Country>
        {
            new Country {Code = "AA", Name = "Test Country"},
            new Country {Code = "BB", Name = "And another"}
        };
    }
}

Now you can add an ExamplesOperationFilter which implements Swashbuckle’s IOperationFilter.

public class ExamplesOperationFilter : IOperationFilter
{
    public void Apply(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.schema.type == schema.type && x.Value.schema.@ref == schema.@ref).Value;

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

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

        return ConvertToCamelCase(examples);
    }

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

And finally configure it when you configure Swashbuckle’s startup.

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.

response

And then, when you browse the swagger-ui at /swagger, instead of an autogenerated example like this:
response old

You’ll see your desired example, like this:
response new

Be sure to check out Part 2, where we again use Swashbuckle to generate example requests.