zoukankan      html  css  js  c++  java
  • ASP.NET MVC

    ASP.NET MVC - 控制器(Controller)

    System.Web.Mvc命名空间下的有关控制器的类型

    此命名空间下定义了与控制器有关的类型,首先从宏观上了解一下这些类型。

    类型(class)
    ControllerBuilder //控制器Builder,创建控制器工厂
    DefaultControllerFactory //控制器工厂,创建控制器实例
    ControllerBase //控制器抽象基类
    Controller //控制器
    ControllerContext //控制器上下文,封装了Http请求上下文对象、路由数据对象、控制器对象

    接口(interface)
    IControllerFactory //控制器工厂接口

    ControllerBuilder(创建控制器工厂的类) 

    用于创建控制器工厂

    DefaultNamespaces
    //获取所有控制器类型所在的命名空间,返回一个HashSet<string>

    GetControllerFactory( )
    //创建一个控制器工厂,返回一个IControllerFactory实例

    DefaultControllerFactory(控制器工厂类) 

    IControllerFactory接口的实现类

    CreateController( RequestContext requestContext, string controllerName )
    //从requestContext中取出路由数据(RouteData),从路由数据中取出控制器和Action方法的名称,创建一个对应的控制器
    static GetControllerSessionBehavior( RequestContext requestContext, Type controllerType )
    //获取默认的Session,返回一个SessionStateBehavior 
    ReleaseController( IController controller )
    //释放控制器,清理资源

    ControllerBase(控制器抽象基类)

    此类是所有控制器的抽象基类,所有的控制器都必须实现抽象基类ControllerBase的Execute抽象方法才能执行控制器(因为开发人员都使用基架创建控制器,所以不需要手动实现Execut方法)。在开发人员利用基架创建的控制器被初始化的时候,会同时初始化它的三个属性TempData(TempDataDictionary)、ViewData(ViewDataDictionary)、ViewBag(dynamic),这三个属性主要是用于向View传递一些数据。,其中TempData的数据在读取一次后会被自动销毁。这几个属性通过yourController=>Controller=>ControllerBase的派生关系而获得。 

    TempData
    //一个TempDataDictionary字典对象
    ViewData
    //一个ViewDataDictionary字典对象
    ViewBag
    //一个dymanic动态类型的对象
    ControllerContext
    //一个ControllerContext对象,此对象封装了具体的Controller对象和RequestContext对象,RequestContext封装了路由数据对象和Http请求上下文

    Controller(控制器抽象基类)

    从ControllerBase派生,通过基架创建出的Controller都自动从Controller派生,此类继承和实现了如下的类型或接口。 

    ControllerBase
    IController //控制器接口,提供Execute同步执行方法
    IAsyncController //控制器接口,提供BeginExecute、EndExecute方法异步执行控制器Controller实现了IAsyncController和IController接口,所以它既可以同步执行也可以异步执行。它的受保护的DisableAsyncSupport属性可以在派生类中重写,此属性如果被设为true则会禁止异步执行。
    IAuthenticationFilter
    IAuthorizationFilter
    IExceptionFilter
    IResultFilter
     

    Controller的激活过程

    前半部分参看:ASP.NET的Http处理管道

    MvcHandler的BeginProcessRequest方法的部分源码: 

    internal IAsyncResult BeginProcessRequest( HttpContextBase httpContext )
    {
        IController controller; 
        IControllerFactory factory;
        //此方法内部会通过ControllerBuilder.Current得到全局的ControllerBuilder对象
        //再调用ControllerBuilder.GetControllerFactory得到控制器工厂
        //最后由控制器工厂创建出路由数据中的Controller所对应的控制器实例
        //最终通过out操作符将控制器传出
        ProcessRequestInit( httpContext, out controller, out factory );
        if(controller is IAsyncController)
        {
            try { /*默认都是以异步方式执行控制器*/ IAsyncResult asyncControllerResult = controller.BeginExecute( requestContext ); controller.EndExecute( asyncController ); }
            finally {/*释放控制器*/ factory.ReleaseController( asyncControllerResult ); }
        }
        else
        {
            try { /*同步执行控制器*/controller.Execute( requestContext ); }
            finally {/*释放控制器*/ factory.ReleaseController( controller ); }
        }        
    }

    ProcessRequestInit方法内部会通过ControllerBuilder.Current得到共享的ControllerBuilder对象,通过ControllerBuilder创建出IControllerFactory,然后通过RequestContext获得当前客户端请求路由地址中的控制器的名称,然后将RequestContext和控制器名称作为参数传递给IControllerFactory的CreatController方法以便该创建出具体的控制器对象。(此处需要注意,如果定义了多个同名的控制器,那么CreatController方法将无法正确创建控制器实例,因为它不知道应该实例化哪一个控制器用于对请求的响应。为此,如果定义了多个同名的控制器则需要在通过RouteCollection的MapRoute方法注册路由时显示地以namespaces指出当前路由中控制器类所在的命名空间,指定了namespaces后,此属性会保存在当前路由的DataTokens字典集合中,通过Route.DataTokens[Namespaces]可以获取控制器所在的命名空间。)

    接下来就是解析CreatController方法如何创建控制器了,如果仅仅凭传递给此方法的ControllerName,它是没有办法创建控制器的,因为路由地址中的控制器名称不区分大小写,所以假设控制器的类名是类似MYControllEr这样的格式,而请求的路由地址中的控制器名称是mycontroller,这样根本无法根据名称new出控制器对象,另外还要考虑一个情况,如果存在区域路由且注册区域路由时你没有显示指定路由的命名空间,那么区域路由的命名空间会默认带有.*后缀,因为控制器类名无法推测和区域路由的命名空间可能被带上了包含.*的后缀,所以不可能new出对象。最终这一解决方案是:DefaultControllerFactory将使用System.Web.Compilation.BuildManager.GetReferencedAssemblies方法载入当前项目引用的所有的程序集,利用反射查找所有的IController类型,将路由地址中的ControllerName拼接上Controller作后缀,然后在反射得到的控制器类型集合中过滤出控制器类型名与ControllerName完全相同的控制器,如果只有一个控制器则可以马上激活它,但因为可能过滤出不止一个控制器,所以还需要进一步按以下三种情况将得到的控制器所在的命名空间与反射得到的控制器类型所在的命名空间进行匹配以便确定用户请求的究竟是哪一个控制器:

    1.如果RouteData.DataTokens["Namespaces"]不是null,则将其与反射得到的控制器类型所在的命名空间进行匹配。匹配成则返回正确的控制器类型,否则返回null。

    2.如果1是null,则测试RouteData.DataTokens["UseNamespaceFallback"]是否是null,如果不是,则说明用户没有禁用路由后续查找,所以可以获取ControllerBuilder的DefaultNamespaces,因为此属性所存储了所有控制器类型所在的命名空间,所以可以将其与反射得到的控制器类型所在的命名空间进行匹配,匹配成则返回正确的控制器类型,否则返回null。

    3.如果1和2都是null,则将拼接了Controller作后缀的ControllerName字符与反射得到的控制器类型列表的每个控制器类型的名称进行匹配,如果得到的结果列表中只存在唯一能匹配的控制器类型则直接返回它,否则返回null。

     

    自定义控制器激活类MyControllerFactory

    namespace MVC
    {
        public class MyControllerFactory : IControllerFactory
        {
            private static List<Type> ControllerTypes = new List<Type> ( );

            //构造函数
            public MyControllerFactory ( )
            {
                var DllList = System.Web.Compilation.BuildManager.GetReferencedAssemblies ( );
                foreach ( Assembly dll in DllList )
                {
                    var IControllerClassList = dll.GetTypes ( ).Where ( t => typeof ( IController ).IsAssignableFrom ( t ) );
                    ControllerTypes.AddRange ( IControllerClassList );
                }
            }

            //requestedNamespace:有以下三种情况
            //1.提供RouteData.DataTokens["Namespaces"]存储的当前路由所在的命名空间
            //2.如果1是null,则测试RouteData.DataTokens["UseNamespaceFallback"]是否是false
            //3.如果2是false,则说明用户没有禁用后续查找,
            //所以可提供ControllerBuilder.Current.DefaultNamespaces的默认命名空间,该命名空间存储了所有控制器类型所在的命名空间
            //targetNamesapce:反射得到的所有的控制器类型所在的命名空间
            //此方法需要在迭代中调用以便完成对命名空间的解析
            private static bool IsNameSpaceMatch ( string requestedNamespace , string targetNamesapce )
            {
                bool IsAreaNamespace = requestedNamespace.EndsWith ( ".*" );
                //如果是区域路由,则取消默认添加的".*"后缀
                requestedNamespace = IsAreaNamespace ? requestedNamespace.Substring ( 0 , requestedNamespace.Length - 2 ) : requestedNamespace;
                //返回两个命名空间的名字是否完全相同
                return string.Equals ( requestedNamespace , targetNamesapce );
            }

            //namespaces:待匹配的命名空间列表
            //controllerTypes:控制器类型列表
            //将控制器类型列表与待匹配的命名空间列表进行匹配
            //返回匹配成功的控制器类型或null
            private Type GetControllerType ( IEnumerable<string> namespaces , IEnumerable<Type> controllerTypes )
            {
                var typeList = controllerTypes.Where ( type => namespaces.Any ( ns => IsNameSpaceMatch ( ns , type.Namespace ) ) );
                int typeListCount = typeList.Count ( );
                return typeListCount == 0 ? null : typeListCount > 1 ? throw new Exception ( "具有多个同名的Controller,无法确定使用哪一个控制器" ) : typeList.ToArray ( ) [ 0 ];
            }

            public Type GetControllerType ( RouteData routeData , string ControllerName )
            {
                //得到控制器
                string ControllerTypeName = ControllerName + "Controller";//将ControllerName拼接上Controller作后缀        
                var types = ControllerTypes.Where ( t => ControllerTypeName.CompareTo ( t.Name ) == 0 );  //在控制器的类型集合中过滤出控制器类型名与ControllerTypeName完全相同的控制器
                if ( types.Count ( ) == 0 ) { return null; }

                //考虑到可能会过滤出多个同名的控制器,所以还需要进一步将路由信息中存储的路由所在的命名空间取出来与控制器所在的命名空间进行匹配
                //从而正确得到一个与Http请求地址所对应的控制器            
                var namesapces = routeData.DataTokens [ "Namespaces" ] == null ? new string [ 0 ] : routeData.DataTokens [ "Namespaces" ] as string [ ]; //获取当前路由信息中的命名空间            
                Type controllerType = GetControllerType ( namesapces , types );
                if ( controllerType != null ) { return controllerType; }


                //如果路由信息并未存储Namespaces(注册路由时可能没有显示地指定namespaces),则判断RouteData.DataTokens[ "UseNamespaceFallback" ]是否允许后续查找
                //如果允许,则继续后续查找,通过ControllerBuilder.Current.DefaultNamespaces获取到所有的控制器类型所在的命名空间列表,然后继续进行匹配
                bool IsFallBack = routeData.DataTokens [ "UseNamespaceFallback" ] != null ? ( bool ) routeData.DataTokens [ "UseNamespaceFallback" ] : false;
                if ( !IsFallBack ) { return null; }
                controllerType = GetControllerType ( ControllerBuilder.Current.DefaultNamespaces , types );
                if ( controllerType != null ) { return controllerType; }

                //如果以上条件都只能得到null,则判断types是否只存在一个与ControllerName匹配的控制器,如果是则返回它
                int typesCount = types.Count ( );
                if ( typesCount > 1 ) { throw new Exception ( "具有多个同名的Controller,无法确定使用哪一个控制器" ); }
                else if ( typesCount == 0 ) { return null; }
                return types.ToArray ( ) [ 0 ];          
            }

            //创建控制器
            public IController CreateController ( RequestContext requestContext , string controllerName )
            {
                Type controllerType = GetControllerType ( requestContext.RouteData , controllerName ); //获取控制器的类型
                if ( controllerType == null ) { return null; }
                return ( IController ) Activator.CreateInstance ( controllerType ); //实例化控制器
            }

            //获取Session会话状态
            public SessionStateBehavior GetControllerSessionBehavior ( RequestContext requestContext , string controllerName )
            {
                Type controllerType = GetControllerType ( requestContext.RouteData , controllerName );
                if ( null == controllerType )
                {
                    return SessionStateBehavior.Default;
                }
                SessionStateAttribute attribute = controllerType.GetCustomAttributes ( true ).OfType<SessionStateAttribute> ( ).FirstOrDefault ( );
                attribute = attribute ?? new SessionStateAttribute ( SessionStateBehavior.Default );
                return attribute.Behavior;
            }

            //释放控制器
            public void ReleaseController ( IController controller )
            {
                IDisposable disposable = controller as IDisposable;
                if ( null != disposable )
                {
                    disposable.Dispose ( );
                }
            }
        }
    }

    完成以上对自定义控制器工厂的创建后,还需要在Global文件中的Application_Start中设置默认的控制器工厂,以便ControllerBuilder能正确创建出自定义的控制器工厂的实例。

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start ( )
        {
            AreaRegistration.RegisterAllAreas ( );
            FilterConfig.RegisterGlobalFilters ( GlobalFilters.Filters );
            RouteConfig.RegisterRoutes ( RouteTable.Routes );
            BundleConfig.RegisterBundles ( BundleTable.Bundles );
            ControllerBuilder.Current.SetControllerFactory ( new MyControllerFactory ( ) ); //动态将默认的DefaultControllerFactory替换掉
        }
    }

    最后在任意控制器的Action中做测试:

    public ActionResult Test()
    {
        ViewBag.msg = "本控制器由自定义的MyControllerFactory激活";
        return View();
    }

    打开浏览器测试:

    通过以上的激活流程,在MvcHandler的BeginProcessRequest方法中就能得到正确控制器类型并new出控制器对象了。接着会调用控制器对象的Execute或BeginExecute方法(同步或异步)执行控制器,执行完毕后,MvcHandler再调用IControllerFactory对象的ReleaseController方法对控制器对象进行释放清理操作。至此,Http请求如何被路由到控制器中,MvcHandler又是如何处理Http请求的,这整套业务流程通过ASP.NET MVC - 路由(最后部分的分析)和上面的代码解析就彻底清晰明朗了。

    默认控制器激活类DefaultControllerFactory

    在ASP.NET MVC中,负责激活控制器的是实现了IControllerFactory接口的DefaultControllerFactory,DefaultControllerFactory激活控制器的方式稍微有别于我们自定义的控制器激活工厂类,虽然IControllerFactory提供了CreateController、GetControllerSessionBehavior和ReleaseController三个方法,上面自定义的MyControllerFactory也是通过实现这三个方法来完成对控制器的激活工作的,DefaultControllerFactory虽然也实现这三个方法,但它实际上是利用了一个实现IControllerActivator的类型激活控制器的,IControllerActivator提供了Create(RequestContext requestContext, Type ControllerType)方法创建控制器实例。IControllerActivator实例作为DefaultControllerFactory构造函数的参数被传递进去用于激活控制器。具体实现细节与MyControllerFactory大同小异,此处不再赘述。

     

    异步控制器

    IIS维护着一个线程池,线程池默认的最大线程数量是5000个,也即5000个线程最多只能同时处理5000个请求,从第5001个请求开始都会进行队列,直到前5000个线程有一个被释放到池中,它才能被用来处理新的请求,所以,当请求量大于5000而新的请求到达时就总是会发生线程阻塞。web程序在高并发下的线程阻塞是必然存在的,但线程阻塞的时间是取决于CPU操作和I/O绑定操作,CPU操作是指如果处理的请求逻辑只是简单的从CPU内存中操作数据,则线程可以很快速的完成任务,所以线程会很快被释放,然后可以接着处理其他被队列的请求。而I/O绑定操作是指从远程(非本机)数据源(数据库等数据存储介质)操作数据,则线程的执行很可能会发生高延迟,这样,线程可能一直阻塞从而导致队列的请求得不到即时的执行。所以两种不同的操作就会产生两种不同的阻塞时间,本机CPU操作是快速完成的,所以采用同步控制器就可以了,而I/O绑定操作可能是高延迟的,所以需要采用异步控制器。异步控制器不会从线程池取线程来处理请求,它会创建一个Task线程任务,由这个任务去执行非本机的远程I/O绑定操作,这样,线程池的每个线程就只用于处理本机的CPU操作,这些线程就可以很快得到释放,然后就可以快速处理其它队列中的请求了。最后注意,因为线程池对线程的分配是基于运算得出的,它是合理分配线程资源,不需要我们手动对线程进行管理,所以对本机CPU操作使用线程池提供的线程是最佳的选择,而非线程池的异步任务是由异步控制器创建的线程,这种线程会消耗不合理的内存分配,所以不要试图为本机CPU操作应用异步控制器,免得浪费内存资源和性能。

    //异步Action
    public async Task<ActionResult> Index()
    {
        var albums = db.Albums.Include(a => a.Artis);
        //linq to EF通过了N多异步方法,这些异步方法不从线程池取线程而是创建新线程,所以这种异步任务对线程池的线程没有任何影响
        List<Album> list = await albums.ToListAsync();
        return View(list);
    }

     

    依赖注入

    将一个控制器所依赖的对象注入到控制器的方式主要有以下三种:

    1.构造器注入:在外部new出B对象,将B作为依赖于它的另一个类型的构造函数的参数,在构造函数中将B赋值给接口成员

    2.属性注入:在外部new出B对象,将B赋值给依赖于它的另一个类型的接口成员

    3.接口注入:略

    Unity框架简介

    这是微软开发的一个轻量级的loC(依赖注入容器),可从Nuget获取程序包。使用这个loC容器来实现依赖注入非常轻松。安装好Unity程序包后,在项目中引入Microsoft.Practices.Unity命名空间,就可以使用这个loC容器了。

    IUnityContainer
    //表示loC容器的接口,应将此接口作为依赖类的成员使用

    UnityContainer
    //表示loC容器的类,此类实现了IUnityContainer接口

    IUnityContainer.Resolve(Type t)
    //接口提供了Resolve方法,此方法由UnityContainer类实现,用于解析一个依赖对象(不是被依赖的对象),将依赖对象注册到容器中。
     
    using Microsoft.Practices.Unity;

    namespace MVC
    {
        public class LocControllerFactory : DefaultControllerFactory
        {
            //为了将已经匹配成功的控制器对象注册到loC中,需要定义一个IUnityContainer,在构造函数中初始化它,然后在GetControllerInstance方法中将控制器注册到loC中
            public IUnityContainer UnityContainer { getprivate set; }
            public LocControllerFactory ( IUnityContainer xUnityContainer )
            {
                UnityContainer = xUnityContainer;
            }
            //重写基类DefaultControllerFactory的GetControllerInstance虚方法      
            protected override IController GetControllerInstance (RequestContext requestContext, Type controllerType )
            {
                return controllerType == null ? null : ( IController ) UnityContainer.Resolve ( controllerType ); //将控制器注册到loC容器中
            }
        }
    }
    假设控制器依赖于一个Employee,为了降低耦合度,所以为控制器定义一个接口成员,让Employee实现这个接口。
    public interface IEmployee
    {
        int GetCount ( );
    }

    public class Employee : IEmployee
    {
        public int GetCount() => 100;
    }
    public class TestController : Controller
    {
        [Dependency//此特性被loC识别后,loC会自动将被依赖对象注入到此属性中
        public IEmployee Proxy { getset; }

        public ActionResult Index()
        {
            ViewBag.count = Proxy.GetCount ( );
            return View();
        }
    }

    public class TestController : Controller
    {
        public IEmployee Proxy { getset; }
        [InjectionMethod//此特性被loC识别后,loC会自动将被依赖对象注入到此方法中
        public void Injection ( IEmployee xProxy ) => Proxy = xProxy;

        public ActionResult Index()
        {
            ViewBag.count = Proxy.GetCount ( );
            return View();
        }
    }

    在Global文件的Application_Start事件中将默认的控制器激活对象替换为LocControllerFactory 

    protected void Application_Start ( )
    {
        AreaRegistration.RegisterAllAreas ( );
        FilterConfig.RegisterGlobalFilters ( GlobalFilters.Filters );
        RouteConfig.RegisterRoutes ( RouteTable.Routes );
        BundleConfig.RegisterBundles ( BundleTable.Bundles );

        UnityContainer unityContainer = new UnityContainer ( );
        unityContainer.RegisterType<IEmployee , Employee> ( ); //今后需求有变,只改这一处的代码,将Employee类换成其它实现过IEmployee接口的类型
        LocControllerFactory LocControllerFactory = new LocControllerFactory ( unityContainer );
        ControllerBuilder.Current.SetControllerFactory ( LocControllerFactory ); //动态将默认的DefaultControllerFactory替换掉
    }

    追加其它实体

    unityContainer.RegisterType<IEmployee , Employees> ( );
    unityContainer.RegisterType<IStudent , Student>( ); //追加注册其它实体

     

     

    ASP.NET - 学习总目录

  • 相关阅读:
    【干货分享】C# 实体类生成工具
    ASP.NET Core部署系列二:发布到CentOS上
    ASP.NET Core部署系列一:发布到IIS上
    微信公众号开发:用户管理
    微信公众号开发:自定义菜单
    像使用SQL一样对List对象集合进行排序
    使用JRebel插件实现SpringBoot应用代码热加载
    Java Stream函数式编程图文详解(二):管道数据处理
    SpringBoot生命周期管理之停掉应用服务几种方法
    本月16日SpringBoot2.2发布,有哪些变化先知晓
  • 原文地址:https://www.cnblogs.com/myrocknroll/p/3260480.html
Copyright © 2011-2022 走看看