本文首发于 码友网 -- 《基于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. EditText设置输入的类型,只能输入纯数字,只能输入手机号码,只能输入邮箱等等。

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985转载请说明出处. 下面以数字.电话为例讲述EditText怎么设置输入类型,其他类型可以参考InputT ...

  2. UGUI核心元素、基本控件、复合控件和高级控件

    UGUI的核心元素: Anchor(锚点):每个控件都有一个Anchor属性,控件的4个顶点,分别与Anchor的4个点保持不变的距离,不受屏幕分辨率变化的影响. 系统默认设置控件的Anchor位置在 ...

  3. Codeforece E. Anton and Permutation

    主席树算贡献l,r中交换位置,算出>=rank(h) 和 <=rank(h) a[l],a[r] 先不统计 a[l]比a[r]大的话交换后ans-1,a[l]比a[r]小的话交换后ans- ...

  4. 【HttpRunner v3.x】笔记—8.用例引用、变量传递

    看到这里,对于httprunner已经有了一个大概的了解,现在想对于一些比较重要或者常用的功能,进行一些实践操作. 毕竟那谁说过,"纸上得来终觉浅,绝知此事要躬行." 上一篇提到了 ...

  5. Almost All Divisors(求因子个数及思维)

    ---恢复内容开始--- We guessed some integer number xx. You are given a list of almost all its divisors. Alm ...

  6. 设置logback的log文件地址为程序运行的当前目录

    这个需求虽然怪异,却也不是无事生非,在以jar包为执行主体的程序中就会遇到. 设置方法就是指定Log_HOME为./,其在如下配置文件的第四行: <?xml version="1.0& ...

  7. MySQL教程 | 菜鸟教程

    装数据库失败后的重装步骤!!! --[创建数据库]CREATE DATABASE <数据库名>: --使用mysqladamin 创建数据库-- 使用普通用户,你可能需要特定的权限来创建或 ...

  8. adb命令装包failure问题

    图片摘自CSDN,待验证

  9. Node.js连接MongoDB数据库

    首先要启动MongoDB服务器 先找到你的mongoDb安装目录,我的如下:就在bin文件夹下创建一个data文件夹,data内包含两个空文件夹,如下: 接着回到bin文件夹处,按住shift键,右击 ...

  10. JUC使用

    1.什么是JUC 源码 + 官方文档 面试高频问! java.util 工具包.包.分类 业务:普通的线程代码 Thread Runnable 没有返回值.效率相比入 Callable 相对较低! 2 ...