@[TOC] When developing a Web API, writing documentation is a pain, and without documentation people don’t know how to call it, so they have to write.

Swagger can automatically generate interface documentation and test interfaces, greatly freeing programmer productivity.

1 installation

Install Swashbuckle via NuGet.

http://localhost:7001/swagger (note for you here

The page appears as follows:

2 Change the name and version

The name and version number in the box above can be changed. Open the swaggerConfig. cs file and find the following code:

c.SingleApiVersion("v1"."API.Test");
Copy the code

Modify the parameters and re-publish them.

3 Display Description

Swagger can read comments in code and display them on a web page. This way, all we need to do is write the comments in code, and we can produce an API document that others can read.

Swagger reads comments from an XML file generated at compile time. The XML file is not generated by default, so the configuration needs to be modified first.

Step 1: Right-click project -> Properties -> Generate and tick the XML document file.

The second step:

GlobalConfiguration.Configuration
    .EnableSwagger(c =>
        {
            c.SingleApiVersion("v2"."Test API interface documentation");
            // Configure the XML file path
            c.IncludeXmlComments($@"{System.AppDomain.CurrentDomain.BaseDirectory}\bin\API.Test.xml");
        })
Copy the code

Note: When publishing, XML files are not published together and need to be manually copied to the publishing directory.

4 Display controller comments and Chinese version

Controller comments are not displayed by default, you need to write them yourself. In the new class SwaggerControllerDescProvider App_Start, code is as follows:

/// <summary>
///Swagger Displays the description of the 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 the controller description from the API documentation
    /// </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 comments
                        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()); } } } } }returncontrollerDescDict; }}Copy the code

In addition, create a new swagger.js file and set it as an embedded resource. The purpose of this file is to display controller comments and localization. The 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": "Implementation Notes"."Response Class": "Response class"."Status": "State"."Parameters": "Parameters"."Parameter": "Parameters"."Value": "Value"."Description": "Description"."Parameter Type": "Parameter type"."Data Type": "Data type"."Response Messages": "Response message"."HTTP Status Code": "HTTP status code"."Reason": "Why"."Response Model": "Response model"."Request URL": "The request URL"."Response Body": "Response body"."Response Code": "Response code"."Response Headers": "Response header"."Hide Response": "Hide response"."Headers": "Head"."Try it out!": "Try it!."Show/Hide": "Show/Hide"."List Operations": "Display operation"."Expand Operations": "Expand operation"."Raw": "Original"."can't parse JSON. Raw result": "Unable to parse JSON. Original results"."Model Schema": "Model Architecture"."Model": "Model"."apply": "Application"."Username": "Username"."Password": "Password"."Terms of service": "Terms of Service"."Created by": "Founder"."See more at": "See more:"."Contact the developer": "Contact the developer"."api version": "API version"."Response Content Type": "Response Content Type"."fetching resource": "Acquiring resources"."fetching resource list": "Obtaining resource list"."Explore": "Browse"."Show Swagger Petstore Example Apis": "Show Swagger Petstore sample Apis"."Can't read from server. It may not have the appropriate access-control-origin settings.": "Cannot read from server. "Access-control-origin may not be set up correctly."."Please specify the protocol for": "Please specify protocol:"."Can't read swagger JSON from": "Cannot read swagger JSON on"."Finished Loading Resource Information. Rendering Swagger UI": "Resource information has been loaded. Rendering Swagger UI"."Unable to read api": "Unable to read API"."from path": Slave path."server returned": "Server return"
});
$(function () {
    window.SwaggerTranslator.translate();
    window.SwaggerTranslator.setControllerSummary();
});
Copy the code

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"); 
            });
Copy the code

5 Different query parameters for the same route

In ASP.NET Web API, it is possible to have the same route, the same HTTP method, different query parameters, but sorry, swagger does not support, and 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;
}
Copy the code

Error: Multiple operations with path ‘API /users’ and method ‘GET’

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

This configuration means that when this happens, take the first method to show.

This avoids errors, but multiple methods will only show one in Swagger. Treating the symptoms rather than the root cause is not recommended. So the only solution was to set up a different route. It is not known if this issue will be fixed in a later version.

6 Ignore some fields in the Model

As shown in the following figure, when creating a User, the background needs a User class as a parameter. Click Model on the right to display the attributes and comments for the User class.

State

To mark on these properties [Newtonsoft. Json. JsonIgnore] features, no longer shows in the swagger.

Of course, there are drawbacks to this approach, as the default serialization method called by the Web API when returning data is also newtonsoft.json serialization.

7 pass the header

When the API is called, some information is put in HTTP headers, such as tokens. This Swagger is also supportive.

Create a new feature:

public class ApiAuthorizeAttribute : Attribute{}Copy the code

Create a new class code like this:

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 a permission filter

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

If you encounter a method marked with the ApiAuthorizeAttribute attribute, add a parameter named token to the header.

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

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

The effect is as follows:

8 HTTP status code when an error occurs

We return a 400 in this method

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

However, the status code returned by Swagger is 0.

Content
content

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