zoukankan      html  css  js  c++  java
  • 解读ASP.NET 5 & MVC6系列(10):Controller与Action

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

    Controller&Action的定义和使用

    在新版MVC6框架中,依然提供了一个Controller基类,在这里除了依然提供了UrlRouteDataHttpContextRequestResponse以外,还提供了一个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系列

  • 相关阅读:
    Linq聚合操作之Aggregate,Count,Sum,Distinct源码分析
    Linq分区操作之Skip,SkipWhile,Take,TakeWhile源码分析
    Linq生成操作之DefautIfEmpty,Empty,Range,Repeat源码分析
    Linq基础操作之Select,Where,OrderBy,ThenBy源码分析
    PAT 1152 Google Recruitment
    PAT 1092 To Buy or Not to Buy
    PAT 1081 Rational Sum
    PAT 1084 Broken Keyboard
    PAT 1077 Kuchiguse
    PAT 1073 Scientific Notation
  • 原文地址:https://www.cnblogs.com/TomXu/p/4496456.html
Copyright © 2011-2022 走看看