基於ASP.NET Core 3.x的端點路由(Endpoint Routing)實現控制器(Controller)和操作(Action)分離的介面服務
阿新 • • 發佈:2020-10-09
本文首發於 [碼友網][3] -- [《基於ASP.NET Core 3.x的端點路由(Endpoint Routing)實現控制器(Controller)和操作(Action)分離的介面服務》][4]
## 前言
如題,今天為大家分享一種基於ASP.NET Core 3.x的端點路由(Endpoint Routing)實現控制器(Controller)和操作(Action)分離的介面服務方案。
為什麼寫這篇文章?為什麼控制器(Controller)和操作(Action)分離?這來源由Github上的一個開源ASP.NET Core專案--[**Ardalis.ApiEndpoints**][1],其中的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]
2.建立一個路徑為[Endpoints/v1/Student/]的檔案目錄,在此目錄中建立一個繼承至`BaseEndpoint`的類`GetById.cs`,其中的`TRequest`表示介面的請求引數實體類,`TResponse`表示介面的返回實體類。
3.在`GetById.cs`類中實現抽象類中的`Handle()`方法。
4.標記`Handle()`方法的HTTP請求型別,如:HttpGet,HttpPost...
5.定義返回實體類`TResponse`,示例中的類名為`StudentResponse.cs`
程式碼如下:
```csharp
using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Mvc;
namespace EndpointDemo.Endpoints.v1.Students
{
///
/// 獲取指定ID的學生資訊
///
public class GetById : BaseEndpoint
{
///
/// 獲取指定ID的學生資訊
///
///
///
[HttpGet, Route("api/v1/student/{id:int}")]
public override ActionResult Handle(int id)
{
var response = new StudentResponse
{
Id = id,
Name = "Rector"
};
return Ok(response);
}
}
}
```
StudentResponse.cs
```csharp
namespace EndpointDemo.Endpoints.v1.Students
{
///
/// 返回的學生資訊響應實體類
///
public class StudentResponse
{
///
/// ID
///
public int Id { get; set; }
///
/// 姓名
///
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的許可權訪問),如下:
```csharp
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檔案,如下:
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"JwtToken": {
"SecretKey": "SecretKeywqewqeqqqqqqqqqqqweeeeeeeeeeeeeeeeeee",
"Issuer": "http://localhost:56369/"
}
}
```
接下來,我們使用`SwaggerOperation`來豐富介面文件的註釋,修改`GetById.cs`檔案如下:
```csharp
using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
namespace EndpointDemo.Endpoints.v1.Students
{
///
/// 獲取指定ID的學生資訊
///
public class GetById : BaseEndpoint
{
///
/// 獲取指定ID的學生資訊
///
///
///
[Authorize]
[HttpGet, Route("api/v1/student/{id:int}")]
[SwaggerOperation(
Summary = "獲取指定ID的學生資訊",
Description = "獲取指定ID的學生資訊",
OperationId = "Student.GetById",
Tags = new[] { "StudentEndpoint" }
)]
public override ActionResult Handle(int id)
{
var response = new StudentResponse
{
Id = id,
Name = "Rector"
};
return Ok(response);
}
}
}
```
同時,我還建立了一個`Create.cs`檔案,用來演示[HttpPost]請求,如下:
```csharp
using System;
using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
namespace EndpointDemo.Endpoints.v1.Students
{
///
/// 建立新的學生記錄
///
public class Create : BaseEndpoint
{
///
/// 建立新的學生記錄
///
///
///
[HttpPost, Route("api/v1/student/create")]
[SwaggerOperation(
Summary = "建立新的學生記錄",
Description = "建立新的學生記錄",
OperationId = "Student.Create",
Tags = new[] { "StudentEndpoint" }
)]
public override ActionResult Handle(NewStudentRequest request)
{
var response = new StudentResponse
{
Name = request.Name,
Id = new Random().Next(1, 100)
};
return Ok(response);
}
}
}
```
NewStudentRequest.cs
```csharp
using System.ComponentModel.DataAnnotations;
namespace EndpointDemo.Endpoints.v1.Students
{
///
/// 建立學生的實體類
///
public class NewStudentRequest
{
///
/// 姓名
///
[Required]
public string Name { get; set; }
}
}
```
建立用於使用者授權的目錄`v1/Auth`,並建立獲取令牌的類`GrantToken.cs`,程式碼如下:
```csharp
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
{
///
///
///
public class GrantToken : BaseEndpoint
{
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 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 如果執行成功,你將看到如下介面:
![](https://statics.codedefault.com/uploads/u/2020/09/nlg67o5t3z.png)
這時,如果你直接點選【獲取指定ID的學生資訊】,介面返回的是401錯誤,如圖:
![](https://statics.codedefault.com/uploads/u/2020/09/5324kw06y5.png)
因為我們還未對介面訪問進行授權,那麼我們需要先請求授權介面:/api/v1/auth/grant_token,以獲取使用者令牌,如下:
![](https://statics.codedefault.com/uploads/u/2020/09/2h9io3n8a8.png)
將獲取到的令牌填入授權視窗中,如下:
![](https://statics.codedefault.com/uploads/u/2020/09/ttncg21jl2.png)
![](https://statics.codedefault.com/uploads/u/2020/09/e55bl60o68.png)
最後,再請求【獲取指定ID的學生資訊】,得到正確的介面返回內容,如下:
![](https://statics.codedefault.com/uploads/u/2020/09/qs5m0r8q6x.png)
專案結構如下:
![](https://statics.codedefault.com/uploads/u/2020/09/802j8z8ha4.png)
本文為你分享的**Ardalis.ApiEndpoints**內容就到這裡,使用**Ardalis.ApiEndpoints**,你可在不用建立控制器的場景下任意地組織和管理你的介面服務端點。感謝你的閱讀!
本文示例原始碼託管地址請至原文獲取:[《基於ASP.NET Core 3.x的端點路由(Endpoint Routing)實現控制器(Controller)和操作(Action)分離的介面服務》][4]
[1]: https://github.com/ardalis/ApiEndpoints
[2]: https://www.nuget.org/packages/Ardalis.ApiEndpoints/
[3]: https://codedefault.com
[4]: https://codedefault.com/p/asp-net-core-3x-endpoint-routing-api-implem