25 | 路由与终结点:如何规划好你的Web API
自定义约束实现了路由约束接口,它只有一个 Match 方法,这个方法传入了 Http 当前的 httpContext,route,routeKey
这个 routeKey 就是我们要验证的 key 值
后面两个参数 RouteValueDictionary 就是当前可以获取到的这个 routeKey 对应的传入的值是什么值,这样就可以验证我们传入的信息
routeDirection 这个枚举的作用是当前验证是用来验证 URL 请求进来,验证是否路由匹配,还是用来生成 URL,是进还是出的这样一个定义,在不同的场景下面可能响应的逻辑是不一样的
下面的逻辑是如果路由是进来的,也就是通过 URL 配置 action 的情况,就做一个判断,根据 routeKey 取到当前输入的这个值,然后判断它是否可以转成 long,这个其实模拟了类型验证,比如说 long 型验证的方式
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;}}
}
RouteDirection
namespace Microsoft.AspNetCore.Routing
{public enum RouteDirection{IncomingRequest = 0,UrlGeneration = 1}
}
接下来看一下约束是如何注入到我们系统里生效的
可以给我们的约束起一个名字 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 上面会有体现
可以看到这个接口已经被标记为废弃的,但是它的调用还是可以工作的
总结一下
1、Restful 不是必须的,只要约束好 Http 方法以及 URL 地址,还有 Http 响应码,响应的 Json 格式,这些约定只要适合团队的协作习惯就可以了,也就是说需要定义好 API 的表达契约
2、建议是把 API 都约束在特定的目录下面,与其他功能性页面进行隔离,比如说 /api /api 加版本号这样子的方式
3、在废弃 API 的过程中间,应该是间隔版本的方式废弃,也就是说先将即将废弃的 API 标记为已废弃,但是它还是可以工作,间隔几个版本之后将代码删除掉
到目前为止,讲解了依赖注入,配置日志,中间件等必要的内容,下一节开始将进入微服务实战的部分