Hooman Bahreini
Hooman Bahreini

Reputation: 15559

How to provide model documentation and example value using Swashbuckle?

I have created an API method using Web API 2 (MVC 5), as shown below:

/// <summary>
/// Import all of the jobs for the given organisation. This method assumes that all of the organisation's active jobs are present in the jobs array.
/// To delete a job, simply exclude it from the jobs array. To delete all of the jobs, pass an empty array
/// </summary>
/// <param name="org">Organisation Id, provided by Shopless team</param>
/// <param name="jobs">Full list of jobs which should be imported, json array</param>
/// <response code="200">Jobs list have been queued for import (includes validation errors if any)</response>
/// <response code="401">Access to this organisation was denied</response>
/// <response code="404">Invalid organisation id</response>
[SwaggerResponse(HttpStatusCode.BadRequest)]
[SwaggerResponse(HttpStatusCode.NotFound)]
[SwaggerResponse(HttpStatusCode.Unauthorized)]
[HttpPost]
[Route("org/{org}/jobs/full-import")]
public IHttpActionResult FullImport(long org, [FromBody] List<JobApiDto> jobs)
{
    if (!ModelState.IsValid)
    {
        return BadRequest("Jobs array is invalid");
    }
    else if (org < 1)
    {
        return NotFound("Invalid organisation id");
    }
    else if (!((ClaimsPrincipal)User).HasReadWriteAccessToOrganisation(org))
    {
        return Unauthorized("Access to this organisation was denied");
    }

    _apiProductUploader.Upload(org, jobs, out string message);
    return Accepted(message);
}

The above methods accepts a list of JobApiDto:

public class JobApiDto
{
   /// <summary>
   /// Job Id (unique id assigned to the job by the API consumer)
   /// </summary>
   /// <example>e5f52dae-e008-49bd-898b-47b5f1a52f40</example>
   public string UniqueThirdPartyId { get; set; }

   /// <summary>
   /// Short description which will be displayed in search result
   /// </summary>
   /// <example>Competitive salary, great team, cutting edge technology!</example>
   public string Synopsis { get; set; }

   // more properties

And this is how the Swagger documentation looks like:

enter image description here

When I expand OrganisationJobs action:

enter image description here

And this is the Model tab: enter image description here

As you can see the xml documentation for the controller has generated the correct description for the API method, but I am not able to understand why the description that I have provided for my model (i.e. JobApiDto) does not show?

Also when I click on Example Value nothing happens.

Upvotes: 2

Views: 1392

Answers (2)

pbur
pbur

Reputation: 103

Meanwhile (using .NET 7 and Swashbuckle.AspNetCore 6.5.0) providing an example value can be easily achieved using the <example> tag.

Controller method:

/// <summary>
/// Import an unapproved measurement.
/// </summary>
[HttpPost("/measurement")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> ImportNewMeasurement(NewMeasurementImportViewModel viewModel)
{
    ...
}

Viewmodel:

public class NewMeasurementImportViewModel
{
   /// <example>02AAA000</example>
   [Required] [JsonProperty("barcode")] public string Barcode { get; set; } = "";
   ...
}

Result:

Example provided by tag

Upvotes: 2

Hooman Bahreini
Hooman Bahreini

Reputation: 15559

Thanks to Alexandre for pointing out this answer.

I installed Swashbuckle.Examples Nuget package. (The documentation is great.)

I had to annotated the controller with:

[SwaggerRequestExample(typeof(JobApiDto), typeof(ListJobApiDtoExample), jsonConverter: typeof(StringEnumConverter))]

This is how the controller definition looks like:

/// <summary>
/// Import all of the jobs for the given organisation. This method assumes that all of the organisation's active jobs are present in the jobs array.
/// To delete a job, simply exclude it from the jobs array. To delete all of the jobs, pass an empty array
/// </summary>
/// <param name="org">Organisation Id, provided by Shopless team</param>
/// <param name="jobs">Full list of jobs which should be imported, json array</param>
[SwaggerRequestExample(typeof(JobApiDto), typeof(ListJobApiDtoExample), jsonConverter: typeof(StringEnumConverter))]
[SwaggerResponse(HttpStatusCode.OK, Description = "Jobs array has been queued for import", Type = typeof(ImportResponse))]
[SwaggerResponseExample(HttpStatusCode.OK, typeof(ImportResponseExamples))]
[SwaggerResponse(HttpStatusCode.BadRequest, "Jobs array is invalid")]
[SwaggerResponse(HttpStatusCode.NotFound, "Invalid organisation id")]
[SwaggerResponse(HttpStatusCode.Unauthorized, "Access to this organisation was denied")]
[HttpPost]
[Route("org/{org}/jobs/full-import")]
public IHttpActionResult FullImport(long org, [FromBody] List<JobApiDto> jobs)
{
    if (!ModelState.IsValid)
    {
        return BadRequest("Jobs array is invalid");
    }
    else if (org < 1)
    {
        return NotFound("Invalid organisation id");
    }
    else if (!((ClaimsPrincipal)User).HasReadWriteAccessToOrganisation(org))
    {
        return Unauthorized("Access to this organisation was denied");
    }

    _apiProductUploader.Upload(org, jobs, out ImportResponse importResponse);
    return Accepted(importResponse);
}

And here is the implementation of ListJobApiDtoExample:

public class ListJobApiDtoExample : IExamplesProvider
{
    public object GetExamples()
    {
        return new JobApiDto()
        {
            UniqueThirdPartyId = "e5f52dae-e008-49bd-898b-47b5f1a52f40",
            CategoryId = 1183,
            Title = "Senior Software Developer",
            Description = "Example company is looking for a senior software developer... more details about the job",
            Reference = "",
            Synopsis = "Competitive salary, great team, cutting edge technology!",
            MinSalary = 120000,
            MaxSalary = 140000,
            SalaryPer = "Per annum",
            DisplaySalary = true,
            CustomSalaryText = "plus bonus",
            WorkType = "Full time",
            Location = "Wellington CBD",
            ContactName = "John Smith",
            ContactEmail = "[email protected]",
            ContactPhoneNumber = "021 123 456 789",
            ApplicationUrl = "",
            LogoUrl = "https://www.example-company.com/my-company-logo",
            YouTubeVideo = "https://www.youtube.com/watch?v=khb7pSEUedc&t=1s",
            StartDate = null,
            PostedAt = null
        };
    }
}

Note that I also have an example for my API return type, ImportResponse. Similar to previous model, I have this annotation on the controller:

[SwaggerResponseExample(HttpStatusCode.OK, typeof(ImportResponseExamples))] 

And here is it's implantation:

public class ImportResponseExamples : IExamplesProvider
{
    public object GetExamples()
    {
        return new ImportResponse()
        {
            Message = "Products are queued to be imported"
        };
    }
}

And now, the documentation shows the examples correctly:

enter image description here

Upvotes: 2

Related Questions