[swagger] the use and avoidance of swagger in C Chen

@

Catalog

When developing web api, it's a pain to write documents. Without documents, people don't know how to call them, so they have to write them.

swagger can automatically generate interface documents and test interfaces, which greatly liberates the productivity of programmers.

1 installation

Install Swashbuckle through NuGet.

After the installation is completed, an additional SwaggerConfig.cs file will appear in the app start folder.

Regenerate and publish api, open web page http://localhost:7001/swagger host)

The web page appears as follows:

2 modification name and version number

The name and version number shown in the box above can be modified. Open the SwaggerConfig.cs file and find the following code:

c.SingleApiVersion("v1", "API.Test");

Modify the parameters and republish them.

3 display description

swagger can read the comments in the code and display them on the web page. In this way, we only need to write comments in the code, and then we can generate an API document for others to read.

swagger reads comments through an xml file generated at compile time. This xml file is not generated by default, so you need to modify the configuration first.

Step 1: right click item - > Property - > generate, and check the XML document file.

Step 2: add configuration
Add the following configuration to the SwaggerConfig.cs file:

GlobalConfiguration.Configuration
    .EnableSwagger(c =>
        {
            c.SingleApiVersion("v2", "test API Interface document");
            // Configure xml file path
            c.IncludeXmlComments($@"{System.AppDomain.CurrentDomain.BaseDirectory}\bin\API.Test.xml");
        })

Note: when publishing, the XML files will not be published together. You need to copy them to the publishing directory manually.

4 display controller notes and Chinese

By default, controller comments will not be displayed. You need to write them yourself.
Create a new class swaggercontrollerdescrovider in app start. The code is as follows:

/// <summary>
///Description of swagger display controller
/// </summary>
public class SwaggerCacheProvider : ISwaggerProvider
{
    private readonly ISwaggerProvider _swaggerProvider;
    private static ConcurrentDictionary<string, SwaggerDocument> _cache = new ConcurrentDictionary<string, SwaggerDocument>();
    private readonly string _xmlPath;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="swaggerProvider"></param>
    ///< param name = "xmlpath" > XML document path < / param >
    public SwaggerCacheProvider(ISwaggerProvider swaggerProvider, string xmlpath)
    {
        _swaggerProvider = swaggerProvider;
        _xmlPath = xmlpath;
    }

    public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
    {
        var cacheKey = string.Format("{0}_{1}", rootUrl, apiVersion);
        //Read only once
        if (!_cache.TryGetValue(cacheKey, out SwaggerDocument srcDoc))
        {
            srcDoc = _swaggerProvider.GetSwagger(rootUrl, apiVersion);

            srcDoc.vendorExtensions = new Dictionary<string, object>
            {
                { "ControllerDesc", GetControllerDesc() }
            };
            _cache.TryAdd(cacheKey, srcDoc);
        }
        return srcDoc;
    }

    /// <summary>
    ///Read controller description from API document
    /// </summary>
    ///< returns > description of all controllers < / returns >
    public ConcurrentDictionary<string, string> GetControllerDesc()
    {
        ConcurrentDictionary<string, string> controllerDescDict = new ConcurrentDictionary<string, string>();
        if (File.Exists(_xmlPath))
        {
            XmlDocument xmldoc = new XmlDocument();
            xmldoc.Load(_xmlPath);

            string[] arrPath;
            int cCount = "Controller".Length;
            foreach (XmlNode node in xmldoc.SelectNodes("//member"))
            {
                string type = node.Attributes["name"].Value;
                if (type.StartsWith("T:"))
                {
                    arrPath = type.Split('.');
                    string controllerName = arrPath[arrPath.Length - 1];
                    if (controllerName.EndsWith("Controller"))  //Controller
                    {
                        //Get controller comment
                        XmlNode summaryNode = node.SelectSingleNode("summary");
                        string key = controllerName.Remove(controllerName.Length - cCount, cCount);
                        if (summaryNode != null && !string.IsNullOrEmpty(summaryNode.InnerText) && !controllerDescDict.ContainsKey(key))
                        {
                            controllerDescDict.TryAdd(key, summaryNode.InnerText.Trim());
                        }
                    }
                }
            }
        }
        return controllerDescDict;
    }
}

In addition, create a new swagger.js file and set it as an embedded resource. The function of this file is to display controller comments and sinicize. JS code is as follows:

'use strict';
window.SwaggerTranslator = {
    _words: [],

    translate: function () {
        var $this = this;
        $('[data-sw-translate]').each(function () {
            $(this).html($this._tryTranslate($(this).html()));
            $(this).val($this._tryTranslate($(this).val()));
            $(this).attr('title', $this._tryTranslate($(this).attr('title')));
        });
    },

    setControllerSummary: function () {
        $.ajax({
            type: "get",
            async: true,
            url: $("#input_baseUrl").val(),
            dataType: "json",
            success: function (data) {
                var summaryDict = data.ControllerDesc;
                var id, controllerName, strSummary;
                $("#resources_container .resource").each(function (i, item) {
                    id = $(item).attr("id");
                    if (id) {
                        controllerName = id.substring(9);
                        strSummary = summaryDict[controllerName];
                        if (strSummary) {
                            $(item).children(".heading").children(".options").first().prepend('<li class="controller-summary" title="' + strSummary + '">' + strSummary + '</li>');
                        }
                    }
                });
            }
        });
    },
    _tryTranslate: function (word) {
        return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word;
    },

    learn: function (wordsMap) {
        this._words = wordsMap;
    }
};


/* jshint quotmark: double */
window.SwaggerTranslator.learn({
    "Warning: Deprecated": "Warning: obsolete",
    "Implementation Notes": "Achieve remarks",
    "Response Class": "Response class",
    "Status": "state",
    "Parameters": "parameter",
    "Parameter": "parameter",
    "Value": "value",
    "Description": "describe",
    "Parameter Type": "Parameter type",
    "Data Type": "data type",
    "Response Messages": "Response message",
    "HTTP Status Code": "HTTP Status code",
    "Reason": "Reason",
    "Response Model": "Response model",
    "Request URL": "request URL",
    "Response Body": "Response body",
    "Response Code": "Response code",
    "Response Headers": "Response head",
    "Hide Response": "Hidden response",
    "Headers": "head",
    "Try it out!": "Want a go!",
    "Show/Hide": "display/hide",
    "List Operations": "Display operation",
    "Expand Operations": "Deployment operation",
    "Raw": "Original",
    "can't parse JSON.  Raw result": "Cannot resolve JSON. Original result",
    "Model Schema": "Model architecture",
    "Model": "Model",
    "apply": "application",
    "Username": "User name",
    "Password": "Password",
    "Terms of service": "Terms of service",
    "Created by": "creator",
    "See more at": "See more:",
    "Contact the developer": "Contact developer",
    "api version": "api Edition",
    "Response Content Type": "response Content Type",
    "fetching resource": "Getting resources",
    "fetching resource list": "Getting resource list",
    "Explore": "browse",
    "Show Swagger Petstore Example Apis": "display Swagger Petstore Example Apis",
    "Can't read from server.  It may not have the appropriate access-control-origin settings.": "Unable to read from server. May not be set up correctly access-control-origin. ",
    "Please specify the protocol for": "Please specify the agreement:",
    "Can't read swagger JSON from": "Unable to read swagger JSON to",
    "Finished Loading Resource Information. Rendering Swagger UI": "Resource information loaded. Rendering Swagger UI",
    "Unable to read api": "Unable to read api",
    "from path": "From the path",
    "server returned": "Server return"
});
$(function () {
    window.SwaggerTranslator.translate();
    window.SwaggerTranslator.setControllerSummary();
});

Add the following configuration to SwaggerConfig.cs:

GlobalConfiguration.Configuration
    .EnableSwagger(c =>
        {
            c.CustomProvider((defaultProvider) => new SwaggerCacheProvider(defaultProvider, $@"{System.AppDomain.CurrentDomain.BaseDirectory}\bin\API.Test.xml"));
        })
        .EnableSwaggerUi(c =>
            {
                c.InjectJavaScript(System.Reflection.Assembly.GetExecutingAssembly(), "API.Test.swagger.js"); 
            });

5. The same route and different query parameters

In the actual ASP.NET Web API, there can be methods with the same route, the same HTTP method, and different query parameters, but sorry, swagger does not support it, and it will directly report an error.

The following code,

[Route("api/users")]
public IEnumerable<User> Get()
{
    return users;
}

[Route("api/users")]
public IEnumerable<User> Get(int sex)
{
    return users;
}

Error: multiple operations with path 'API / users' and method' get '


You can add the following configuration in SwaggerConfig.cs:

GlobalConfiguration.Configuration
    .EnableSwagger(c =>
        {
            c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
        })

The meaning of this configuration is to take the first method to display when this situation occurs.

This avoids reporting errors, but multiple methods only show one in swagger. It is not recommended to treat the symptoms rather than the root causes. So the only solution is to set up different routes. I don't know if this problem will be fixed in later versions.

6 ignore some fields in the Model

As shown in the figure below, when creating a new User, the background needs a User class as a parameter. Click the Model on the right to display the properties and comments of the User class.

However, some fields do not need to be passed by the caller. For example, State, the caller does not need to know the existence of these fields.

Mark these attributes with the [Newtonsoft.Json.JsonIgnore] feature, which is no longer shown in swagger.

Of course, this approach also has disadvantages, because when the web api returns data, the default serialization method called is also Newtonsoft.Json serialization.

7 delivery header

When calling the api, some information is put in the HTTP Header, such as token. This swagger is also supported.

Create a new property:

public class ApiAuthorizeAttribute : Attribute
{

}

Create a new class code as follows:

public class AuthorityHttpHeaderFilter : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        if (operation.parameters == null)
            operation.parameters = new List<Parameter>();

        //Determine whether to add permission filter

        var isAuthorized = apiDescription.ActionDescriptor.GetCustomAttributes<ApiAuthorizeAttribute>().Any();
        if (isAuthorized)
        {
            operation.parameters.Add(new Parameter { name = "token", @in = "header", description = "token", required = false, type = "string" });
        }
    }
}

This code tells swagger to add a parameter named token in the header if the method encountered is marked with the ApiAuthorizeAttribute attribute.

Finally, you need to register this filter in SwaggerConfig.cs.

GlobalConfiguration.Configuration
    .EnableSwagger(c =>
        {
            c.OperationFilter<AuthorityHttpHeaderFilter>();
        })

The effect is as follows:

8 HTTP status code on error

We return a 400 in the method

[Route("api/users")]
public HttpResponseMessage Post([FromBody]User user)
{
    return new HttpResponseMessage()
    {
        Content = new StringContent("Error creating new user", Encoding.UTF8, "application/json"),
        StatusCode = HttpStatusCode.BadRequest
    };
}

However, the status code returned in swagger is 0.

This is because content specifies JSON format, but the content passed in is not JSON format.

Change content to JSON format, or change mediaType to text/plain.

Keywords: C# JSON xml Attribute encoding

Added by nbalog on Tue, 07 Apr 2020 10:29:41 +0300