本文首发于 码友网 -- 《基于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)分离的接口服务》

基于ASP.NET Core 3.x的端点路由(Endpoint Routing)实现控制器(Controller)和操作(Action)分离的接口服务的更多相关文章

  1. 用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(1)

    最近使用vscode比较多. 学习了一下如何在mac上使用vscode开发asp.netcore项目. 这里是我写的关于vscode的一篇文章: https://www.cnblogs.com/cgz ...

  2. 如何基于asp.net core的Identity框架在mysql上作身份验证处理

    首先了解这个概念,我一开始也是理解和掌握基本的概念,再去做程序的开发.Identity框架是微软自己提供,基于.net core平台,可拓展.轻量 级.面向多个数据库的身份验证框架.IdentityS ...

  3. 基于Asp.Net Core的简单社区项目源代码开源

    2019年3月27号 更新版本 本项目基于 ASP.NET CORE 3.0+EF CORE 3.0开发 使用vs2019 +sqlserver 2017(数据库脚本最低支持sql server 20 ...

  4. AServer - 基于Asp.net core Kestrel的超迷你http服务器

    AServer是基于ASP.NET Core Kestrel封装的一个超迷你http服务器.它可以集成进你的Core程序里,用来快速的响应Http请求,而不需要集成整个ASP.NET Core MVC ...

  5. 基于ASP.NET Core 创建 Web API

    使用 Visual Studio 创建项目. 文件->新建->项目,选择创建 ASP.NET Core Web 应用程序. 基于 ASP.NET Core 2.0 ,选择API,身份验证选 ...

  6. 基于ASP.Net Core开发的一套通用后台框架

    基于ASP.Net Core开发一套通用后台框架 写在前面 这是本人在学习的过程中搭建学习的框架,如果对你有所帮助那再好不过.如果您有发现错误,请告知我,我会第一时间修改. 知其然,知其所以然,并非重 ...

  7. 基于ASP.NET Core 3.0快速搭建Razor Pages Web应用

    前言 虽然说学习新的开发框架是一项巨大的投资,但是作为一个开发人员,不断学习新的技术并快速上手是我们应该掌握的技能,甚至是一个.NET Framework开发人员,学习.NET Core 新框架可以更 ...

  8. [译]基于ASP.NET Core 3.0的ABP v0.21已发布

    基于ASP.NET Core 3.0的ABP v0.21已发布 在微软发布仅仅一个小时后, 基于ASP.NET Core 3.0的ABP v0.21也紧跟着发布了. v0.21没有新功能.它只是升级到 ...

  9. 基于Asp.Net Core,利用ZXing来生成二维码的一般流程

    本文主要介绍如何在.net环境下,基于Asp.Net Core,利用ZXing来生成二维码的一般操作.对二维码工作原理了解,详情见:https://blog.csdn.net/weixin_36191 ...

随机推荐

  1. SSD-Tensorflow 512x512 训练配置

    搞了几天终于把这个给搞得差不多了,遇到的错误这里也记录一下: 一.配置[配置什么的300和512其实差不多,这里只举一个例子来分析一下] 之前的文件修改什么的和300x300的一样:https://w ...

  2. layui 表单验证汇总

    1 表单自带校验 lay-verify:是表单验证的关键字有以下值供选择: required 必填项phone 手机号email 邮箱date 日期url 链接identity 身份证number 数 ...

  3. File类与IO流

    一.File类与IO流 数组.集合等内容都是把数据放在内存里面,一旦关机或者断电,数据就会立刻从内存里面消失.而IO主要讲文件的传输(输入和输出),把内存里面的数据持久化到硬盘上,如.txt .avi ...

  4. 5 art-template

    npm 在终端命令下执行装包,就会在该目录下产生一个node_modules包 art-template地址:https://aui.github.io/art-template/zh-cn/docs ...

  5. Oracle12C配置对外访问

    Oracle12C配置对外访问 第一步: 开放端口或者关闭防火墙 第二步: 配置Oracle net manager打开Net manager 修改为共享服务器 第三步: 配置连接数打开Databas ...

  6. MySql数据库规范与原则

    1.数据库表名命名规范 采用26个英文字母(区分大小写)和0-9的自然数(经常不需要)加上下划线'_'组成; 命名简洁明确,多个单词用下划线'_'分隔; 例如:user_login, user_pro ...

  7. [java]将多个文件压缩成一个zip文件

    此文进阶请见:https://www.cnblogs.com/xiandedanteng/p/12155957.html 方法: package zip; import java.io.Buffere ...

  8. Mybatis动态SQL配置

    使用 if where foreach标签对映射配置文件中sql语句进行动态配置 1.首先在dao接口中设置两个查询方法 package sun.dao; import sun.domain.Quer ...

  9. SpringCloud-config分布式配置

    为什么要统一管理微服务配置? 随着微服务不断的增多,每个微服务都有自己对应的配置文件.在研发过程中有测试环境.UAT环境.生产环境,因此每个微服务又对应至少三个不同环境的配置文件.这么多的配置文件,如 ...

  10. [LeetCode]621. 任务调度器(贪心)

    题目 给定一个用字符数组表示的 CPU 需要执行的任务列表.其中包含使用大写的 A - Z 字母表示的26 种不同种类的任务.任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完.CP ...