基於ASP.NET Core 3.x的端點路由(Endpoint Routing)實現控制器(Controller)和操作(Action)分離的接口服務


本文首發於 碼友網 -- 《基於ASP.NET Core 3.x的端點路由(Endpoint Routing)實現控制器(Controller)和操作(Action)分離的接口服務》

前言

如題,今天為大家分享一種基於ASP.NET Core 3.x的端點路由(Endpoint Routing)實現控制器(Controller)和操作(Action)分離的接口服務方案。

為什么寫這篇文章?為什么控制器(Controller)和操作(Action)分離?這來源由Github上的一個開源ASP.NET Core項目--Ardalis.ApiEndpoints,其中的Readme中描述了為什么要控制器和操作分離,為什么有ApiEndpoints這個項目的出現,引用並總結如下:

常規的MVC模式本質上是一種反模式,這種模式集合了許多但從不相互調用的方法,並且很少在相同的狀態下操作。隨着項目的發展,一個控制器會變得越來越臃腫,甚至可能無法控制。當你需要創建一個不同類型的接口服務的時候,還得首先創建相應的控制器,無法做到業務邏輯分開處理等等問題。

其實,在常規的MVC或者Web API應用程序中,許多開發者也許已經意識到了這種問題的存在,但仍然沒有更好的辦法來組織,拆分和管理這些控制器和操作,所以就出現了Ardalis.ApiEndpoints這個項目。

Ardalis.ApiEndpoints簡介

如上所述,Ardalis.ApiEndpoints是為了解決分離控制器(Controller)類和操作(Action)服務的解決方案。有了它,你可以按照不同的業務來分開組織並管理服務接口端點,甚至可以為不同服務創建獨立的文件夾,就像ASP.NET Razor Pages的項目結構類似,而不同把所有服務放到一個控制器中。下面我們就使用Ardalis.ApiEndpoints來創建一個示例。

Ardalis.ApiEndpoints示例

1.首先,我們創建一個ASP.NET Core 3.x 的Web項目,命名為:EndpointDemo,然后使用Nuget安裝Ardalis.ApiEndpoints
2.創建一個路徑為[Endpoints/v1/Student/]的文件目錄,在此目錄中創建一個繼承至BaseEndpoint<TRequest, TResponse>的類GetById.cs,其中的TRequest表示接口的請求參數實體類,TResponse表示接口的返回實體類。
3.在GetById.cs類中實現抽象類中的Handle()方法。
4.標記Handle()方法的HTTP請求類型,如:HttpGet,HttpPost...
5.定義返回實體類TResponse,示例中的類名為StudentResponse.cs

代碼如下:

using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Mvc;

namespace EndpointDemo.Endpoints.v1.Students
{
    /// <summary>
    /// 獲取指定ID的學生信息
    /// </summary>
    public class GetById : BaseEndpoint<int, StudentResponse>
    {
        /// <summary>
        /// 獲取指定ID的學生信息
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet, Route("api/v1/student/{id:int}")]
        public override ActionResult<StudentResponse> Handle(int id)
        {
            var response = new StudentResponse
            {
                Id = id,
                Name = "Rector"
            };
            return Ok(response);
        }
    }
}

StudentResponse.cs

namespace EndpointDemo.Endpoints.v1.Students
{
    /// <summary>
    /// 返回的學生信息響應實體類
    /// </summary>
    public class StudentResponse
    {
        /// <summary>
        /// ID
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }
    }
}

以上就完成了一個基於ASP.NET Core 3.x的端點服務接口,這里我們並沒有創建任何控制器,請求地址為:http://localhost:12345/api/v1/student/{id:int}

Startup.cs文件中需要注冊控制器的服務,如:
services.AddControllers();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});

以下我們來集成Swagger接口文檔,還是使用Nuget安裝Swashbuckle.AspNetCore.Annotations,然后在Startup.cs文件中配置Swagger(同時配置了Swagger的權限訪問),如下:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System;
using System.IO;
using System.Text;

namespace EndPointDemo
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
                c.EnableAnnotations();
                c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT Authorization header using the Bearer scheme (Example: 'Bearer 12345abcdef')",
                    Name = "Authorization",
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Bearer",
                    BearerFormat = "JWT",
                    In = ParameterLocation.Header
                });

                c.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            }
                        },
                        new string[] {}

                    }
                });
                var filePath = Path.Combine(AppContext.BaseDirectory, "EndpointDemo.xml");
                c.IncludeXmlComments(filePath);
            });
            services.AddAuthentication(option =>
            {
                option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            }).AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = false,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["JwtToken:Issuer"],
                    ValidAudience = Configuration["JwtToken:Issuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtToken:SecretKey"]))
                };
            });
            services.AddControllers();
            services.AddRazorPages();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseSwagger();

            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"));
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
                endpoints.MapRazorPages();
            });
        }
    }
}

修改appsettings.json文件,如下:

{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    },
    "AllowedHosts": "*",
    "JwtToken": {
        "SecretKey": "SecretKeywqewqeqqqqqqqqqqqweeeeeeeeeeeeeeeeeee",
        "Issuer": "http://localhost:56369/"
    }
}

接下來,我們使用SwaggerOperation來豐富接口文檔的注釋,修改GetById.cs文件如下:

using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;

namespace EndpointDemo.Endpoints.v1.Students
{
    /// <summary>
    /// 獲取指定ID的學生信息
    /// </summary>
    public class GetById : BaseEndpoint<int, StudentResponse>
    {
        /// <summary>
        /// 獲取指定ID的學生信息
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [Authorize]
        [HttpGet, Route("api/v1/student/{id:int}")]
        [SwaggerOperation(
            Summary = "獲取指定ID的學生信息",
            Description = "獲取指定ID的學生信息",
            OperationId = "Student.GetById",
            Tags = new[] { "StudentEndpoint" }
            )]
        public override ActionResult<StudentResponse> Handle(int id)
        {
            var response = new StudentResponse
            {
                Id = id,
                Name = "Rector"
            };
            return Ok(response);
        }
    }
}

同時,我還創建了一個Create.cs文件,用來演示[HttpPost]請求,如下:

using System;
using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;

namespace EndpointDemo.Endpoints.v1.Students
{
    /// <summary>
    /// 創建新的學生記錄
    /// </summary>
    public class Create : BaseEndpoint<NewStudentRequest, StudentResponse>
    {
        /// <summary>
        /// 創建新的學生記錄
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>

        [HttpPost, Route("api/v1/student/create")]
        [SwaggerOperation(
            Summary = "創建新的學生記錄",
            Description = "創建新的學生記錄",
            OperationId = "Student.Create",
            Tags = new[] { "StudentEndpoint" }
        )]
        public override ActionResult<StudentResponse> Handle(NewStudentRequest request)
        {
            var response = new StudentResponse
            {
                Name = request.Name,
                Id = new Random().Next(1, 100)
            };
            return Ok(response);
        }
    }
}

NewStudentRequest.cs

using System.ComponentModel.DataAnnotations;

namespace EndpointDemo.Endpoints.v1.Students
{
    /// <summary>
    /// 創建學生的實體類
    /// </summary>
    public class NewStudentRequest
    {
        /// <summary>
        /// 姓名
        /// </summary>
        [Required]
        public string Name { get; set; }
    }
}

創建用於用戶授權的目錄v1/Auth,並創建獲取令牌的類GrantToken.cs,代碼如下:

using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Mvc;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Swashbuckle.AspNetCore.Annotations;

namespace EndpointDemo.Endpoints.v1.Auth
{
    /// <summary>
    /// 
    /// </summary>
    public class GrantToken : BaseEndpoint<AuthInfoRequest, TokenResponse>
    {
        private readonly IConfiguration _config;

        public GrantToken(IConfiguration config)
        {
            _config = config;
        }

        [SwaggerOperation(
            Summary = "用戶登錄",
            Description = "用戶登錄",
            OperationId = "Auth.GrantToken",
            Tags = new[] { "AuthEndpoint" }
        )]
        [AllowAnonymous]
        [HttpPost, Route("api/v1/auth/grant_token")]
        public override ActionResult<TokenResponse> Handle(AuthInfoRequest request)
        {
            if (request == null) return Unauthorized();
            var validUser = Authenticate(request);
            var token = "";
            if (validUser)
            {
                token = BuildToken();
            }
            else
            {
                return Unauthorized();
            }
            var response = new TokenResponse
            {
                Token = token
            };
            return Ok(response);
        }

        private string BuildToken()
        {
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JwtToken:SecretKey"]));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(_config["JwtToken:Issuer"],
                _config["JwtToken:Issuer"],
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);

            return new JwtSecurityTokenHandler().WriteToken(token);
        }

        private bool Authenticate(AuthInfoRequest login)
        {
            var validUser = login.Username == "admin" && login.Password == "123456";

            return validUser;
        }
    }
}

運行項目,打開地址:http://localhost:56369/swagger 如果運行成功,你將看到如下界面:

這時,如果你直接點擊【獲取指定ID的學生信息】,接口返回的是401錯誤,如圖:

因為我們還未對接口訪問進行授權,那么我們需要先請求授權接口:/api/v1/auth/grant_token,以獲取用戶令牌,如下:

將獲取到的令牌填入授權窗口中,如下:

最后,再請求【獲取指定ID的學生信息】,得到正確的接口返回內容,如下:

項目結構如下:

本文為你分享的Ardalis.ApiEndpoints內容就到這里,使用Ardalis.ApiEndpoints,你可在不用創建控制器的場景下任意地組織和管理你的接口服務端點。感謝你的閱讀!

本文示例源碼托管地址請至原文獲取:《基於ASP.NET Core 3.x的端點路由(Endpoint Routing)實現控制器(Controller)和操作(Action)分離的接口服務》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM