Perhaps you should know some of the ASP.NET Core Web API use tips

Original: Perhaps you should know some of the ASP.NET Core Web API use tips

 I. Introduction

  In the current trend of software development, both before and after the end of the separation or service transformation, the back end is to provide more support for a variety of business clients web, app, desktop and other services by building the API interface, how to build a line with specifications, API interface is easy to understand that we need to consider the back-end developers. In this article, I will list some use to me in building the interface service using ASP.NET Core Web API some tips, because ignorant, there may be a wrong place, welcome that.

  Code Warehouse: https://github.com/Lanesra712/ingos-server

 二、Step by Step

  Because this article some of the knowledge involved in the previous article, there are already specific explanation, so here only explains how to how to use ASP.NET Core Web API, the details will not do too much . If you need more details, you can jump to the chain address given in the article to view it.

  The code used in this article is based on .NET Core 2.2 + .NET Standard 2.0 build, if you used a different version I use, may ultimately up code will be different, please know in advance. At the same time, this article will continue to exist in all the sample code in github repo listed in the preface, I will try to develop each point as a function commit, and will be updated from time to time in subsequent improvement, and ultimately build a project template based back-end domain driven thinking, If it helps you, welcome to continue to focus.

  1, lowercase route

   In my previous article ( build more readable ASP.NET Core Routing ) has mentioned, because .NET defaults to the class named Pascal, if the default route is generated, the final build route will address the presence of mixed case together, although in the case of .NET Core routing address for eventually to the correct resources, but in order to better meet the specifications of the front end, so here we first follow the previous article the method to modify the route listed in the address format generated by default.

  Because here we ultimately want to achieve is in line with Restful API interface style, so here we first need to generate the default URL address to all lowercase mode.

public  void ConfigureServices (IServiceCollection Services) 
{ 
    // lower case of the URL routing mode 
    services.AddRouting (Options => 
    { 
        options.LowercaseUrls = to true ; 
    }); 
}

  If you have seen the construction of more readable ASP.NET Core routing this article, you will discover that we finally realize that the hyphen (-) format Url address, then why do not we here for subsequent modification of it ?

  If you have a default template see .NET Core generated API Controller, look carefully, there actually is a characteristic route to use, so here we are not through the traditional route Startup.UseMvc defined template, or directly in the Startup.Configure the UseMvcWithDefaultRoute way to modify the routing address format of our generation.

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
}

  2, to allow cross-domain request

  Whether service transformation backend interface, or simply separation of the front and rear end project development, project our front-end and back-end interfaces are typically not deployed together, so we need to address cross-border visits will be involved when the front access interface The problem.

  For cross-domain request, we can use JSONP, or by configuring the server in response to the header parameter nginx header information, or the use of CORS, and or other solutions. You are free to choose, and here I direct CORS configuration for the support of the backend interface.

  In .NET Core has been added to this library in the Microsoft.AspNetCore.Cors for CORS support, because this library exists in .NET Core SDK we have installed, so we do not need to be here by Nuget installation, can be used directly.

  In .NET Core CORS configuration in the rules, we can add different authorization policies in Startup.ConfigureServices this method, then after a Controller or Action for this Attribute manner configured by adding EnableCors, where the policy if the policy specified name, specify the strategy will be used, if not specified, the default configuration is suitable for the system. Similarly, we can also set only one policy, be configured directly for the entire project, where I used across domains using a common request for the entire project configuration.

  When configuring CORS policy, we can set only allow requests from some URL address can access, or the specified interface to allow only certain HTTP methods to access, or must contain certain information in the header of the request can access our interface.

  In the code below, I define a strategy for cross-domain request for the entire project, and here I just set up a control for the requesting party URL address of the interface, by reading the data in the configuration file, so as to allow only certain IP can access our purpose interface.

public  class the Startup 
{ 
    // default cross-domain request policy name 
    Private  const  String _defaultCorsPolicyName = " Ingos.Api.Cors " ; 

    // This Method, the gets Called by the Use at The Runtime Services to the Add to the this Method, at The Container.. 
    public  void ConfigureServices ( Services IServiceCollection) 
    { 
        services.AddMvc ( 
            // add CORS authorized filter 
            Options => options.Filters.Add ( new new CorsAuthorizationFilterFactory (_defaultCorsPolicyName)) 
        ) .SetCompatibilityVersion (CompatibilityVersion.Version_2_2); 

        // configure authorization policy CORS
        services.AddCors(options => options.AddPolicy(_defaultCorsPolicyName,
            builder => builder.WithOrigins(
                    Configuration["Application:CorsOrigins"]
                    .Split(",", StringSplitOptions.RemoveEmptyEntries).ToArray()
                )
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials()));
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public voidThe Configure (App IApplicationBuilder, IHostingEnvironment the env) 
    { 
        // allow cross-domain request access 
        app.UseCors (_defaultCorsPolicyName); 
    } 
}

  For example, in the following settings, I only allow this one address can access our interface, if you need to specify multiple words, you can pass in English, are separated.

"Application": {
    "CorsOrigins": "http://127.0.0.1:5050"
}

  In some cases, if we do not want to limit it, only need to change the value of * can be.

"Application": {
    "CorsOrigins": "*"
}

  3, add the interface version control

   In some scenarios involve feature upgrades to the interface, and when we need to modify the interface logic and the old version of the interface can not be disabled, in order to reduce the impact to the original interface, we can take the form of adding the version information for the interface, thereby reduce the effects caused by the use of different versions. If you want to learn more about, you can view this article, a lift = " ASP.NET Core practical: Build API interface with version control .

   Before implementing an interface with a version control, we need to add the following two by Nuget dll, because I was configured in Ingos.Api.Core this library, so I installed next to the library, you need to your own situation choice ultimately is to install an interface Api project or in other libraries.

Install-Package Microsoft.AspNetCore.Mvc.Versioning
Install-Package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer

  After installation is complete, we can Startup.ConfigureServices method, interface configuration version information for the project, where I used solution is to add a version number to the URL address of the interface.

  Because for all the middleware configuration will be in Startup.ConfigureServices method, in order to maintain the purity of the process, here I wrote an extension method for configuring our api version can be called directly after.

public  static  class ApiVersionExtension 
{ 
    ///  <Summary> 
    /// Add API versioning Extension Method
     ///  </ Summary> 
    ///  <param name = "Services"> set of service lifecycle injected <see cref = " IServiceCollection "/> </ param> 
    public  static  void AddApiVersion ( the this IServiceCollection Services) 
    { 
        // add API supported 
        services.AddApiVersioning (O => 
        { 
            // if the information returned in response to header information in the API version 
            o.ReportApiVersions = to true ; 
    
            // default version of the API
            = o.DefaultApiVersion new new apiVersion ( . 1 , 0 ); 
    
            // when the API version is not specified, the default version set API version 
            o.AssumeDefaultVersionWhenUnspecified = to true ; 
        }); 

        // configuration API version information 
        services.AddVersionedApiExplorer (Option => 
        { 
            // API version packet name 
            option.GroupNameFormat = " 'v'VVVV " ; 
    
            // when the API version is not specified, the default version set API version 
            option.AssumeDefaultVersionWhenUnspecified = to true ; 
        }); 
    } 
}

  The final implementation of the extension method as shown in the code above, then we can directly in ConfigureServices method to directly call the extension method on it.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Config api version
    services.AddApiVersion();
}

  Now we remove the default project to create ValuesController generated v1 to create a folder in the Controllers directory, on behalf of the controller under this folder are version v1. UsersController to add a resource acquisition system users, now project file structure as shown in FIG.

  Now we have to transform our UsersController, we only need to add ApiVersion properties on the Controller or Action you can specify the current version information Controller / Action of. At the same time, because I need to add version information to the API URL address generated, so here we need to modify the characteristics of routing template, our version will add a route to the URL generated as a placeholder in the form of modification is completed code shown and achieved the following effects.

[ApiVersion("1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
}

  4、添加对于 Swagger 接口文档的支持

   在前后端分离开发的情况下,我们需要提供给前端开发人员一个接口文档,从而让前端开发人员知道以什么样的 HTTP 方法或是传递什么样的参数给后端接口,从而获取到正确的数据,而 Swagger 则提供了一种自动生成接口文档的方式,同时也提供类似于 Postman 的功能,可以实现对于接口的实时调用测试。

  首先,我们需要通过 Nuget 添加 Swashbuckle.AspNetCore 这个 dll 文件,之后我们就可以在此基础上实现对于 Swagger 的配置。

Install-Package Swashbuckle.AspNetCore

  与上面配置 API 接口的版本信息相似,这里我依旧采用构建扩展方法的方式来实现对于 Swagger 中间件的配置。具体的配置过程可以查看我之前写的文章(ASP.NET Core 实战:构建带有版本控制的 API 接口),这里只列出最终配置完成的代码。

public static void AddSwagger(this IServiceCollection services)
{
    // 配置 Swagger 文档信息
    services.AddSwaggerGen(s =>
    {
        // 根据 API 版本信息生成 API 文档
        //
        var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();

        foreach (var description in provider.ApiVersionDescriptions)
        {
            s.SwaggerDoc(description.GroupName, new Info
            {
                Contact = new Contact
                {
                    Name = "Danvic Wang",
                    Email = "[email protected]",
                    Url = "https://yuiter.com"
                },
                Description = "Ingos.API 接口文档",
                Title = "Ingos.API",
                Version = description.ApiVersion.ToString()
            });
        }

        // 在 Swagger 文档显示的 API 地址中将版本信息参数替换为实际的版本号
        s.DocInclusionPredicate((version, apiDescription) =>
        {
            if (!version.Equals(apiDescription.GroupName))
                return false;

            var values = apiDescription.RelativePath
                .Split('/')
                .Select(v => v.Replace("v{version}", apiDescription.GroupName)); apiDescription.RelativePath = string.Join("/", values);
            return true;
        });

        // 参数使用驼峰命名方式
        s.DescribeAllParametersInCamelCase();

        // 取消 API 文档需要输入版本信息
        s.OperationFilter<RemoveVersionFromParameter>();

        // 获取接口文档描述信息
        var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);
        var apiPath = Path.Combine(basePath, "Ingos.Api.xml");
        s.IncludeXmlComments(apiPath, true);
    });
}

  当我们配置完成后就可以在 Startup 类中去启用 Swagger 文档。

public void ConfigureServices(IServiceCollection services)
{
    // 添加对于 swagger 文档的支持
    services.AddSwagger();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
    // 启用 Swagger 文档
    app.UseSwagger();
    app.UseSwaggerUI(s =>
    {
        // 默认加载最新版本的 API 文档
        foreach (var description in provider.ApiVersionDescriptions.Reverse())
        {
            s.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
                $"Sample API {description.GroupName.ToUpperInvariant()}");
        }
    });
}

  因为我们在之前设置构建的 API 路由时包含了版本信息,所以在最终生成的 Swagger 文档中进行测试时,我们都需要在参数列表中添加 API 版本这个参数。这无疑是有些不方便,所以这里我们可以通过继承 IOperationFilter 接口,控制在生成 API 文档时移除 API 版本参数,接口的实现方法如下所示。

public class RemoveVersionFromParameter : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        var versionParameter = operation.Parameters.Single(p => p.Name == "version");
        operation.Parameters.Remove(versionParameter);
    }
}

  当我们实现自定义的接口后就可以在之前针对 Swagger 的扩展方法中调用这个过滤方法,从而实现移除版本信息的目的,扩展方法中的添加位置如下所示。

public static void AddSwagger(this IServiceCollection services)
{
    // 配置 Swagger 文档信息
    services.AddSwaggerGen(s =>
    {
        // 取消 API 文档需要输入版本信息
        s.OperationFilter<RemoveVersionFromParameter>();
    });
}

  最终的实现效果如下图所示,可以看到,参数列表中已经没有版本信息这个参数,但是我们在进行接口测试时会自动帮我们添加上版本参数信息。

  这里需要注意,因为我们需要在最终生成的 Swagger 文档中显示出我们对于 Controller 或是 Action 添加的注释信息,所以这里我们需要在 Web Api 项目的属性选项中勾选上输出 XML 文档文件。同时如果你不想 VS 一直提示你有方法没有添加参数信息,这里我们可以在取消显示警告这里添加上 1591 这个参数。

  5、构建符合 Restful 风格的接口

   在没有采用 Restful 风格来构建接口返回值时,我们可能会习惯于在接口返回的信息中添加一个接口是否请求成功的标识,就像下面代码中示例的这种返回形式。

{
    sueecss: true
    msg: '',
    data: [{
        id: '20190720214402',
        name: 'zhangsan'
    }]
}

  但是,当我们想要构建符合 Restful 风格的接口时,我们就不能再这样进行设计了,我们应该通过返回的 HTTP 响应状态码来标识这次访问是否成功。一些比较常用的 HTTP 状态码如下表所示。

HTTP 状态码 涵义 解释说明
200 OK 用于一般性的成功返回,不可用于请求错误返回
201 Created 资源被创建
202 Accepted 用于资源异步处理的返回,仅表示请求已经收到。对于耗时比较久的处理,一般用异步处理来完成
204 No Content 此状态可能会出现在 PUT、POST、DELETE 的请求中,一般表示资源存在,但消息体中不会返回任何资源相关的状态或信息
400 Bad Request 用于客户端一般性错误信息返回, 在其它 4xx 错误以外的错误,也可以使用,错误信息一般置于 body 中
401 Unauthorized 接口需要授权访问,为通过授权验证
403 Forbidden 当前的资源被禁止访问
404 Not Found 找不到对应的信息
500 Internal Server Error 服务器内部错误

  我们知道 HTTP 共有四个谓词方法,分别为 Get、Post、Put 和 Delete,在之前我们可能更多的是使用 Get 和 Post,对于 Put 和 Delete 方法可能并不会使用。同样的,如果我们需要创建符合 Restful 风格的接口,我们则需要根据这四个 HTTP 方法谓词一些约定俗成的功能定义去定义对应接口的 HTTP 方法。

HTTP 谓词方法 解释说明
GET 获取资源信息
POST 提交新的资源信息
PUT 更新已有的资源信息
DELETE 删除资源

  例如,对于一个获取所有资源的方法,我们可能会定义接口的默认返回 HTTP 状态码为 200 或是 400,当状态码为 200 时,代表数据获取成功,接口可以正常返回数据,当状态码为 400 时,则代表接口访问出现问题,此时则返回错误信息对象。

  在 ASP.NET Core Web API 中,我们可以通过在 Action 上添加 ProducesResponseType 特性来定义接口的返回状态码。通过 F12 按键我们可以进入 ProducesResponseType 这个特性,可以看到这个特性存在两个构造方法,我们可以只定义接口返回 HTTP 状态码或者是在定义接口返回的状态码时同时返回的具体对象信息。

  上面给出的接口案例的示例代码如下所示,从下图中可以看到,Swagger 会自动根据我们的 ProducesResponseType 特性来列出我们接口可能返回的 HTTP 状态码和对象信息。这里因为是示例程序,UserListDto 并没有定义具体的属性信息,所以这里显示的是一个不包含任何属性的对象数组。

/// <summary>
/// 获取全部的用户信息
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<UserListDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Get()
{
    // 1、获取资源数据

    // 2、判断数据获取是否成功
    if (true)
        return Ok(new List<UserListDto>());
    else
        return BadRequest(new
        {
            statusCode = StatusCodes.Status400BadRequest,
            description = "错误描述",
            msg = "错误信息"
        });
}

  可能这里你可能会有疑问,当接口返回的 HTTP 状态码为 400 时,返回的信息是什么鬼,与我们定义的错误信息对象字段不同啊?原来,在 ASP.NET Core 2.1 之后的版本中,对于 API 接口返回 400 的 HTPP 状态码会默认返回 ProblemDetails 对象,因为这里我们并没有将接口中的返回 BadRequest 中的错误信息对象作为 ProducesResponseType 特性的构造函数的参数,所以这里就采用了默认的错误信息对象。

  当然,当接口的 HTTP 返回状态码为 400 时,最终还是会返回我们自定义的错误信息对象,所以这里为了不造成前后端对接上的歧义,我们最好将返回的对象信息也作为参数添加到 ProducesResponseType 特性中。

  同时,除了上面示例的接口中通过返回 OK 方法和 BadRequest 方法来表明接口的返回 HTTP 状态码,在 ASP.NET Core Web API 中还有下列继承于 ObjectResult 的方法来表明接口返回的状态码,对应信息如下。

HTTP 状态码 方法名称
200 OK()
201 Created()
202 Accepted()
204 NoContent()
400 BadRequest()
401 Unauthorized()
403 Forbid()
404 NotFound()

  6、使用 Web API 分析器

  在上面的示例中,因为我们需要指定接口需要返回的 HTTP 状态码,所以我们需要提前添加好 ProducesResponseType 特性,在某些时候我们可能在代码中添加了一种 HTTP 状态码的返回结果,可是却忘了添加特性描述,那么有没有一种便捷的方式提示我们呢?

  在 ASP.NET Core 2.2 及以后更新的 ASP.NET Core 版本中,我们可以通过 Nuget 去添加 Microsoft.AspNetCore.Mvc.Api.Analyze 这个包,从而实现对我们的 API 进行分析,首先我们需要将这个包添加到我们的 API 项目中。

Install-Package Microsoft.AspNetCore.Mvc.Api.Analyzers

   例如在下面的接口代码中,我们根据用户的唯一标识去寻找用户数据,当获取不到数据的时候,返回的 HTTP 状态码为 400,而我们只添加了 HTTP 状态码为 200 的特性说明。此时,分析器将 HTTP 404 状态代码的缺失特性说明做为一个警告,并提供了修复此问题的选项,我们进行修复后就可以自动添加特性。

/// <summary>
/// 获取用户详细信息
/// </summary>
/// <param name="id">用户唯一标识</param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserEditDto), StatusCodes.Status200OK)]
public IActionResult Get(string id)
{
    // 1、根据 Id 获取用户信息
    UserEditDto user = null;

    if (user == null)
        return NotFound();
    else
        return Ok(user);
}

  However, after the completion of the automatic document completion fact, we still need to do something, for example, if we need to specify the type of the return value of Type, we still need to manually add to the ProducesResponseType characteristics.

  Padded carrying characteristics when the analyzer also help us fill up a ProducesDefaultResponseType characteristics. By pointing to the Microsoft documentation Swagger document ( Swagger the Default the Response ) you can learn, if we no matter what the state of the interface, response in response to the structure of the final return is the same, we can use the feature to specify the response directly ProducesDefaultResponseType of response of the structure, without the need to add each have a characteristic HTTP state.

 Third, the summary

    In this article, I introduced some to use in the process of using ASP.NET Core Web API's some tips and some solutions to the stepped pit in the past, if a bit of help to you if ,Honored. Also, if you have a better solution, or for some of you stepped before the Web API pit solutions, you are welcome to put forward in the comments area.

  My blog is about to be synchronized to Tencent cloud + community, inviting all of them settled: https://cloud.tencent.com/developer/support-plan?invite_code=1jarnly4f8ua3

Guess you like

Origin www.cnblogs.com/lonelyxmas/p/11266866.html