解读ASP.NET 5 & MVC6系列教程(10):Controller与Action
我们知道在mvc5和之前的版本,两个框架的生命周期是不一样的,在新版mvc6中,mvc controller/web api controller已经合二为一了,本章我们主要讲解controller和action的定义与使用,以及在mvc框架中,如何根据路由查询相应的controller和action。
controller&action的定义和使用
在新版mvc6框架中,依然提供了一个controller基类,在这里除了依然提供了url
、routedata
、httpcontext
、request
、response
以外,还提供了一个iserviceprovider
类型的resovler
属性,该属于是依赖注入的容器,用于获取当前请求作用域内指定类型的实例对象。
其遵守如下规则:
继承于microsoft.aspnet.mvc.controller
的类肯定都是控制器,不管有没有controller后缀。不继承microsoft.aspnet.mvc.controller
的自定义xxxcontroller要作为mvc controller的话,,则必须要引用microsoft.aspnet.mvc
相关的程序集。如果不想让满足上述条件的controller类作为controller,需要在该类上加上noncontrollerattribute
特性。同理,如果不想让某个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系列教程(10):Controller与Action
-
解读ASP.NET 5 & MVC6系列教程(14):View Component
-
解读ASP.NET 5 & MVC6系列教程(17):MVC中的其他新特性
-
解读ASP.NET 5 & MVC6系列教程(16):自定义View视图文件查找逻辑
-
解读ASP.NET 5 & MVC6系列教程(2):初识项目
-
解读ASP.NET 5 & MVC6系列教程(4):核心技术与环境配置
-
解读ASP.NET 5 & MVC6系列教程(3):项目发布与部署
-
解读ASP.NET 5 & MVC6系列教程(12):基于Lamda表达式的强类型Routing实现
-
解读ASP.NET 5 & MVC6系列教程(5):Configuration配置信息管理
-
解读ASP.NET 5 & MVC6系列教程(1):ASP.NET 5简介