什么是路由
路由(Routing)负责匹配传入的HTTP请求,然后将这些请求发送到应用的可执行终结点(Endpoint)
。终结点是应用的可执行请求处理代码单元。终结点在应用中进行定义,并在应用启动时进行配置。终结点匹配过程可以从请求的URL中提取值,并为请求处理提供这些值。通过使用应用中的终结点信息,路由还能生成映射到终结点的URL。
应用可以使用以下内容配置路由:
Controllers
RazorPages
SignalR
gRPC服务
启用终结点的中间件,例如运行状况检查
通过路由注册的委托和Lambda
路由注册方式
路由系统的核心作用是,将访问URL和应用程序Controller形成一种映射关系,这种映射关系有两种作用:
把Url映射到对应的Controller的Action上
根据Controller和Action的名字来生成URL
ASP.Net Core提供两种方式来注册路由
路由模板的方式,之前传统的方式,作为MVC页面的Web配置
RouteAttribute的方式,更适合Web API的场景,更适合前后端分离的架构
正则表达式
自定义IRouteConstraint
URL的生成
ASP.Net Core提供了两个类可以根据路由的信息来反向生成URL地址
LinkGenerator,全新提供的一个链接生成对象,可从容器获取它,根据需要生成URL地址
IUrlHelper,和之前MVC框架中使用的MVCHelper很像
https://github.com/TaylorShi/HelloRoutingEndpoint
什么是Swagger
https://swagger.io
Swagger是一套功能强大而又易于使用的API开发工具,适用于团队和个人,使开发贯穿整个API生命周期,从设计和文档,到测试和部署。
Swagger由开源、免费和商业化的工具组合而成,允许任何人,从技术工程师到街头聪明的产品经理,都能建立人人喜爱的惊人的API。
Swagger是由SmartBear软件公司建立的,该公司是为团队提供软件质量工具的领导者。SmartBear是软件领域中一些最大的名字的背后,包括Swagger、SoapUI和QAComplete。
Swashbuckle.AspNetCore为用ASP.NET Core构建的API提供Swagger工具。直接从你的路由、控制器和模型中生成漂亮的API文档,包括一个探索和测试操作的用户界面。
除了Swagger 2.0和OpenAPI 3.0生成器外,Swashbuckle还提供了一个嵌入式版本的swagger-ui,它由生成的Swagger JSON驱动。这意味着你可以用始终与最新代码同步的活的文档来补充你的API。最重要的是,它只需要最少的编码和维护,让你能够专注于建立一个很棒的API。
和OpenAPI
Swagger(OpenAPI)是一个与语言无关的规范,用于描述RESTAPI。它使计算机和用户无需直接访问源代码即可了解RESTAPI的功能。
其主要目标是:
尽量减少连接分离的服务所需的工作量
减少准确记录服务所需的时间
.NET的两个主要OpenAPI实现
Swashbuckle
NSwag
Swagger项目已于2015年捐赠给OpenAPI计划,自此它被称为OpenAPI
。这两个名称可互换使用。不过,"OpenAPI"指的是规范。"Swagger"指的是来自使用OpenAPI规范的SmartBear的开放源代码和商业产品系列。后续开放源代码产品(如
OpenAPIGenerator
)也属于Swagger系列名称,尽管SmartBear未发布也是如此。
OpenAPI是一种规范
Swagger是一种使用OpenAPI规范的工具。例如,OpenAPIGenerator和SwaggerUI
OpenAPI规范(openapi.json)
OpenAPI规范是描述API功能的文档。
该文档基于控制器和模型中的XML和属性注释
。它是OpenAPI流的核心部分,用于驱动诸如SwaggerUI之类的工具。
默认情况下,它命名为
openapi.json
。下面是为简洁起见而缩减的OpenAPI规范的:
"openapi": "3.0.1",
"info": {
"title": "API V1",
"version": "v1"
"paths": {
"/api/Todo": {
"get": {
"tags": [
"Todo"
"operationId": "ApiTodoGet",
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ToDoItem"
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ToDoItem"
"text/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ToDoItem"
"post": {
"/api/Todo/{id}": {
"get": {
"put": {
"delete": {
"components": {
"schemas": {
"ToDoItem": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
"name": {
"type": "string",
"nullable": true
"isCompleted": {
"type": "boolean"
"additionalProperties": false
SwaggerUI
SwaggerUI提供了基于Web的UI,它使用生成的OpenAPI规范提供有关服务的信息
。
Swashbuckle和NSwag均包含SwaggerUI的嵌入式版本,因此可使用中间件注册调用将该嵌入式版本托管在ASP.NET Core应用中。
控制器中的每个公共操作方法都可以从UI中进行测试。选择方法名称可以展开该部分。添加所有必要的参数,然后选择
Try it Out
Swashbuckle三大组件
Swashbuckle包括
Swashbuckle.AspNetCore.Swagger
、
Swashbuckle.AspNetCore.SwaggerGen
、
Swashbuckle.AspNetCore.SwaggerUI
这三大组件
Swashbuckle.AspNetCore.Swagger
:将
SwaggerDocument
对象公开为JSON终结点的Swagger对象模型和中间件。
Swashbuckle.AspNetCore.SwaggerGen
:从路由、控制器和模型直接生成
SwaggerDocument
对象的Swagger生成器。它通常与Swagger终结点中间件结合,以自动公开SwaggerJSON。
Swashbuckle.AspNetCore.SwaggerUI
:SwaggerUI工具的嵌入式版本。它解释SwaggerJSON以构建描述WebAPI功能的可自定义的丰富体验。它包括针对公共方法的内置测试工具。
通常来说,我们更倾向于在ASP.Net Core中引入
Swashbuckle.AspNetCore
这个聚合Nuget包,它将包括上诉三个组件。
通过Swagger来呈现路由方案
引入依赖包
https://www.nuget.org/packages/Swashbuckle.AspNetCore
dotnet add package Swashbuckle.AspNetCore
设置项目生成XML注释文件
进入项目属性设置,勾选文档文件
的选项,让其可以生成包含API文档的文件,最终它会生成一个和项目同名的xml文件。
添加SwaggerGen服务
在Startup.cs
的ConfigureServices
方法中,添加并配置Swagger相关的服务
public void ConfigureServices(IServiceCollection services)
services.AddControllers();
services.AddSwaggerGen(swaggerGenOptions =>
swaggerGenOptions.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "Tesla API", Version = "v1"});
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
swaggerGenOptions.IncludeXmlComments(xmlPath);
这里通过AddSwaggerGen
的方法将Swagger
注册进来,同时通过SwaggerGenOptions
委托入参来传入一些必要信息。
这里我们看到IncludeXmlComments
方法把当前程序集的XML包括进来了,这将帮助我们启用XML注释。
/// <summary>
/// 订单是否存在
/// </summary>
/// <param name="id">必须可以转为Long</param>
/// <returns></returns>
[HttpGet("{id:IsLong}")]
public bool OrderExist([FromRoute]object id)
return true;
当我们存在这种注释时,它将在SwaggerUI中展示出来。
这里甚至我们还可以通过remarks
标记来添加示例信息
/// <summary>
/// 订单是否存在
/// </summary>
/// <remarks>
/// 请求示例:
/// GET /api/order/OrderExist/123
/// </remarks>
/// <param name="id">必须可以转为Long</param>
/// <returns></returns>
[HttpGet("{id:IsLong}")]
public bool OrderExist([FromRoute]object id)
return true;
我们来看下AddSwaggerGen
的定义
public static class SwaggerGenServiceCollectionExtensions
public static IServiceCollection AddSwaggerGen(
this IServiceCollection services,
Action<SwaggerGenOptions> setupAction = null)
// Add Mvc convention to ensure ApiExplorer is enabled for all actions
services.Configure<MvcOptions>(c =>
c.Conventions.Add(new SwaggerApplicationConvention()));
// Register custom configurators that takes values from SwaggerGenOptions (i.e. high level config)
// and applies them to SwaggerGeneratorOptions and SchemaGeneratorOptoins (i.e. lower-level config)
services.AddTransient<IConfigureOptions<SwaggerGeneratorOptions>, ConfigureSwaggerGeneratorOptions>();
services.AddTransient<IConfigureOptions<SchemaGeneratorOptions>, ConfigureSchemaGeneratorOptions>();
// Register generator and it's dependencies
services.TryAddTransient<ISwaggerProvider, SwaggerGenerator>();
services.TryAddTransient<IAsyncSwaggerProvider, SwaggerGenerator>();
services.TryAddTransient(s => s.GetRequiredService<IOptions<SwaggerGeneratorOptions>>().Value);
services.TryAddTransient<ISchemaGenerator, SchemaGenerator>();
services.TryAddTransient(s => s.GetRequiredService<IOptions<SchemaGeneratorOptions>>().Value);
services.TryAddTransient<ISerializerDataContractResolver>(s =>
#if (!NETSTANDARD2_0)
var serializerOptions = s.GetService<IOptions<JsonOptions>>()?.Value?.JsonSerializerOptions
?? new JsonSerializerOptions();
#else
var serializerOptions = new JsonSerializerOptions();
#endif
return new JsonSerializerDataContractResolver(serializerOptions);
// Used by the <c>dotnet-getdocument</c> tool from the Microsoft.Extensions.ApiDescription.Server package.
services.TryAddSingleton<IDocumentProvider, DocumentProvider>();
if (setupAction != null) services.ConfigureSwaggerGen(setupAction);
return services;
public static void ConfigureSwaggerGen(
this IServiceCollection services,
Action<SwaggerGenOptions> setupAction)
services.Configure(setupAction);
再来看下SwaggerGenOptions
的定义
public class SwaggerGenOptions
public SwaggerGeneratorOptions SwaggerGeneratorOptions { get; set; } = new SwaggerGeneratorOptions();
public SchemaGeneratorOptions SchemaGeneratorOptions { get; set; } = new SchemaGeneratorOptions();
// NOTE: Filter instances can be added directly to the options exposed above OR they can be specified in
// the following lists. In the latter case, they will be instantiated and added when options are injected
// into their target services. This "deferred instantiation" allows the filters to be created from the
// DI container, thus supporting contructor injection of services within filters.
public List<FilterDescriptor> ParameterFilterDescriptors { get; set; } = new List<FilterDescriptor>();
public List<FilterDescriptor> RequestBodyFilterDescriptors { get; set; } = new List<FilterDescriptor>();
public List<FilterDescriptor> OperationFilterDescriptors { get; set; } = new List<FilterDescriptor>();
public List<FilterDescriptor> DocumentFilterDescriptors { get; set; } = new List<FilterDescriptor>();
public List<FilterDescriptor> SchemaFilterDescriptors { get; set; } = new List<FilterDescriptor>();
public class FilterDescriptor
public Type Type { get; set; }
public object[] Arguments { get; set; }
看看SwaggerDoc
的定义
public static void SwaggerDoc(
this SwaggerGenOptions swaggerGenOptions,
string name,
OpenApiInfo info)
swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs.Add(name, info);
看下IncludeXmlComments
的定义
public static void IncludeXmlComments(
this SwaggerGenOptions swaggerGenOptions,
string filePath,
bool includeControllerXmlComments = false)
swaggerGenOptions.IncludeXmlComments(() => new XPathDocument(filePath), includeControllerXmlComments);
启用Swagger、SwaggerUI中间件
接下来还需要在Configure
方法中启用Swagger
、SwaggerUI
这两个中间件。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(swaggerUIOptions =>
swaggerUIOptions.SwaggerEndpoint("/swagger/v1/swagger.json", "Tesla API v1");
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
endpoints.MapControllers();
也可以写成
app.UseSwaggerUI(swaggerUIOptions =>
swaggerUIOptions.SwaggerEndpoint("v1/swagger.json", "Tesla API v1");
其中UseSwagger
中间件的作用是将生成的Swagger作为一个JSON端点提供服务,而UseSwaggerUI
中间件的作用是服务Swagger-ui(HTML、JS、CSS等)。
注意UseSwaggerUI
方法调用启用了静态文件中间件。如果以.NET Framework或.NET Core 1.x为目标,请将Microsoft.AspNetCore.StaticFiles
NuGet包添加到项目。
如果要让这个机制生效,模板自带的UseEndpoints
及endpoints.MapControllers()
也是必不可少的。
看下UseSwagger
的定义
public static IApplicationBuilder UseSwagger(
this IApplicationBuilder app,
Action<SwaggerOptions> setupAction = null)
SwaggerOptions options;
using (var scope = app.ApplicationServices.CreateScope())
options = scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<SwaggerOptions>>().Value;
setupAction?.Invoke(options);
return app.UseSwagger(options);
看下UseSwaggerUI
的定义
public static IApplicationBuilder UseSwaggerUI(
this IApplicationBuilder app,
Action<SwaggerUIOptions> setupAction = null)
SwaggerUIOptions options;
using (var scope = app.ApplicationServices.CreateScope())
options = scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<SwaggerUIOptions>>().Value;
setupAction?.Invoke(options);
// To simplify the common case, use a default that will work with the SwaggerMiddleware defaults
if (options.ConfigObject.Urls == null)
var hostingEnv = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
options.ConfigObject.Urls = new[] { new UrlDescriptor { Name = $"{hostingEnv.ApplicationName} v1", Url = "v1/swagger.json" } };
return app.UseSwaggerUI(options);
看下SwaggerUIOptions
的定义
public class SwaggerUIOptions
/// <summary>
/// Gets or sets a route prefix for accessing the swagger-ui
/// </summary>
public string RoutePrefix { get; set; } = "swagger";
/// <summary>
/// Gets or sets a Stream function for retrieving the swagger-ui page
/// </summary>
public Func<Stream> IndexStream { get; set; } = () => typeof(SwaggerUIOptions).GetTypeInfo().Assembly
.GetManifestResourceStream("Swashbuckle.AspNetCore.SwaggerUI.index.html");
/// <summary>
/// Gets or sets a title for the swagger-ui page
/// </summary>
public string DocumentTitle { get; set; } = "Swagger UI";
/// <summary>
/// Gets or sets additional content to place in the head of the swagger-ui page
/// </summary>
public string HeadContent { get; set; } = "";
/// <summary>
/// Gets the JavaScript config object, represented as JSON, that will be passed to the SwaggerUI
/// </summary>
public ConfigObject ConfigObject { get; set; } = new ConfigObject();
/// <summary>
/// Gets the JavaScript config object, represented as JSON, that will be passed to the initOAuth method
/// </summary>
public OAuthConfigObject OAuthConfigObject { get; set; } = new OAuthConfigObject();
/// <summary>
/// Gets the interceptor functions that define client-side request/response interceptors
/// </summary>
public InterceptorFunctions Interceptors { get; set; } = new InterceptorFunctions();
在根目录提供Swagger
如果想要直接在根目录提供Swagger UI,我们在UseSwaggerUI
那里需要把RoutePrefix
的值设置为空,因为它是有默认值swagger
的。
app.UseSwaggerUI(swaggerUIOptions =>
swaggerUIOptions.SwaggerEndpoint("/swagger/v1/swagger.json", "Tesla API v1");
swaggerUIOptions.RoutePrefix = string.Empty;
看下SwaggerEndpoint
的定义
public static void SwaggerEndpoint(this SwaggerUIOptions options, string url, string name)
var urls = new List<UrlDescriptor>(options.ConfigObject.Urls ?? Enumerable.Empty<UrlDescriptor>());
urls.Add(new UrlDescriptor { Url = url, Name = name });
options.ConfigObject.Urls = urls;
Swashbuckle
依赖于MVC的Microsoft.AspNetCore.Mvc.ApiExplorer
来发现路由和终结点。如果项目调用AddMvc
,则自动发现路由和终结点。调用AddMvcCore
时,必须显式调用AddApiExplorer
方法。
降低到2.0规范
Swashbuckle
默认会启动3.0的OpenAPI规范,如果因为对一些应用的兼容需要,比如Microsoft Power Apps和Microsoft Flow,可通过配置降级为2.0的标准。
app.UseSwagger(swaggerOptions =>
swaggerOptions.SerializeAsV2 = true;
访问Swagger
这时候,其实我们可以修改launchSettings.json
中当前启动配置文件中的launchUrl
的值为swagger
"demoForRouting31": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
这时候默认启动就会打开swagger
的地址:https://localhost:5001/swagger/index.html
同时需要注意的是,还有个前面定义的文档地址:https://localhost:5001/swagger/v1/swagger.json ,它是符合OpenAPI 规范的。
添加API信息和说明
在Startup.cs
的AddSwaggerGen
服务注册时,我们可以在入参委托中基于Microsoft.OpenApi.Models.OpenApiInfo
类定义一些API的信息和说明
services.AddSwaggerGen(swaggerGenOptions =>
swaggerGenOptions.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
Version = "v1",
Title = "Tesla API",
Description = "这是一个示例演示",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
Name = "Taylor Shi",
Email = string.Empty,
Url = new Uri("https://www.cnblogs.com/taylorshi"),
License = new OpenApiLicense
Name = "Use under LICX",
Url = new Uri("https://example.com/license"),
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
swaggerGenOptions.IncludeXmlComments(xmlPath);
基于RouteAttribute方法来标注路由
新建一个OrderController
控制器。
/// <summary>
/// 订单控制器
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class OrderController : ControllerBase
/// <summary>
/// 订单是否存在
/// </summary>
/// <param name="id">必须可以转为Long</param>
/// <returns></returns>
[HttpGet("{id:TeslaRouteConsraint}")]
public bool OrderExist(object id)
return true;
/// <summary>
/// 订单最大值
/// </summary>
/// <param name="id">最大值20</param>
/// <param name="linkGenerator"></param>
/// <returns></returns>
[HttpGet("{id:max(20)}")]
public bool OrderMax(long id, [FromServices] LinkGenerator linkGenerator)
var a = linkGenerator.GetPathByAction("Reque", "Order");
return true;
/// <summary>
/// 订单请求
/// </summary>
/// <returns></returns>
[HttpGet("{name:required}")]
public bool OrderRequest(string name)
return true;
/// <summary>
/// 订单编号
/// </summary>
/// <param name="number">必须是三个数字</param>
/// <returns></returns>
[HttpGet("{number:regex(^\\d{{3}}$)}")]
public bool OrderNumber(string number)
return true;
在OrderController
上方标注了ApiController
和Route
,同时为了让它可以生效,我们需要在Startup.cs
的Configure
方法的UseEndpoints
中间件这里调用MapControllers
。
app.UseEndpoints(endpoints =>
// 启用RouteAttribute
endpoints.MapControllers();
针对OrderExist
这个我们使用了自定义的参数
/// <summary>
/// 订单是否存在
/// </summary>
/// <param name="id">必须可以转为Long</param>
/// <returns></returns>
[HttpGet("{id:TeslaRouteConsraint}")]
public bool OrderExist(object id)
return true;
针对OrderMax
使用了max
约束,限制其id
的值最大不能超过20
/// <summary>
/// 订单最大值
/// </summary>
/// <param name="id">最大值20</param>
/// <param name="linkGenerator"></param>
/// <returns></returns>
[HttpGet("{id:max(20)}")]
public bool OrderMax(long id, [FromServices]LinkGenerator linkGenerator)
var a = linkGenerator.GetPathByAction("Reque", "Order");
return true;
针对OrderRequest
这里我们要求name
是必填
/// <summary>
/// 订单请求
/// </summary>
/// <returns></returns>
[HttpGet("{name:required}")]
public bool OrderRequest(string name)
return true;
针对OrderNumber
这里我们通过正则表达式约束要求number
必须是三个数字
/// <summary>
/// 订单编号
/// </summary>
/// <param name="number">必须是三个数字</param>
/// <returns></returns>
[HttpGet("{number:regex(^\\d{{3}}$)}")]
public bool OrderNumber(string number)
return true;
当约束条件不符合的时候,得到的HTTP响应是404
通过数据标记来驱动Swagger
当我们定义一个数据模型的时候,我们可以基于System.ComponentModel.DataAnnotations
下的可用属性来标记模型
/// <summary>
/// 订单项
/// </summary>
public class OrderItem
/// <summary>
/// 订单ID
/// </summary>
public long Id { get; set; }
/// <summary>
/// 订单名称
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// 是否完结
/// </summary>
[DefaultValue(false)]
public bool IsComplete { get; set; }
当我们使用的时候
/// <summary>
/// 获取订单项
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id:IsLong}")]
public OrderItem GetOrderItem(long id)
return new OrderItem { Id = id };
会看到针对模型的一些Swagger提示
回顾下System.ComponentModel.DataAnnotations
有哪些可用的清单
[Route("api/[controller]/[action]")]
[ApiController]
public class OrderController : ControllerBase
描述响应代码
使用ProducesResponseType
可以给Action标记响应代码,同时通过XML描述结构中的<response code="201">
可以添加针对不同响应代码的描述。
/// <summary>
/// 订单是否存在
/// </summary>
/// <remarks>
/// 请求示例:
/// GET /api/order/OrderExist/123
/// </remarks>
/// <param name="id">必须可以转为Long</param>
/// <returns></returns>
/// <response code="201">Returns the newly created item</response>
/// <response code="400">If the item is null</response>
[HttpGet("{id:IsLong}")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public bool OrderExist([FromRoute]object id)
return true;
定制Swagger的界面样式
通过启用静态文件中间件和wwwroot
文件夹,我们可以把我们的css样式文件追加进来。
public void Configure(IApplicationBuilder app)
app.UseStaticFiles();
app.UseSwaggerUI(c =>
c.InjectStylesheet("/swagger-ui/custom.css");
自定义约束(RouteConsraint)
继承自IRouteConstraint
来自定义我们的路由约束类TeslaRouteConsraint
/// <summary>
/// 自定义路由约束
/// </summary>
public class TeslaRouteConsraint : IRouteConstraint
/// <summary>
/// 是否匹配
/// </summary>
/// <param name="httpContext"></param>
/// <param name="route"></param>
/// <param name="routeKey"></param>
/// <param name="values"></param>
/// <param name="routeDirection"></param>
/// <returns></returns>
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
if(RouteDirection.IncomingRequest == routeDirection)
var value = values[routeKey];
if(long.TryParse(value.ToString(), out var longValue))
return true;
return false;
继承IRouteConstraint
接口,它只有一个Match
方法,它有几个入参:
httpContext,HTTP请求的上下文
routeKey,路由参数
values,路由参数值的字典,通过它可以获取到对应路由参数的值
routeDirection,路由场景,区分验证的使用场景
这里的逻辑是,在路由场景为进入请求的场景时,拿到当前输入参数的值,判断是否可以转成Long。
这里来看下RouteDirection
的定义
public enum RouteDirection
// 摘要:
// A URL from a client is being processed.
IncomingRequest = 0,
// 摘要:
// A URL is being created based on the route definition.
UrlGeneration = 1
其中IncomingRequest
代表当前验证时用来验证路由是否匹配、UrlGeneration
代表验证URL生成。
定义好路由约束类TeslaRouteConsraint
之后,我们还需要去Startip.cs
的ConfigureServices
中添加AddRouting
服务,并且将自定义的路由约束添加进来。
services.AddRouting(routeOptions =>
routeOptions.ConstraintMap.Add("TeslaRouteConsraint", typeof(TeslaRouteConsraint));
这时候再回到OrderController
稍加改造,在路由参数前面增加[FromRoute]
标记,然后id
后面对应的约束填写前面定义的名称TeslaRouteConsraint
。
/// <summary>
/// 订单是否存在
/// </summary>
/// <param name="id">必须可以转为Long</param>
/// <returns></returns>
[HttpGet("{id:TeslaRouteConsraint}")]
public bool OrderExist([FromRoute]object id)
return true;
当然,根据实际作用,我们可以给自定义约束取更符合其功能的名称。
services.AddRouting(routeOptions =>
routeOptions.ConstraintMap.Add("IsLong", typeof(TeslaRouteConsraint));
/// <summary>
/// 订单是否存在
/// </summary>
/// <param name="id">必须可以转为Long</param>
/// <returns></returns>
[HttpGet("{id:IsLong}")]
public bool OrderExist([FromRoute]object id)
return true;
效果也是一样的。
链接生成(LinkGenerator)
通过容器可以获得LinkGenerator
对象,使用GetPathByAction
方法可获取指定动作的请求路径,使用GetUriByAction
方法可获取指定动作的完整请求地址。
/// <summary>
/// 订单最大值
/// </summary>
/// <param name="id">最大值20</param>
/// <param name="linkGenerator"></param>
/// <returns></returns>
[HttpGet("{id:max(20)}")]
public bool OrderMax(long id, [FromServices]LinkGenerator linkGenerator)
// 获取请求路径
var actionPath = linkGenerator.GetPathByAction(HttpContext,
action: "OrderRequest",
controller: "Order",
values: new { name = "abc" });
Console.WriteLine($"ActionPath: {actionPath}");
// 获取完整URL
var actionUri = linkGenerator.GetUriByAction(HttpContext,
action: "OrderRequest",
controller: "Order",
values: new { name = "abc" });
Console.WriteLine($"ActionUrl: {actionUri}");
return true;
/// <summary>
/// 订单请求
/// </summary>
/// <returns></returns>
[HttpGet("{name:required}")]
public bool OrderRequest(string name)
return true;
运行下看下结果
ActionPath: /api/Order/OrderRequest/abc
ActionUrl: https://localhost:5001/api/Order/OrderRequest/abc
查看下GetPathByAction
的定义,可以看到有其他重载可以使用
public static class ControllerLinkGeneratorExtensions
public static string GetPathByAction(this LinkGenerator generator, HttpContext httpContext, string action = null, string controller = null, object values = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null);
public static string GetPathByAction(this LinkGenerator generator, string action, string controller, object values = null, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null);
public static string GetUriByAction(this LinkGenerator generator, HttpContext httpContext, string action = null, string controller = null, object values = null, string scheme = null, HostString? host = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null);
public static string GetUriByAction(this LinkGenerator generator, string action, string controller, object values, string scheme, HostString host, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null);
查看下GetUriByAction
的定义,可以看到有其他重载可以使用
public static class ControllerLinkGeneratorExtensions
public static string GetPathByAction(this LinkGenerator generator, HttpContext httpContext, string action = null, string controller = null, object values = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null);
public static string GetPathByAction(this LinkGenerator generator, string action, string controller, object values = null, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null);
public static string GetUriByAction(this LinkGenerator generator, HttpContext httpContext, string action = null, string controller = null, object values = null, string scheme = null, HostString? host = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null);
public static string GetUriByAction(this LinkGenerator generator, string action, string controller, object values, string scheme, HostString host, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null);
标记接口为废弃状态
通过[Obsolete]
标记,我们可以将一个接口标记为已废弃状态,但是它还可以正常工作。
/// <summary>
/// 订单请求
/// </summary>
/// <returns></returns>
[HttpGet("{name:required}")]
[Obsolete]
public bool OrderRequest(string name)
return true;
Web API定义
Restful是可选的
约定好API的表达契约
将API约束在特定目录下,如/api/
使用ObsoleteAttribute
可标记即将废弃的API
废弃API的时候应该是采用间隔版本的方式废弃,先将即将废弃的API标记为已废弃,但是它还是可以工作,间隔几个版本以后我们再去将代码删除掉。
基于Swagger进行API版本控制
我们只需要把版本那里进行动态化配置改造即可,然后在Controller的Action通过ApiExplorerSettings
完成分组设置。
自定义版本控制枚举ApiVersion
/// <summary>
/// API版本
/// </summary>
public enum ApiVersion
/// <summary>
/// v1版本
/// </summary>
V1 = 1,
/// <summary>
/// v2版本
/// </summary>
V2 = 2,
/// <summary>
/// v3版本
/// </summary>
V3 = 3,
改造服务注入,遍历ApiVersion
来添加Swagger Doc信息
public void ConfigureServices(IServiceCollection services)
services.AddControllers();
services.AddSwaggerGen(swaggerGenOptions =>
typeof(ApiVersion).GetEnumNames().ToList().ForEach(version =>
swaggerGenOptions.SwaggerDoc(version, new Microsoft.OpenApi.Models.OpenApiInfo
Title = "Tesla Open API",
Version = version,
Description = $"Tesla Open API {version} Powered by ASP.NET Core"
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
swaggerGenOptions.IncludeXmlComments(xmlPath);
改造中间件注册,遍历ApiVersion
来生成Swagger Endpoint的API文档
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(swaggerUIOptions =>
typeof(ApiVersion).GetEnumNames().ToList().ForEach(version =>
swaggerUIOptions.SwaggerEndpoint($"/swagger/{version}/swagger.json", version);
改造Controller,添加分组标记实现API分组
/// <summary>
/// 订单控制器
/// </summary>
[Produces("application/json")]
[Route("api/[controller]/[action]")]
[ApiController]
public class OrderController : ControllerBase
/// <summary>
/// 订单是否存在
/// </summary>
/// <remarks>
/// 请求示例:
/// GET /api/order/OrderExist/123
/// </remarks>
/// <param name="id">必须可以转为Long</param>
/// <returns></returns>
/// <response code="201">Returns the newly created item</response>
/// <response code="400">If the item is null</response>
[HttpGet("{id:IsLong}")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ApiExplorerSettings(GroupName = nameof(ApiVersion.V1))]
public bool OrderExist([FromRoute]object id)
return true;
/// <summary>
/// 订单最大值
/// </summary>
/// <param name="id">最大值20</param>
/// <param name="linkGenerator"></param>
/// <returns></returns>
[HttpGet("{id:max(20)}")]
[ApiExplorerSettings(GroupName = nameof(ApiVersion.V2))]
public bool OrderMax(long id, [FromServices]LinkGenerator linkGenerator)
// 获取请求路径
var actionPath = linkGenerator.GetPathByAction(HttpContext,
action: "OrderRequest",
controller: "Order",
values: new { name = "abc" });
Console.WriteLine($"ActionPath: {actionPath}");
// 获取完整URL
var actionUri = linkGenerator.GetUriByAction(HttpContext,
action: "OrderRequest",
controller: "Order",
values: new { name = "abc" });
Console.WriteLine($"ActionUrl: {actionUri}");
return true;
/// <summary>
/// 订单请求
/// </summary>
/// <returns></returns>
[HttpGet("{name:required}")]
[Obsolete]
[ApiExplorerSettings(GroupName = nameof(ApiVersion.V3))]
public bool OrderRequest(string name)
return true;
/// <summary>
/// 订单编号
/// </summary>
/// <param name="number">必须是三个数字</param>
/// <returns></returns>
[HttpGet("{number:regex(^\\d{{3}}$)}")]
[ApiExplorerSettings(GroupName = nameof(ApiVersion.V1))]
public bool OrderNumber(string number)
return true;
/// <summary>
/// 获取订单项
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id:IsLong}")]
[ApiExplorerSettings(GroupName = nameof(ApiVersion.V3))]
public OrderItem GetOrderItem(long id)
return new OrderItem { Id = id };
使用ApiVersion进行API版本控制
https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
https://www.nuget.org/packages/Swashbuckle.AspNetCore
dotnet add package Swashbuckle.AspNetCore
dotnet add package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
自定义一个扩展方法RoutingEndpointExtensions
/// <summary>
/// 路由和终结点扩展
/// </summary>
public static class RoutingEndpointExtensions
/// <summary>
/// 添加和配置API版本相关服务
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddApiVersions(this IServiceCollection services)
// 添加API版本控制
services.AddApiVersioning(apiVersioningOptions =>
// 返回响应标头中支持的版本信息
apiVersioningOptions.ReportApiVersions = true;
// 此选项将用于不提供版本的请求,指向默认版本
apiVersioningOptions.AssumeDefaultVersionWhenUnspecified = true;
// 默认版本号,支持时间或数字版本号
apiVersioningOptions.DefaultApiVersion = new ApiVersion(1, 0);
// 添加版本管理服务
services.AddVersionedApiExplorer(apiExplorerOptions =>
// 设置API组名格式
apiExplorerOptions.GroupNameFormat = "'v'VVV";
// 在URL中替换版本
apiExplorerOptions.SubstituteApiVersionInUrl = true;
// 当未设置版本时指向默认版本
apiExplorerOptions.AssumeDefaultVersionWhenUnspecified = true;
return services;
/// <summary>
/// 添加和配置Swagger相关服务
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddSwaggers(this IServiceCollection services)
// AddApiVersions必须在AddSwaggers之前调用
var apiVersionDescriptionProvider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
// 添加SwaggerGen服务
services.AddSwaggerGen(swaggerGenOptions =>
// 根据请求方式排序
swaggerGenOptions.OrderActionsBy(o => o.HttpMethod);
// 遍历已知的API版本
apiVersionDescriptionProvider.ApiVersionDescriptions.ToList().ForEach(versionDescription =>
var group = versionDescription.GroupName.ToString();
swaggerGenOptions.SwaggerDoc(group, new Microsoft.OpenApi.Models.OpenApiInfo
Title = "Tesla Open API",
Version = group,
Description = $"Tesla Open API {group} Powered by ASP.NET Core"
// 重载方式
swaggerGenOptions.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
// 遍历已存在的XML
foreach (var name in Directory.GetFiles(AppContext.BaseDirectory, "*.*",
SearchOption.AllDirectories).Where(f => Path.GetExtension(f).ToLower() == ".xml"))
swaggerGenOptions.IncludeXmlComments(name, includeControllerXmlComments: true);
return services;
/// <summary>
/// 使用和配置Swagger、ApiVersion相关中间件
/// </summary>
/// <param name="app"></param>
/// <param name="provider"></param>
/// <returns></returns>
public static IApplicationBuilder UseSwaggerAndApiVersions(this IApplicationBuilder app, IApiVersionDescriptionProvider provider)
app.UseApiVersioning();
app.UseSwagger();
app.UseSwaggerUI(swaggerUIOptions =>
provider.ApiVersionDescriptions.ToList().ForEach(versionDescription =>
var group = versionDescription.GroupName.ToString();
swaggerUIOptions.SwaggerEndpoint($"/swagger/{group}/swagger.json", group);
return app;
在Startup.cs
的Configure
添加扩展方法
public void ConfigureServices(IServiceCollection services)
services.AddControllers();
services.AddApiVersions();
services.AddSwaggers();
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseSwaggerAndApiVersions(provider);
对Controller的分组
[ApiVersion("1.0", Deprecated = true)]
[Route("api/v{version:ApiVersion}/[controller]/[action]")]
[ApiController]
public class OrderController : ControllerBase
[ApiVersion("2.0")]
[Route("api/v{version:ApiVersion}/[controller]/[action]")]
[ApiController]
public class OrderController : ControllerBase
ASP.NET Core中的路由
Asp.Net Core EndPoint 终结点路由工作原理解读
ASP.NET Core路由中间件[1]: 终结点与URL的映射
Asp.Net Core EndPoint 终结点路由工作原理解读
Endpoint Routing in ASP.NET Core 2.2 Explained
domaindrivendev/Swashbuckle.AspNetCore
带有Swagger/OpenAPI的ASP.NET Core Web API文档
OpenAPITools/openapi-generator
Swashbuckle和ASP.NET Core入门
NSwag和ASP.NET Core入门
.NET OpenAPI工具命令参考和安装
Swashbuckle, ApiExplorer, and Routing
System.ComponentModel.DataAnnotations命名空间
在ASP.Net Core Web API中使用Swagger进行版本控制
.Net Core WebApi —— Swagger版本控制
利用swagger和API Version实现api版本控制