ASP.NET WebApi通过自定义ControllerSelector来自定义Controller的选择

在web api中,我们可以通过给Cotroller类添加RoutePrefixAttribute来定义url与Controller之间的映射,但是有时候有一些特殊情况下,我们需要做一些特殊处理来将某些即时满足某一url prefix的请求映射到另一个Controller.这种情况下,我们就需要自定义Controller的Selector逻辑了。

看一下这个如下两个Controller:

[RoutePrefix("Order")]
    public class OrderController : ApiController
    {
        [HttpGet]
        [Route("~/Product/Order")]
        public List<string> GetOrder()
        {
            return new List<string>() { "p1", "p2" };
        }
    }

[RoutePrefix("Product")]
    public class ProductController : ApiController
    {
        [HttpPost]
        [Route("{id}")]
        public string Get(string id,[FromBody]string product)
        {
            return "pbyid";
        }
    }

当用户访问/Product/Order这个url时,系统会产生错误,因为OrderController中的GetOrder action和ProductController 中的Get action都与该url匹配(即使这两个Action对应的Method是不一样的,但是当选择Controller的时候只会根据Url中与Controller中Route的匹配情况来选择,具体Controller选择算法参考官方文档)。其实根据我们对id规则的定义,Order这个字符不会是一个合法的id,因此这个Url肯定是应该映射到OrderController中的GetOrder action。(这里仅仅为了演示自定义Controller的选择,实际情况有更好的解决该问题的方法)

这个种情况下我们就需要自定义Controller的选择逻辑了。

首先我们看一下Web Api消息处理的整个流程图:

 

 从这张图可以看到,处理请求时,web api会先根据url找到匹配的Controller,然后在创建的controller对象中去调用匹配的action方法。在上面的例子中,ProductController和OrderController都匹配请求url,所以会发生错误。我们在这个过程中加入自定义逻辑选择OrderController作为/Product/Order的Controller.问题就解决了。

从图中可以看到Controller的选择是通过IHttpControllerSelector这个接口来实现的。

 

 

NameDescription
System_CAPS_pubmethod GetControllerMapping()

Returns a map, keyed by controller string, of all HttpControllerDescriptor that the selector can select. This is primarily called by IApiExplorer to discover all the possible controllers in the system.

System_CAPS_pubmethod SelectController(HttpRequestMessage)

Selects a HttpControllerDescriptor for the given HttpRequestMessage.

web api框架提供了这个接口的默认实现DefaultHttpControllerSelector.我们可以通过派生出自己的selector,然后重写SelectorController方法来自定义controller的选择逻辑。

public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            //string controllerName = GetControllerName(request);
            if (request.RequestUri.PathAndQuery.StartsWith("/Product/Order"))
            {
                if (_orderControllerDescriptor == null)
                {
                    _orderControllerDescriptor = new HttpControllerDescriptor()
                    {
                        Configuration = _configuration,
                        ControllerName = typeof(OrderController).FullName,
                        ControllerType = typeof(OrderController)
                    };
                }

                //return _orderControllerDescriptor;
            }

            return base.SelectController(request);
        }

然后在将自定义的selector注册到web api的对象容器中.

config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(config));

这样我们就将请求GET /Product/Order映射到了OrderController上,后续的Action Seletion会选择OrderController中的GetOrder方法来处理该请求。

采用相同的办法,我们也可以从默认的ActionSelector:ApiControllerActionSelector派生出自己的ActionSelector,然后重写SelectAction来自定义action选择逻辑。

posted @ 2017-07-15 21:03  self.refactoring  阅读(1103)  评论(0编辑  收藏  举报