解读ASP.NET 5 & MVC6系列(10):Controller与Action

原文:解读ASP.NET 5 & MVC6系列(10):Controller与Action

我们知道在MVC5和之前的版本,两个框架的生命周期是不一样的,在新版MVC6中,MVC Controller/Web API Controller已经合二为一了,本章我们主要讲解Controller和Action的定义与使用,以及在MVC框架中,如何根据路由查询相应的Controller和Action。

Controller&Action的定义和使用

在新版MVC6框架中,依然提供了一个Controller基类,在这里除了依然提供了UrlRouteDataHttpContentRequestResponse以外,还提供了一个IServiceProvider类型的Resovler属性,该属于是依赖注入的容器,用于获取当前请求作用域内指定类型的实例对象。

其遵守如下规则:

  1. 继承于Microsoft.AspNet.Mvc.Controller的类肯定都是控制器,不管有没有Controller后缀。
  2. 不继承Microsoft.AspNet.Mvc.Controller的自定义XXXController要作为MVC Controller的话,,则必须要引用Microsoft.AspNet.Mvc相关的程序集。
  3. 如果不想让满足上述条件的Controller类作为Controller,需要在该类上加上NonControllerAttribute特性。
  4. 同理,如果不想让某个Controller中的方法作为Action,则需要在该方法上加上NonActionAttribute特性。

另外还有如下几个特性需要注意:

特性 描述
ActionNameAttribute 定义Action的名称(可以和Action方法名不同)
AcceptVerbsAttribute 定义支持的Http Method名称,支持单个或多个Method。
ActivateAttribute 依赖注入的标记,可以放在具有set权限的属性或字段上。
ResponseCacheAttribute 针对某个Controller或Action设置客户端缓存。
RequireHttpsAttribute 限制必须是Https请求。
RemoteAttribute 标记为Ajax请求,服务器端不验证form表单的验证。
NonControllerAttribute 标记该类不是Controller。
NonActionAttribute 标记该方法不是Action。

Controller的查找机制

由上述章节,我们知道MVC6不仅支持正常的Controller(继承于Controller基类的子类),也支持POCO的Controller,本节我们就来研究一下Controller的查找原理机制。

首先,要判断一个类是否是Controller必须先确定有多少个程序集里定义了这样的类。Microsoft.AspNet.Mvc命名空间下的IAssemblyProvider接口就是覆盖查找所有可能定义Controller的程序集,该接口的默认实现是DefaultAssemblyProvider类,在该类中,设置的必要条件是,定义了MVC的Controller必须要引用了如下程序集中的一个或多个程序集,列表如下:

Microsoft.AspNet.Mvc
Microsoft.AspNet.Mvc.Core
Microsoft.AspNet.Mvc.ModelBinding
Microsoft.AspNet.Mvc.Razor
Microsoft.AspNet.Mvc.Razor.Host
Microsoft.AspNet.Mvc.TagHelpers
Microsoft.AspNet.Mvc.Xml
Microsoft.AspNet.PageExecutionInstrumentation.Interfaces

也就是说,如果你定义了一个引用了Microsoft.AspNet.Mvc的DLL类库的话,其里面的POCO Controller都会被认为是MVC的Controller。换句话说,如果你定义的POCO Controller类没有引用上述程序集中的任意一个程序集,那这些Controller类不会被认为是MVC的Controller。

程序集的查找

目前有两种方式可以自定义Controller的查找机制,第一种是继承IAssemblyProvider实现CandidateAssemblies方法(或重载DefaultAssemblyProvider),来定义自己的逻辑。接口定义如下:

public interface IAssemblyProvider
{
    IEnumerable<Assembly> CandidateAssemblies { get; }
}

另外一种方式,可能相对来说更简单一些,那就是使用IServicesCollection上定义的扩展方法来定义要查找的程序集:

services.AddMvc().WithControllersAsServices(new[]
{
    typeof(MyController).Assembly,
    typeof(ExternalPocoController).Assembly
});

使用上述代码后,系统将会把DefaultAssemblyProvider切换成FixedSetAssemblyProvider来实现上述判断机制,即:在固定范围内的程序集里进行查找。

程序集的筛选

确定了程序集以后,另外一个问题就来了,如何判断一个程序集是否引用了上述MVC必要条件中所列的程序集呢?答案是,Microsoft.Framework.Runtime中的ILibraryManager接口实例的GetReferencingLibraries方法,可以查找有多少个程序集引用了上述列表中的其中一个程序集。例如,可以根据Microsoft.AspNet.Mvc程序集,来查找有多少个程序集引用了该程序集,示例如下:

var col = this.Resolver.GetRequiredService<ILibraryManager>();
var data = col.GetReferencingLibraries("Microsoft.AspNet.Mvc");

该功能在DefaultAssemblyProvider默认实现类中的使用代码如下:

protected virtual IEnumerable<ILibraryInformation> GetCandidateLibraries()
{
    if (ReferenceAssemblies == null)
    {
        return Enumerable.Empty<ILibraryInformation>();
    }

    // GetReferencingLibraries returns the transitive closure of referencing assemblies
    // for a given assembly.
    return ReferenceAssemblies.SelectMany(_libraryManager.GetReferencingLibraries)
                                .Distinct()
                                .Where(IsCandidateLibrary);
}

Controller的判断

确定了符合必要条件的程序集之后,就可以遍历该程序集内所有的类型,并接着判断该类型是否是Controller了。在新版的Controller判断上,实现该功能的是一个IControllerTypeProvider接口,该接口提供了一个ControllerTypes只读属性用于获取所有定义的Controller,接口定义如下:

public interface IControllerTypeProvider
{
    IEnumerable<TypeInfo> ControllerTypes { get; }
}

DefaultControllerTypeProvider是该接口的默认实现,在查询符合条件的Controller的时候,该默认实现类定义了一个IsController方法,用于判断一个类型是否是Controller,具体逻辑如下:

protected internal virtual bool IsController([NotNull] TypeInfo typeInfo,
                                             [NotNull] ISet<Assembly> candidateAssemblies)
{
    if (!typeInfo.IsClass)  // 该类型必须是一个类
    {
        return false;
    }
    if (typeInfo.IsAbstract) // 该类必须不是抽象类
    {
        return false;
    }
    // We only consider public top-level classes as controllers. IsPublic returns false for nested
    // classes, regardless of visibility modifiers
    if (!typeInfo.IsPublic) // 该类必须是一个Public类(并且不嵌套),嵌套类不能作为Controller
    {
        return false;
    }
    if (typeInfo.ContainsGenericParameters) // 该类不能是泛型类
    {
        return false;
    }
    if (!typeInfo.Name.EndsWith(ControllerTypeName, StringComparison.OrdinalIgnoreCase) &&
        !DerivesFromController(typeInfo, candidateAssemblies)) // 该类以Controller结尾,或继承于Controller基类,或其父类也是Controller。
    {
        return false;
    }
    if (typeInfo.IsDefined(typeof(NonControllerAttribute))) // 该类不能设置NonControllerAttribute特性
    {
        return false;
    }

    return true;
}

你也可以自己实现IControllerTypeProvider接口来定义自己的Controller判断逻辑,不过和固定某些程序集类型,MVC在IServicesCollection上也提供了一个扩展方法,用于限制一些Controller特定类型,示例如下:

services.AddMvc().WithControllersAsServices(new[]
    {
        typeof(MyController),
        typeof(ExternalPocoController)
    });

使用上述代码后,系统将会把DefaultControllerTypeProvider切换成FixedSetControllerTypeProvider来实现上述判断机制,即:限制某些特定的类作为Controller,其它类型都不能作为Controller。

Action的查找机制

Action的选择则是通过IActionSelector接口的默认实现类DefaultActionSelector来实现的,在实现的SelectAsync方法中,通过上下文和路由数据选择最匹配的Action,示意代码如下:

public Task<ActionDescriptor> SelectAsync([NotNull] RouteContext context)
{
   // ...
}

还有一个地方会判断一个方法是否是Action,那就是IActionModelBuilder接口,该接口的默认实现为DefaultActionModelBuilder类,实现方法如下:

public IEnumerable<ActionModel> BuildActionModels([NotNull] TypeInfo typeInfo,
                                                  [NotNull] MethodInfo methodInfo)
{
    if (!IsAction(typeInfo, methodInfo))
    {
        return Enumerable.Empty<ActionModel>();
    }
    // ....省略其它代码
}

该实现方法,通过一个内部的IsAction方法来判断该方法是否是一个真正的Action方法,具体代码如下:

protected virtual bool IsAction([NotNull] TypeInfo typeInfo, [NotNull] MethodInfo methodInfo)
{
    // The SpecialName bit is set to flag members that are treated in a special way by some compilers
    // (such as property accessors and operator overloading methods).
    if (methodInfo.IsSpecialName)  // 不能是特殊名称(如重载的操作符或属性访问器)
    {
        return false;
    }

    if (methodInfo.IsDefined(typeof(NonActionAttribute)))  // 不能声明NonActionAttribute特性
    {
        return false;
    }

    // Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
    if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object)) //不能是重载的方法,比如Equals和GetHashCode
    {
        return false;
    }

    // Dispose method implemented from IDisposable is not valid
    if (IsIDisposableMethod(methodInfo, typeInfo))  // 不能是Dispose方法
    {
        return false;
    }

    if (methodInfo.IsStatic) // 不能是静态方法
    {
        return false;
    }

    if (methodInfo.IsAbstract) // 不能是抽象方法
    {
        return false;
    }

    if (methodInfo.IsConstructor)  // 不能是构造函数
    {
        return false;
    }

    if (methodInfo.IsGenericMethod)  // 不能是泛型方法
    {
        return false;
    }

    return
        methodInfo.IsPublic;   // 必须是Public方法
}

以上内容就是关于Controller和Action查找相关的重要代码,详细原理步骤,请参考Microsoft.AspNet.Mvc.Core程序集下的所有源码。

同步与推荐

本文已同步至目录索引:解读ASP.NET 5 & MVC6系列

时间: 2024-10-24 01:53:32

解读ASP.NET 5 & MVC6系列(10):Controller与Action的相关文章

解读ASP.NET 5 &amp; MVC6系列(11):Routing路由

原文:解读ASP.NET 5 & MVC6系列(11):Routing路由 新版Routing功能介绍 在ASP.NET 5和MVC6中,Routing功能被全部重写了,虽然用法有些类似,但和之前的Routing原理完全不太一样了,该Routing框架不仅可以支持MVC和Web API,还支持一般的ASP.NET5程序.新版的改变有如下几个部分. 首先,Routing系统是基于ASP.NET 5的,是一个独立于MVC的路由框架,而不是基于MVC的.MVC只是在上面扩展了一个快捷方式而已. 其次,

解读ASP.NET 5 &amp; MVC6系列(1):ASP.NET 5简介

原文:解读ASP.NET 5 & MVC6系列(1):ASP.NET 5简介 ASP.NET 5简介 ASP.NET 5是一个跨时代的改写,所有的功能和模块都进行了独立拆分,做到了彻底解耦.为了这些改写,微软也是蛮 拼的,几乎把.NET Framwrok全部改写了一遍,形成了一个.NET Core的东西. 在.NET Core里一切都是可配置的,包括Session.MVC等功能,而一切可配置的功能都是可以在Nuget上进行下载. 目前ASP.NET 5依旧兼容老的.NET Framwrok,但要

解读ASP.NET 5 &amp; MVC6系列(14):View Component

原文:解读ASP.NET 5 & MVC6系列(14):View Component 在之前的MVC中,我们经常需要类似一种小部件的功能,通常我们都是使用Partial View来实现,因为MVC中没有类似Web Forms中的WebControl的功能.但在MVC6中,这一功能得到了极大的改善.新版MVC6中,提供了一种叫做View Component的功能. 你可以将View Component看做是一个mini的Controller--它只负责渲染一小部分内容,而非全部响应,所有Parti

解读ASP.NET 5 &amp; MVC6系列(3):项目发布与部署

原文:解读ASP.NET 5 & MVC6系列(3):项目发布与部署 本章我们将讲解ASP.NET5项目发布部署相关的内容,示例项目以我们前一章创建的BookStore项目为例. 发布前的设置 由于新版ASP.NET5支持多版本DNX运行环境的发布和部署,所以在部署之前,我们需要设定部署的目标DNX(即之前的KRE). 步骤:右键BookStore项目->属性->Application选项卡,选择DNX的版本,本例中,选择dnx-coreclr-win-x64.1.0.0-beta4.

解读ASP.NET 5 &amp; MVC6系列(12):基于Lamda表达式的强类型Routing实现

原文:解读ASP.NET 5 & MVC6系列(12):基于Lamda表达式的强类型Routing实现 前面的深入理解Routing章节,我们讲到了在MVC中,除了使用默认的ASP.NET 5的路由注册方式,还可以使用基于Attribute的特性(Route和HttpXXX系列方法)来定义.本章,我们将讲述一种基于Lambda表达式的强类型类型. 这种方式的基本使用示例如下: services.Configure<MvcOptions>(opt => { opt.EnableTy

解读ASP.NET 5 &amp; MVC6系列(7):依赖注入

原文:解读ASP.NET 5 & MVC6系列(7):依赖注入 在前面的章节(Middleware章节)中,我们提到了依赖注入功能(Dependency Injection),ASP.NET 5正式将依赖注入进行了全功能的实现,以便开发人员能够开发更具弹性的组件程序,MVC6也利用了依赖注入的功能重新对Controller和View的服务注入功能进行了重新设计:未来的依赖注入功能还可能提供更多的API,所有如果还没有开始接触依赖注入的话,就得好好学一下了. 在之前版本的依赖注入功能里,依赖注入的

解读ASP.NET 5 &amp; MVC6系列(6):Middleware详解

原文:解读ASP.NET 5 & MVC6系列(6):Middleware详解 在第1章项目结构分析中,我们提到Startup.cs作为整个程序的入口点,等同于传统的Global.asax文件,即:用于初始化系统级的信息(例如,MVC中的路由配置).本章我们就来一一分析,在这里如何初始化这些系统级的信息. 新旧版本之间的Pipeline区别 ASP.NET 5和之前版本的最大区别是对HTTP Pipeline的全新重写,在之前的版本中,请求过滤器的通常是以HttpModule为模块组件,这些组件

解读ASP.NET 5 &amp; MVC6系列(8):Session与Caching

原文:解读ASP.NET 5 & MVC6系列(8):Session与Caching 在之前的版本中,Session存在于System.Web中,新版ASP.NET 5中由于不在依赖于System.Web.dll库了,所以相应的,Session也就成了ASP.NET 5中一个可配置的模块(middleware)了. 配置启用Session ASP.NET 5中的Session模块存在于Microsoft.AspNet.Session类库中,要启用Session,首先需要在project.json

解读ASP.NET 5 &amp; MVC6系列(2):初识项目

原文:解读ASP.NET 5 & MVC6系列(2):初识项目 初识项目 打开VS2015,创建Web项目,选择ASP.NET Web Application,在弹出的窗口里选择ASP.NET 5 Website模板创建项目,图示如下: 我们可以看到,此时Web Forms\MVC\Web API复选框都选择不了,原有是因为在ASP.NET 5中做了大量更改,移除了Web Forms功能,将MVC.Web API.Web Pages这些功能合在了一起,所以自然就不需要这些复选框了.另外由于是CT