Attribute Routing(外传)

题外话:由于这个技术点是新学的,并不属于原系列,但借助了原系列的项目背景,故命名外传系列,以后也可能在这个系列中附加一些新的技术。

前言

在Web Api 2.0中,提出了一种新的配置路由方式——基于特性的路由(Attribute-based Routing),在我们之前介绍的配置路由方式称为——基于公约的路由(Convention-based Routing),新的路由配置方式同样应用在MVC5中,因此本文就来介绍一下基于特性的路由。

在之前的一篇文章中,我们处理了这么一个业务——实现学生选课。我们是通过在“WebApiConfig”定制了一条路由数据来实现的,这条路由实现了选课以及根据课程Id来查询选择选择该课程的所有学生信息,感觉设计还可以。在实际应用中一般来说查询用的是最多的,使用Attribute Routing来注册路由会更灵活,控制起来也更方便,更符合Rest。本文以根据课程的名字来查询所有选择该课程的学生信息以及根据课程名字以及学生名字查询某一学生的信息。

基于特性的路由介绍

顾名思义,新路由将使用一个特性来实现路由注册,如下:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true, Inherited=true)]
    public sealed class RouteAttribute : Attribute, IHttpRouteInfoProvider
    {
        public RouteAttribute();
        public RouteAttribute(string template);
    
        public string  Name { get; set; }
        public int     Order { get; set; }
        public string  Template { get; private set; }
    }

这个类包含3个属性:Name指路由的名字,Order是指路由的顺序,Template就是我们要去匹配URL的模板

实现Attribute Routing

原理性的东西不多介绍的(我也没多研究 呵呵),能把学到的技术运用到实际中才是王道,上来一大堆原理容易晕,以后需要深入研究再看原理。

在“EnrollmentsController”中新增一个方法GetStudentsInfo:

[Route("api/enrollments/{courseName}/{studentName?}")]
        public IEnumerable<StudentBaseModel> GetStudentsInfo(string courseName, string studentName="")
        {
            IQueryable<Student> query;
            Course course=  TheRepository.GetAllCourses().Where(c => c.Name == courseName).FirstOrDefault();
            if (course==null )
            {
                return null ;
            }
             query = TheRepository.GetEnrolledStudentsInCourse(course.Id).OrderBy(s => s.LastName);
            if (!string.IsNullOrWhiteSpace(studentName))
            {
                query = query.Where(s => s.FirstName == studentName);
            }
            var totalCount = query.Count();

            System.Web.HttpContext.Current.Response.Headers.Add("X-InlineCount", totalCount.ToString());

            var results = query

                        .ToList()
                        .Select(s => TheModelFactory.Create(s));

            return results;

        }

在我们的Action上使用了RouteAttribute。分析一下这个URL模板( " api/enrollments/{courseName}/{studentName?} " ),{courseName}会匹配到Action的courseName参数上,对于Action的另一个参数studentName是一个可选参数,也就是说请求中没有给出值那么就是默认的空字符串,有值得话就会被赋值,因此我们在“{studentName}”后面加上? 标记为可选的URI参数。

ok,就这么简单,测试一次:

image

结果:

image

呃,出错了。。。

一个新的问题

出错了,不过不管怎么说解决方案总归是有的,首先看下错误原因:是在LearningControllerSelector类的方法中出现了空引用,那么我们就不得不看下这个方法:

public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            var controllers = GetControllerMapping(); //Will ignore any controls in same name even if they are in different namepsace
 
            var routeData = request.GetRouteData();
 
            var controllerName = routeData.Values["controller"].ToString();
 
            HttpControllerDescriptor controllerDescriptor;
 
            if (controllers.TryGetValue(controllerName, out controllerDescriptor))
            {
 
                var version = "2";
 
                var versionedControllerName = string.Concat(controllerName, "V", version);
 
                HttpControllerDescriptor versionedControllerDescriptor;
                if (controllers.TryGetValue(versionedControllerName, out versionedControllerDescriptor))
                {
                    return versionedControllerDescriptor;
                }
 
                return controllerDescriptor;
            }
 
            return null;
 
        }

看了这段代码我们不难发现这个方法是我们重写了基类的方法,目的是为了实现现版本控制(详情可移步: http://www.cnblogs.com/fzrain/p/3558765.html ),在我们重写的方法中我们用到了RouteData中包含的Controller的名字,而这个名字是由基于公约的路由与URI匹配得到的,因此我们这里肯定是没有的。因为我们的项目混合了2钟路由配置,而自定义的方法是针对基于公约的路由配置,因此对于Attribute Routing我们直接使用默认的选择方式:

在“ var controllerName = routeData.Values[ " contro