1

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.

Hooman Bahreini
  • 14,480
  • 11
  • 70
  • 137
  • Usually, model descriptions are placed at the bottom of Swagger documentation page, because a model could be used in multiple controllers and methods. – Alexander Mar 28 '23 at 04:38
  • @Alexander: thanks, I have added another screenshot... I cannot see any section on the documentation page regarding the models... – Hooman Bahreini Mar 28 '23 at 04:48
  • 1
    Is the "Model" tab enabled? Try to switch th the tab (instead of "Example Value") and expand a property. – Alexander Mar 28 '23 at 04:52
  • @Alexander: Thanks, I hadn't noticed that tab... I have added a new screenshot. The `` tag shows up now, but the `` tag does not seem to be showing up! – Hooman Bahreini Mar 28 '23 at 05:00
  • 1
    Does this answer your question? [How to set example in swagger by xml documentation?](https://stackoverflow.com/questions/53045237/how-to-set-example-in-swagger-by-xml-documentation) It seems, you just need to update the Swashbuckle package. – Alexander Mar 28 '23 at 05:31
  • 2
    You might find [this SO post](https://stackoverflow.com/q/39111885/3094533) useful. – Zohar Peled Mar 28 '23 at 05:55
  • @ZoharPeled: thanks but that post explain how to include the XML comments... in my case the XML comments are being included, the issue I am having is how to display the example. – Hooman Bahreini Mar 29 '23 at 05:06
  • @Alexander: thanks, your answer (which is now deleted) helped me solve the issue, I used Swashbuckle.Examples... I would have accepted that answer if it was not deleted. – Hooman Bahreini Mar 29 '23 at 22:57
  • My answer requires to use additional NuGet package. But I found [another post](https://stackoverflow.com/questions/53045237/how-to-set-example-in-swagger-by-xml-documentation) that says your existing solution should work with actual version of Swashbuckle, so it's more simple in my opinion. Also the second answer from the post is same as my answer. So, I have provided the link to the post in comments above and deleted my answer. I'm glad it was helpful. – Alexander Mar 30 '23 at 03:57

2 Answers2

2

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 = "john.smith@example-company.com",
            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

Hooman Bahreini
  • 14,480
  • 11
  • 70
  • 137
1

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

pbur
  • 85
  • 7