Search code examples
c#asp.net-mvc-5swaggerasp.net-web-api2swashbuckle

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.


Solution

  • 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