RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.7k stars 1.24k forks source link

Set Swagger response description via xml docs or attribute #1746

Open OculiViridi opened 5 years ago

OculiViridi commented 5 years ago

Update by @RicoSuter: Documentation about how to specify the response descriptions with XML Docs.

I'm on a .NET Core 2.1 Web API project and trying to obtain a complete swagger documentation like Swagger PetStore Demo. I've enabled the XML comments on my project, by adding the <GenerateDocumentationFile> tag on the .csproj file:

<GenerateDocumentationFile>true</GenerateDocumentationFile>

I'm using this syntax on my controllers:

[Authorize]
[ApiVersion("1.0")]
[Produces("application/json")]
[SwaggerTag("Anomalies", Description = "Anomalies management.")]
public class AnomaliesController : BaseController
{
    /// <summary>
    /// Find anomaly by ID
    /// </summary>
    /// <param name="id">ID of the anomaly to return</param>
    /// <response code="200">Successful operation</response>
    /// <response code="400">Invalid ID supplied</response>
    /// <response code="404">Anomaly not found</response>
    [HttpGet("{id:long}")]
    [ProducesResponseType(typeof(api.Anomaly), (int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.NotFound)]
    public async Task<ActionResult<api.Anomaly>> Get(long id)
    {
        return Mapper.Map<api.Anomaly>(await Mediator.Send(new GetAnomalyByIdQuery() { Id = id }));
    }
}

I also tried with SwaggerResponse attribute:

[SwaggerResponse(HttpStatusCode.OK, typeof(api.Anomaly), Description = "Successfull operation")]

but without any result.

This is what I get:

image

This is what I want, like in PetStore Demo (red circle are my missing values):

image

RicoSuter commented 5 years ago

I think you can implement an own attribute which inherits from ProducesResponseType and exposes a Description property for now

RicoSuter commented 5 years ago

For this we need to improve this code here: https://github.com/RSuter/NSwag/blob/86e06e49b529fef46e9683e75aa07ff1a7383837/src/NSwag.SwaggerGeneration/Processors/OperationResponseProcessorBase.cs#L98

=> read from xml docs <response> or something else...

But replacing ProducesResponseType with SwaggerResponse should work...

OculiViridi commented 5 years ago

@RSuter

But replacing ProducesResponseType with SwaggerResponse should work...

It works! Thanks!

This is my adjusted code, based on the previous sample.

[Authorize]
[ApiVersion("1.0")]
[Produces("application/json")]
[SwaggerTag("Anomalies", Description = "Anomalies management.")]
public class AnomaliesController : BaseController
{
    /// <summary>
    /// Find anomaly by ID
    /// </summary>
    /// <param name="id">ID of the anomaly to return</param>
    /// <response code="200">Successful operation</response>
    /// <response code="400">Invalid ID supplied</response>
    /// <response code="404">Anomaly not found</response>
    [HttpGet("{id:long}")]
    [SwaggerResponse(HttpStatusCode.OK, typeof(api.Anomaly), Description = "Successfull operation")]
    [SwaggerResponse(HttpStatusCode.BadRequest, typeof(ProblemDetails), Description = "Invalid ID supplied")]
    [SwaggerResponse(HttpStatusCode.NotFound, typeof(ProblemDetails), Description = "Anomaly not found")]
    public async Task<ActionResult<api.Anomaly>> Get(long id)
    {
        return Mapper.Map<api.Anomaly>(await Mediator.Send(new GetAnomalyByIdQuery() { Id = id }));
    }
}

Note: ProblemDetails object is the ASP.NET Core implementation of RFC 7807 Problem Details for HTTP APIs, so it is a standard object that can be used to manage errors.

SeppPenner commented 5 years ago

I'm using the following code in my example project under https://github.com/SeppPenner/NetCoreMQTTExampleIdentityConfig:

/// <summary>
/// Gets the claim by id. GET "api/claim/5".
/// </summary>
/// <param name="claimId">
/// The claim identifier.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
[HttpGet("{claimId}")]
[SwaggerResponse(HttpStatusCode.OK, typeof(DtoReadUserClaim), Description = "Claim found.")]
[SwaggerResponse(HttpStatusCode.NotFound, typeof(int), Description = "Claim not found.")]
[SwaggerResponse(HttpStatusCode.InternalServerError, typeof(string), Description = "Internal server error.")]
[ProducesResponseType(typeof(DtoReadUserClaim), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(int), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<DtoReadUserClaim>> GetClaimById(long claimId)
{
    \\ ...
}

which renders to: image

The red circles show the stuff that is missing:

How can I get this to work?

P.S. The controller description works as excpected:

image

OculiViridi commented 5 years ago

The red circles show the stuff that is missing:

  • Summary for the method, e.g. Find pet by id
  • Summary for the return value, e.g. Returns a single pet
  • Documentation of the parameter, e.g. ID of the pet to return

How can I get this to work?

P.S. The controller description works as excpected:

image

The same happened to me when moved to v12 and now on v13 of NSwag.

SeppPenner commented 5 years ago

The same happened to me when moved to v12 and now on v13 of NSwag.

I don't remember when this happend, but I'm using the latest version of NSwag for sure.

SeppPenner commented 5 years ago

This happens if you use the version 3 of Swagger (AddOpenApiDocument() method) as well.

davidkeaveny commented 4 years ago

I'm using NSwag 13.0.6, and it is using the XMLDOC markup, where the <summary /> tag gets written as the method sumary, the <returns /> tag gets written to the default successful response value, <remarks /> tag forms the body of the documentation, and <param name="" /> tags populate the parameter descriptions. You'll need to make sure you project is set to write the XML output, and NSwag seems to pick that up automatically. That covers most scenarios; the only place it currently falls down is writing a description for non-default return status codes (e.g. 404, 401), and providing example request/response bodies.

So in @SeppPenner 's case, you've got all the above, apart from the <remarks /> tag, so I'm guessing you don't have the <DocumentationFile /> property set in your .csproj

SeppPenner commented 4 years ago

So in @SeppPenner 's case, you've got all the above, apart from the tag, so I'm guessing you don't have the property set in your .csproj

Ah, this might be the case... I will check that.

RicoSuter commented 4 years ago

You can use the <response code=“...”>... xml docs tags to specify the response texts.

davidkeaveny commented 4 years ago

Thanks @RicoSuter that works a treat.

For example, the following endpoint:

/// <summary>
/// Get employee
/// </summary>
/// <param name="id">The identifier of the employee that should be returned.</param>
/// <returns>
/// Returns the specified employee, if it exists and the current user has access to it.
/// </returns>
/// <remarks>
/// Returns the specified employee, if it exists and the current user has access to it. This can depend on any combination of user roles, cost centres, locations, organisation charts etc.
/// </remarks>
/// <response code="404">The specified employee does not exist, or the current user does not have access to it.</response>
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Employee))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ProblemDetails))]
[HttpGet("{id:long}")]
public async Task<IActionResult> GetById(long id)
{
    var results = await _dbContext.Employees.FirstOrDefaultAsync(e => e.Id == id);
    if (results == null)
    {
        return NotFound();
    }

    return Ok(results);
}

gets rendered in OpenAPI 3 as:

image

I still need to generate sample data to use in the responses, but it's looking very good, so thanks again @RicoSuter !

RicoSuter commented 4 years ago

https://github.com/RicoSuter/NJsonSchema/wiki/XML-Documentation#define-examples

SeppPenner commented 4 years ago

@RicoSuter The example from @davidkeaveny works perfectly. In my case, I forgot the documentation file. I guess, you can close this issue.

Leon99 commented 4 years ago

I believe it would still be beneficial to use ProducesResponseType attribute. 1 - it's supplied with ASP.NET 2 - it doesn't force me to specify the return type two times Also, in most cases I'm okay with just showing the default status code description and overriding it with a custom description is not desired.

Leon99 commented 4 years ago

Plus, neither XML comments nor SwaggerResponse work with web API conventions

czioutas commented 4 years ago
        /// <summary>
        /// Returns the requested Contract with the given Id
        /// </summary>
        /// <remarks>*Requires Authorization</remarks>
        /// <returns>A Contract</returns>
        /// <response code="200">Request completed Successfully</response>
        /// <response code="400">Bad Request</response>
        /// <response code="401">Unathorized Request</response>
        /// <response code="500">Interal Error has occured</response>
        [HttpGet("{id}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(Exception), StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(typeof(Exception), StatusCodes.Status400BadRequest)]
        [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]

So this is an example block of code that gets repeated around 100 times in our codebase.

I managed to move all the ProducesResponseType to our custom convention and apply it to the whole codebase through 1 cmd.

However because the ProducesResponseType doesnt contain any field for Description we have to use the xml docs to provide the description. I am not sure why .NetCore doesnt include a description field.

Is anyone else with the same issue?

RicoSuter commented 4 years ago

Yeah this is currently a limitation. You can implement a global custom operation processor (IOperationProcessor) which adds the descriptions to all operations with the convention.

RicoSuter commented 4 years ago

Or use an xmlinclude (one line) and load the xml descs from a single xml file

RicoSuter commented 4 years ago

https://blog.rsuter.com/how-to-write-detailed-c-xml-documentation-and-still-keep-the-source-code-clean/

sakshivij commented 4 years ago

https://blog.rsuter.com/how-to-write-detailed-c-xml-documentation-and-still-keep-the-source-code-clean/

Does the data gets initialized with default values ? Because that's what I get. Is there a way to show the values I set? <example> { "Id": "1", "Name": "Name1" } </example> On UI rather than giving me the values it shows 0 and string.

ddombrowsky commented 4 years ago

@davidkeaveny

So in @SeppPenner 's case, you've got all the above, apart from the <remarks /> tag, so I'm guessing you don't have the <DocumentationFile /> property set in your .csproj

I was losing my mind trying to figure out why none of my XML summaries were showing up in the exported json file or in the web ui. The comment above here led me to the answer. My solution was to:

Add the following dependencies:

Add this line to the csproj file.

<GenerateDocumentationFile>true</GenerateDocumentationFile>

For me, I got hung up on the fact that simply setting a DocumentationFile won't cut it. It needs to be named correctly and in the proper place for it to be picked up.

(From https://github.com/RicoSuter/NJsonSchema/wiki/XML-Documentation#net-core )

hkarask commented 3 years ago

A quick hack to add missing response status descriptions:

services.AddSwaggerDocument(config =>
{
    config.PostProcess = document =>
        document.Paths.Values
            .SelectMany(p => p.Values)
            .SelectMany(p => p.Responses)
            .Where(r => string.IsNullOrWhiteSpace(r.Value.Description))
            .ToList()
            .ForEach(res => {
                if (descriptions.ContainsKey(res.Key))
                        res.Value.Description = descriptions[res.Key];
            });
...

var descriptions = new Dictionary<string, string>
{
    { "200", "OK" },
    { "201", "Created" },
    { "202", "Accepted" },
    { "400", "Bad Request" },
    { "401", "Unauthorized" },
    { "403", "Forbidden" },
    { "404", "Not Found" },
    { "405", "Mehtod Not Allowed" },
    { "406", "Not Acceptable" },
    { "500", "Server Error" },
};
ngnam commented 3 years ago

I using this:

/// <param name="command">An instance of the CreatePatientCommand</param>
        /// <returns>The status of the operation</returns>
        /// <response code="201">Returns the newly created item</response>
        /// <response code="400">If the item is null</response>   
        /// <response code="200">If the item exist</response>    
        [HttpPost]
        [Produces("application/json")]
        [ProducesResponseType(typeof(string), StatusCodes.Status201Created)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status200OK)]

and a config in my.cspj

<GenerateDocumentationFile>true</GenerateDocumentationFile>
    <NoWarn>$(NoWarn);1591</NoWarn>