25 | 路由与终结点:如何规划好你的Web API

路由注册方式

路由的核心作用就是:URL和应用程序Controller的对应关系的一种映射
映射关系实际上有两种:

  1. 把URL映射到我们对应的Controller的action上面去
  2. 根据Controller和action的名字来生产URL

.NET Core 提供了两种路由注册的方式:

  • 路由模板的方式
  • RouteAttribute方式

这两种方式分别适用于的场景是不一样的
路由模板的方式是之前传统的方式,可以用来作为 MVC 的页面 Web 配置
现在用的比较多的前后端分离的架构,定义 Web API 的时候使用 RouteAttribute 方式去做
在定义路由,注册路由的过程中间,有一个重要的特性就是路由约束,是指路由如何匹配

路由约束

在定义路由,注册路由的过程中间,有一个重要的特性就是路由约束,是指路由如何匹配

  • 类型约束
  • 范围约束
  • 正则表达式
  • 是否必选
  • 自定义IRouteConstraint

URL生成

另外路由系统提供了两个关键的类,用来反向根据路由的信息生产 URL地址

  • LinkGenerator
    • LinkGenerator 是全新提供的一个链接生成的对象,可以从容器里面,在任意的位置都可以获取到这个对象,然后根据需要生成 URL 地址
  • IUrlHelper
    • IUrlHelper 与 MVC 框架里面的 MVCHelper 很像

示例

新建Web程序👉选择API模板
为了方便演示,这里先注册了一组 Swagger 的代码,将Web API通过 Swagger 的可视化界面输出出来
引入 Swagger 对应 ASP.NET Core 的包
image.png
将代码文档 XML 文档注入给 Swagger

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    c.IncludeXmlComments(xmlPath);
});

在中间件里面注册Swagger

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

这样子就可以在界面上看到 Swagger 的界面,并且浏览我们定义的 API

Controllers文件夹👉新建OrderController

using Microsoft.AspNetCore.Mvc;

namespace RoutingDemo.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class OrderController : ControllerBase
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id">必须可以转为long</param>
        /// <returns></returns>
        [HttpGet("{id:MyRouteConstraint}")]// 这里使用了自定义的约束
        public bool OrderExist(object id)
        {
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id">最大20</param>
        /// <returns></returns>
        [HttpGet("{id:max(20)}")]// 这里使用了 Max 的约束
        public bool Max(long id)
        {
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ss">必填</param>
        /// <returns></returns>
        [HttpGet("{name:required}")]// 必填约束
        public bool Reque(string name)
        {
            return true;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="number">以三个数字开始</param>
        /// <returns></returns>
        [HttpGet("{number:regex(^\\d{{3}}$)}")]// 正则表达式约束
        public bool Number(string number)
        {
            return true;
        }
    }
}

上面用到了自定义约束 MyRouteConstraint

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;

namespace RoutingDemo.Constraints
{
    public class MyRouteConstraint : IRouteConstraint
    {
        public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {
            if (RouteDirection.IncomingRequest == routeDirection)
            {
                var v = values[routeKey];
                if (long.TryParse(v.ToString(), out var value))
                {
                    return true;
                }
            }
            return false;
        }
    }
}

注册 MyRouteConstraint

services.AddRouting(options =>
{
    options.ConstraintMap.Add("MyRouteConstraint", typeof(MyRouteConstraint));
});

让它生效之前,需要在中间件注册的位置注入 UseEndpoints,然后对 UseEndpoints使用 MapControllers

app.UseEndpoints(endpoints =>
{
    // 使用RouteAttribute
    endpoints.MapControllers();
});

通过这样子的方式把 OrderController 的路由注入进来

image.png
第一个接口是我们实现的自定义约束,点击 try it out 后输入参数

第二个接口约束最大为20
输入5,执行

可以看到响应码是 200
输入25,执行

可以看到响应码是 404,也就说路由匹配失败了
第三个接口因为参数是必须的,所以没办法输入空值,有一个前端的验证
第四个接口以三个数字开始,输入 234,符合正则表达式,响应码 200

自定义约束实现了路由约束接口,它只有一个 Match 方法,这个方法传入了 Http 当前的 httpContextrouterouteKey
这个 routeKey 就是我们要验证的 key
后面两个参数 RouteValueDictionary 就是当前可以获取到的这个 routeKey 对应的传入的值是什么值,这样就可以验证我们传入的信息
routeDirection 这个枚举的作用是当前验证是用来验证 URL 请求进来,验证是否路由匹配,还是用来生成 URL,是进还是出的这样一个定义,在不同的场景下面可能响应的逻辑是不一样的
下面的逻辑是如果路由是进来的,也就是通过 URL 配置 action 的情况,就做一个判断,根据 routeKey 取到当前输入的这个值,然后判断它是否可以转成 long,这个其实模拟了类型验证,比如说 long 型验证的方式

可以给我们的约束起一个名字 isLong,这个名字就是用来 Attribute 上面标识约束的

services.AddRouting(options =>
{
    //options.ConstraintMap.Add("MyRouteConstraint", typeof(MyRouteConstraint));
    options.ConstraintMap.Add("isLong", typeof(MyRouteConstraint));
});

OrderController 里面也修改为 isLong

/// <summary>
/// 
/// </summary>
/// <param name="id">必须可以转为long</param>
/// <returns></returns>
//[HttpGet("{id:MyRouteConstraint}")]// 这里使用了自定义的约束
[HttpGet("{id:isLong}")]
//public bool OrderExist(object id)
public bool OrderExist([FromRoute] string id)
{
    return true;
}

启动程序,输入34,返回响应码200,输入abc,返回响应码404,也就是自定义约束生效了
接下来讲一下链接生成的过程

/// <summary>
/// 
/// </summary>
/// <param name="id">最大20</param>
/// <param name="linkGenerator"></param>
/// <returns></returns>
[HttpGet("{id:max(20)}")]// 这里使用了 Max 的约束
//public bool Max(long id)
public bool Max([FromRoute]long id, [FromServices]LinkGenerator linkGenerator)
{
    // 这两行就是分别获取完整 Uri 和 path 的代码
    // 它还有不同的重载,可以根据需要传入不同的路由的值
    var path = linkGenerator.GetPathByAction(HttpContext,
        action: "Reque",
        controller: "Order",
        values: new { name = "abc" });// 因为下面对 name 有一个必填的约束,所以这里需要传值

    var uri = linkGenerator.GetUriByAction(HttpContext,
        action: "Reque",
        controller: "Order",
        values: new { name = "abc" });
    return true;
}

/// <summary>
/// 
/// </summary>
/// <param name="ss">必填</param>
/// <returns></returns>
[HttpGet("{name:required}")]// 必填约束
public bool Reque(string name)
{
    return true;
}

启动程序,端点调试,输入1,点击执行,可以看到
path 的值为/api/``Order``/Reque/``abc
uri 的值为https://localhost:5001/api/Order/Reque/abc
在定义 Controller 的时候,实际上还会做一些接口废弃的过程,通过 [Obsolete]

/// <summary>
/// 
/// </summary>
/// <param name="ss">必填</param>
/// <returns></returns>
[HttpGet("{name:required}")]// 必填约束
[Obsolete]
public bool Reque(string name)
{
    return true;
}

我们不必直接删除我们的接口,它还可以正常工作,但是我们可以把它标记为已废弃,在 Swagger上面会有体现

可以看到这个接口已经被标记为废弃的,但是它的调用还是可以工作的

总结一下

  • Restful 不是必须的,只要约束好 Http 方法以及 URL 地址,还有 Http 响应码,响应的 Json 格式,这些约定只要适合团队的协作习惯就可以了,也就是说需要定义好 API 的表达契约
  • 建议是把 API 都约束在特定的目录下面,与其他功能性页面进行隔离,比如说 /api 加版本号这样子的方式
  • 在废弃 API 的过程中间,应该是间隔版本的方式废弃,也就是说先将即将废弃的 API 标记为已废弃,但是它还是可以工作,间隔几个版本之后将代码删除掉

到目前为止,讲解了依赖注入,配置日志,中间件等必要的内容,下一节开始将进入微服务实战的部分

推荐这些文章:

【.Net Core一个MVC简单项目开发历程】(2)-----实现基础的仓储+服务

实现基本通用的类
1.EntityFramework:
NuGet中添加Microsoft.EntityFrameworkCore的引用, 添加一个类继承 DbContext
2.IRepositories:
添加一个泛型类,并添加基础的CRUD接口,给Repository去实现:

点击查看代码
/// <summary>
/// 数据访问仓储基类接口
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IBas...

【代码记录】C# 在JObject对象中搜索特定Key的值

背景:如果一个复杂的json对象,如果只是想获取其中一个key的值,建立一个 "Model"类,进行反序列化操作太麻烦,慢慢分析json结构,再提取也很麻烦。 在这里记录一下 提供的一个比较方便的方法轻易获取。
代码参考:c# - Searching for a specific JToken by name in a JObject hierarchy - Stack Overflow
 

public static partial class Extensions
{

/// <summary>
/// JTo...

C#中判断数组或集合中是否含有属性值为value的对象

/// <summary>
/// 判断list中是否有某个对象的Id_srvplan为value
/// </summary>
/// <param name="list"></param>
/// <param name="value"></param>
/// <returns></returns>
private bool check(List<MedSrvPlanDO&...

c# 递归取子级数据

 

/// <summary>
/// 获取子企业Ids
/// </summary>
/// <param name="companyId"></param>
/// <returns></returns>
public IEnumerable<OrganizeEntity> GetCompanyId(string companyId)
{
var query ...

cnblogs 操作 metaweblog api

cnblog不愧是专业的程序员博客,还附带了metaweblog 功能。
这里简单记录一下,说不定以后用得上
metaweblog 是什么?

MetaWeblog API是Blog应用程序对外接口的国际规范标准。
通过MetaWeblog API,blog应用程序可以对外公布blog提供的服务,从而允许外面的程序能取得和设置blog文章的文本或属性。可使用该接口上传博文。

不多说了,操作几个接口试试
在哪看到自己的metaweblog地址
博客设置中,其他设置这里可以看到

点击去即可看到所支持的接口

注意接口是xmlrpc格式的传输方式
调用接口试试
xmlrpc的接口传输
这个是规...

想把域中的用户名和密码等信息取到,存如数据库,怎么办啊,没接触过。

问题

想把域中的用户名和密码等信息取到,存如数据库,怎么办啊,没接触过。要写C#的程序用。

最佳回答

#regionusing System;using System.Collections.Generic;using System.DirectoryServices;using System.Linq;#endregionnamespace Framework.Net.LDAP{ /// <summary> /// Class for helping with AD /// </summ...

判断校验数据是否满足相应的格式

/// <summary>
/// 判断对象是否为Int32类型的数字
/// </summary>
/// <param name="Expression"></param>
/// <returns></returns>
public static bool IsNumeric(string expression)
{
if (expression != null)
{...

3.0AuthenticationBuilder&CookieExtensions&JwtBearerExtensions【->IServiceCollection 】

 

using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// Used to config...

Maven <build>

1. <build>

1.1 <resources>

编译保留 *.propertie s,*.xml

1.2 <plugins>

拷贝config目录
Java 1.8 编译
一般打包(无Spring)
Spring 打包

2. <properties>

编码 编译版本

1. <build>
1.1 <resources>
编译保留 *.propertie s,*.xml
<resource>
<directory>src/main/java</dire...

纯js实现分页

原理:所有数据已加载好,js通过遍历部分显示,实现分页效果
html代码

<html>
<head>
<meta charset='utf-8'>
<script type="text/javascript" src="https://www.cnblogs.com/yanshushu/p/page.js"></script>

<style type="text/css">
#idData {color: red;border: solid;text-align: center;}
a{t...

文章标题:25 | 路由与终结点:如何规划好你的Web API
文章链接:https://www.dianjilingqu.com/50990.html
本文章来源于网络,版权归原作者所有,如果本站文章侵犯了您的权益,请联系我们删除,联系邮箱:saisai#email.cn,感谢支持理解。
THE END
< <上一篇
下一篇>>