zoukankan      html  css  js  c++  java
  • ASP.NET Web API编程——路由

    路由过程大致分为三个阶段:

    1)请求URI匹配已存在路由模板

    2)选择控制器

    3)选择操作

    1匹配已存在的路由模板

    路由模板

    WebApiConfig.Register方法中定义路由,例如模板默认生成的路由为:

    config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );

    上面使用了public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults)方法来配置路由。相关参数为:

    name:路由名称。

    routeTemplate:路由模板,与URI相似。

    例如:

    api/{controller}/{id}、

    api/{controller}/{action}/{id}、api/{controller}/public/{category}/{id}

    defaults:路由值对象。可为占位符设置默认值。

    例如

    api/{controller}/public/{category}/{id}

    设置defaults: new { category = "all" }

    路由词典

    如果Web API匹配到一个已存在的路由模板,会创建一个路由词典,词典的键是模板中占位符的名称,值是占位符对应的值。如果路由值对象被指定为RouteParameter.Optional,那么这个值不会被放入词典中。路由词典会被存储到IHttpRouteData实例中。

     

    匹配示例

    对于api/{controller}/{id}

    首先匹配字符串api,然后匹配控制器(controller),第三匹配以HTTP方法开头的操作(Action),占位符id匹配Action接收的参数。

    对于api/{controller}/{action}/{id}

    首先匹配字符串api,然后匹配控制器(controller),最后匹配操作(Action),占位符id匹配Action接收的参数。

    对于api/root/{id}

    务必对defaults设置控制器(controller)的默认值,,不然无法执行路由过程。可以不设置操作(Action)。首先匹配api和root,然后匹配默认的控制器(controller),最后占位符id匹配操作(Action)接收的参数。若不设置操作(Action)那么匹配以HTTP方法开头的操作(Action)。

     

    2控制器的选择

    控制器(controller)的选择是由IHttpControllerSelector.SelectController完成的,IHttpControllerSelector接口默认实现是DefaultHttpControllerSelectorIHttpControllerSelector.SelectController方法获取HttpRequestMessage实例并返回HttpControllerDescriptor。

    DefaultHttpControllerSelector查找控制器(controller)的算法为:

    在路由词典中查找键为“controller”的值,找到键“controller”对应的值后,将字符串Controller拼接到这个值的后边,便可获得控制器(Controller)名。根据获得的控制器(Controller)名查找Web API中的控制器(controller)。如果没有查找到控制器(controller)名或者匹配到了多个,那么返回错误。DefaultHttpControllerSelector使用IHttpControllerTypeResolver来获得Web API控制器(controller)类型列表。IHttpControllerTypeResolver的默认实现返回具有如下特征的公有类:

    1)实现了IHttpController接口。

    2)不被abstract修饰。

    3)命名以“Controller”结尾。

     

    3匹配控制器操作

    IHttpActionSelector.SelectAction方法获取HttpControllerContext并返回HttpActionDescriptor,IHttpActionSelector接口的默认实现是ApiControllerActionSelector。ApiControllerActionSelector会查找请求的HTTP方法、路由模板中的{action}占位符、控制器操作的参数列表。

    Web API框架认为控制器(controller)的操作(Action)具有如下特征:

    1)公有类型的实例方法。

    2)继承自ApiController的方法

    3)非构造器,事件,操作符重载等特殊方法。

    Web API框架仅选择那些匹配请求的HTTP方法的操作,原则为:

    1)指定了相应特性的操作,例如使用HttpGet特性的操作,只能匹配Get请求。

    2)如果控制器(controller)操作以"Get", "Post", "Put", "Delete", "Head", "Options", or "Patch"开头,按照惯例控制器(controller)操作支持对应的HTTP请求。

    3)如果不满足以上两条,默认支持POST请求。

    ApiControllerActionSelector选择控制器(controller)操作的算法如下:

    1)创建一个链表,链表元素为所有与HTTP请求相匹配的操作(Action)。

    2)如果路由词典中包含关于操作(Action)的键值对,移除链表中名称和值不匹配的操作(Action)。

    3)匹配操作(Action)参数与URI。

    l 对于每一个操作(Action),获得简单类型的参数列表,参数绑定从URI获得操作(Action)参数,不包括可选的参数。

    在参数列表中,从路由表中或请求URI查询字符串中,为每一个参数名找到一个匹配,匹配是不区分大小写的,并且不依赖于参数顺序。

    l 选择一个操作(Action),其参数列表中的每一个参数在请求URI中都对应一个值。

    l 如果有多个操作(Action)满足以上规则,选择有最多参数匹配的一个操作(Action)。

    4)忽略被标记为[NonAction]的方法。

    补充说明:

    对于步骤3)一个参数可以从URI,请求消息体,或者自定义绑定中获得它的值。对于来自于URI的参数,要确保URI确实包含对应参数的值,这个值可能在路由词典中或查询字符串中。

    对于可选的参数,如果绑定不能从URI中获得参数的值,对于操作(Action)的选择也没有影响。

    对于复杂类型,只能通过自定义绑定来匹配URI中的参数值。操作(Action)选择算法的目的是在完成模型绑定之前选出操作(Action),因此操作(Action)选择算法对复杂类型无效。

    一旦操作(Action)被选出,模型绑定器才会被调用。

    4路由过程的扩展

    接口

    描述

    IHttpControllerSelector

    选择控制器

    IHttpControllerTypeResolver

    获得控制器(controller)类型列表,DefaultHttpControllerSelector会从这个列表中选择控制器(controller)类型。

    IAssembliesResolver

    获得项目程序集列表,IHttpControllerTypeResolver 会从这个列表中找到控制器(controller)类型

    IHttpControllerActivator

    创建新的控制器(controller)实例

    IHttpActionSelector

    选择操作(Action)

    IHttpActionInvoker

    调用操作(Action)

    要想使用自定义的上述接口实现,那么要注册服务。

    public static class WebApiConfig
    {
            public static void Register(HttpConfiguration config)
            {
                //其他配置
                config.Services.Replace(typeof(IHttpControllerSelector), new CustomHttpControllerSelector());
            }
    }

    例:扩展IHttpControllerSelector

    实现GetControllerMapping和SelectController方法,GetControllerMapping为发现系统所有可能的控制器(controller),SelectController会使用这些所有可能的控制器(controller),因此需要CustomHttpControllerSelector的属性存储所有可能的控制器(controller)。具体示例见“ASP.NET Web API编程——版本控制”

    public class CustomHttpControllerSelector : IHttpControllerSelector
    {
    
            public IDictionary<string, System.Web.Http.Controllers.HttpControllerDescriptor> GetControllerMapping()
            {
                throw new NotImplementedException();
            }
    
            public System.Web.Http.Controllers.HttpControllerDescriptor SelectController(HttpRequestMessage request)
            {
                throw new NotImplementedException();
            }
    }

    此外,有时扩展Web API框架的DefaultHttpControllerSelector或许是更加合理的方式。

    例:扩展IAssembliesResolver动态加载控制器(controller)

    可以将控制器(controller)类单独编制为一个dll,放在指定的文件夹内,这样无需编译整个框架,就能修改控制器(controller)

        public class ServiceAssembliesResolver : IAssembliesResolver
        {
            private string path;
            public ServiceAssembliesResolver(string path)
            {
                this.path = path;
            }
            public ICollection<System.Reflection.Assembly> GetAssemblies()
            {
                List<Assembly> assemblies = new List<Assembly>();
                try
                {
                    //初始化
                    assemblies = new List<Assembly>();
                    //加载每一个服务插件
                    foreach (string file in Directory.GetFiles(path, "*.dll"))
                    {
                        var controllersAssembly = Assembly.LoadFrom(file);
                        assemblies.Add(controllersAssembly);
                    }
                }
                catch (Exception ex)
                {
                    //处理异常
                }
                return assemblies;
            }
        }

    此外继承Web API框架默认的DefaultAssembliesResolver也是一个好办法。

        public class ServiceAssembliesResolver : DefaultAssembliesResolver
        {
            //服务插件路径
            private string path;
            public ServiceAssembliesResolver(string path):base()
            {
                this.path = path;
            }
            public override ICollection<Assembly> GetAssemblies()
            {
                List<Assembly> assemblies = new List<Assembly>();
                try
                {
                    //获得已有的服务
                    ICollection<Assembly> baseAssemblies = base.GetAssemblies();
                    //初始化
                    assemblies = new List<Assembly>(baseAssemblies);
                    //加载每一个服务插件
                    foreach (string file in Directory.GetFiles(path, "*.dll"))
                    {
                        var controllersAssembly = Assembly.LoadFrom(file);
                        assemblies.Add(controllersAssembly);
                    }
                }
                catch (Exception ex)
                {
                   //处理异常
                }
                return assemblies;
            }
        }

    5使用特性设置路由

    为了更好地支持URI参数,所以使用路由特性。

    5.1使用特性

    RouteAttribute

    路由特性定义为:

    public sealed class RouteAttribute : Attribute, IDirectRouteFactory, IHttpRouteInfoProvider
    {
            public RouteAttribute();
            //template:描述要匹配的 URI 模式的路由模板
            public RouteAttribute(string template);
            //路由名称
            public string Name { get; set; }
            //路由顺序
            public int Order { get; set; }
            //描述要匹配的 URI 模式的路由模板
            public string Template { get; }
    }

    RoutePrefix

    使用RoutePrefix特性为整个控制器(controller)设置路由前缀,路由前缀特性定义为:

    public class RoutePrefixAttribute : Attribute, IRoutePrefix
    {
            protected RoutePrefixAttribute();
            //prefix: 控制器的路由前缀。
            public RoutePrefixAttribute(string prefix);
            //获取路由前缀。
            public virtual string Prefix { get; }
    }

    例子:

        [RoutePrefix("api/values")]
        public class ValuesController : ApiController
        {
             //GET api/values/getvalues
            [Route("getvalues")]
            public IEnumerable<string> Get()
            {
                return new string[] { "value1", "value2" };
            }
        }

    使用“~”可重写路由前缀,例如:

    [RoutePrefix("api/values")]
    public class ValuesController : ApiController
    {
            [Route("~/api/allvalues")]
            public IEnumerable<string> Get()
            {
                return new string[] { "value1", "value2" };
            }
    }

    路由前缀可以包含参数:

    [RoutePrefix("api/values/{id}")]
    public class ValuesController : ApiController
    {
        //GET api/values/1/getvalues
            [Route("getvalues")]
            public IEnumerable<string> Get(string id)
            {
                //
            }
    }

    路由约束

    限制参数的类型,语法为:{parameter:constraint},可以指定多个约束,每个约束用:分隔。Route和RoutePrefix特性均支持这种用法。

    [RoutePrefix("api/values/{id:int:min(1)}")]
    public class ValuesController : ApiController
    {
            [Route("GetValues")]
            public IEnumerable<string> Get(int id)
            {
                //具体实现
            }
    }

    约束规则如下:

    约束

    描述

    例子

    alpha

    匹配大写或小写拉丁字母(A-Z,a-z)

    {x:alpha}

    bool

    匹配Boolean 类型

    {x:bool}

    datetime

    匹配DateTime 类型

    {x:datetime}

    decimal

    匹配decimal类型

    {x:decimal}

    double

    匹配double类型

    {x:double}

    float

    匹配float类型

    {x:float}

    guid

    匹配GUID值

    {x:guid}

    int

    匹配int类型

    {x:int}

    length

    匹配指定长度或指定长度范围内的字符串

    {x:length(6)} {x:length(1,20)}

    long

    匹配long类型

    {x:long}

    max

    匹配整型,其值不能大于设置的值

    {x:max(10)}

    maxlength

    匹配字符串,它的长度不能超过设定的值

    {x:maxlength(10)}

    min

    匹配整型,其值不能小于设定的值

    {x:min(10)}

    minlength

    匹配字符串,它的长度不能小于设置的值

    {x:minlength(10)}

    range

    指定整型的范围

    {x:range(10,50)}

    regex

    匹配正则表达式

    {x:regex(^d{3}-d{3}-d{4}$)}

     

    可选URI参数与默认值

    使用?来标识路由值为可选的,同时必须为操作参数设置默认值。

    例:

            [Route("api/v1/user/{id:int?}")]
            [HttpGet]
            public IHttpActionResult User(int id=1)
            {
                return Json("id:"+id);
            }

    设置路由名称

    设置路由名称后,可以在使用控制器(controller)的属性ApiController.Url或ApiController.Route拼接URL。

    例:在GetPublicationNew中获得路由到操作GetPublicationURL

            [Route("api/v1/publication",Name="V1Publication")]
            public IHttpActionResult GetPublication()
            {
                return Json("api/v1/publication");
            }
    
            [HttpGet]
            [Route("api/v2/publication")]
            public IHttpActionResult GetPublicationNew()
            {
                string url = Url.Link("V1Publication", null);
                return Json(url);
            }

    路由顺序

    RouteOrder值较小的路由先被使用,默认的RouteOrder值为0。

    比较顺序的规则为:

    1)先比较RouteOrder的值

    2)查看路由模板的URI参数,对于每一个参数,由参数决定的顺序为:

    • 字面值顺序排第一。
    • 含有路由约束的顺序排第二。
    • 没有路由约束的顺序排第三。
    • 含有通配符和路由约束的顺序排第四。
    • 含有通配符和无路由约束的顺序排第五。

    3)在上述规则无法区分的情况下,即上述规则判定顺序相同的两个路由,决定顺序的依据是:不区分大小写地,比较字符串的序号。

    例:这里引用官网文档的例子

    https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2)

    [RoutePrefix("orders")]
    public class OrdersController : ApiController
    {
        [Route("{id:int}")] // 设置路由约束
        public HttpResponseMessage Get(int id) { ... }
    
        [Route("details")]  // 字面值
        public HttpResponseMessage GetDetails() { ... }
    
        [Route("pending", RouteOrder = 1)]//指定路由顺序
        public HttpResponseMessage GetPending() { ... }
    
        [Route("{customerName}")]  //无路由约束
        public HttpResponseMessage GetByCustomer(string customerName) { ... }
    
        [Route("{*date:datetime}")]  // 含有通配符
        public HttpResponseMessage Get(DateTime date) { ... }
    }

    路由顺序依次为:

    第一.orders/details

    第二.orders/{id}

    第三.orders/{customerName}

    第四.orders/{*date}

    第五.orders/pending

    使路由特性起作用

    要想使路由特性起作用,必须在WebApiConfig.Register方法中加入代码:config.MapHttpAttributeRoutes();

    如下完整代码:

        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                //启用路由特性
                config.MapHttpAttributeRoutes();
    
                // 其他配置
            }
        }

    可以同时使用路由特性与基于协定路由:

    public static void Register(HttpConfiguration config)
    {
            //启用路由特性
            config.MapHttpAttributeRoutes();
    
            // 基于协定的路由
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
    }

    自定义路由约束

    实现一个继承自IHttpRouteConstraint接口的类,然后注册此类。

    例:

    自定义CustomHttpRouteConstraint

    public class CustomHttpRouteConstraint : IHttpRouteConstraint
    {
    
            public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
            {
                //实现验证过程   
            }
    }

    注册CustomHttpRouteConstraint,为这个约束提供一个简称。

        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                //其他配置
    
                var constraintResolver = new DefaultInlineConstraintResolver();
                constraintResolver.ConstraintMap.Add("customcons", typeof(CustomHttpRouteConstraint));
    
                config.MapHttpAttributeRoutes(constraintResolver);
            }
        }

    使用自定义约束

            [Route("{name:customcons}")]
            public IHttpActionResult SUser(string name)
            {
                return Json("name:" + name);
            }

    5.2应用场景:

    支持多版本API:

    假设随着业务的扩展,对API接口进行升级改造,老的接口还要使用一段时间而不会立即停用,这时需要版本控制机制。如下面的例子,使用路由特性后,

    虽然URI片段中的指定的操作(Action)名称一样,但是调用的操作(Action)却不一样。

    例:

            [Route("api/v1/publication")]
            public IHttpActionResult GetPublication()
            {
                return Json("api/v1/publication");
            }
    
            [Route("api/v2/publication")]
            public IHttpActionResult GetPublicationNew()
            {
                return Json("api/v2/publication");
            }

    当在浏览器中输入:http://localhost:45778/api/v1/publication时,显示"api/v1/publication"

    当在浏览器中输入:http://localhost:45778/api/v2/publication时,显示"api/v2/publication"

    由于上述操作定义在同一个控制器(Controller)类中,所以方法名不能相同。

    注意:由于上述操作名称中含有Get字符串,所以支持Get请求。

    重载

    为了支持重载的方法,使用路由特性

    例:

            [Route("api/v1/user/{id}")]
            public IHttpActionResult GetUser(int id)
            {
                return Json("id:"+id);
            }
    
            [Route("api/v2/user/{name}")]
            public IHttpActionResult GetUser(string name)
            {
                return Json("name:" + name);
            }

    当在浏览器中输入http://localhost:45778/api/v1/user/1时,页面显示“id:1”

    当在浏览器中输入http://localhost:45778/api/v2/user/coding时,页面显示“name:coding”

    支持URI时间参数

    例:

    请求Url:http://localhost:45778/api/user/1982-02-01

            [HttpGet]
            [Route("api/user/{time:datetime}")]
            public IHttpActionResult User(DateTime time)
            {
                return Json("time:" + time);
            }

    输出为:"time:1982/2/1 0:00:00"

     

    请求Url:http://localhost:45778/api/user/1982/02/01

            [HttpGet]
            [Route("api/user/{*time:datetime:regex(\d{4}/\d{2}/\d{2})}")]
            public IHttpActionResult User(DateTime time)
            {
                return Json("time:" + time);
            }

    输出为:"time:1982/2/1 0:00:00"

    也可以将两种约束一起使用,这样可以同时支持两种格式了

            [HttpGet]     
            [Route("api/user/{*time:datetime:regex(\d{4}/\d{2}/\d{2})}")]
            [Route("api/user/{time:datetime:regex(\d{4}-\d{2}-\d{2})}")]
            public IHttpActionResult User(DateTime time)
            {
                return Json("time:" + time);
            }

    参考

    https://docs.microsoft.com/en-us/aspnet/web-api/

    转载与引用请注明出处。
    
    时间仓促,水平有限,如有不当之处,欢迎指正。
  • 相关阅读:
    二,数据类型与流程控制语句
    一,cmd指令集与变量
    web第九天,浮动与定位
    web第八天,PS切图与float浮动
    web第七天,标签分类
    web第六天,CSS优先级与盒子模型
    web第五天复合样式与选择器
    web第四天,CSS基础
    web第三天 表单与css基础
    装饰器
  • 原文地址:https://www.cnblogs.com/hdwgxz/p/8729000.html
Copyright © 2011-2022 走看看