zoukankan      html  css  js  c++  java
  • C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web

    【微信平台,此文仅授权《NCC 开源社区》订阅号发布】

    从前面第四篇开始,进入了实践练习;第五篇实现了实例化一个类型以及对成员方法等的调用。当然,还有一些操作尚将在后面的章节进行介绍。

    因为本系列属于实践练习,所以系列文章可能比较多,内容比较长。要学会一种技术,最好的方法是跟着例子代码写一次,运行调试。

    本篇文章属于阶段练习,将前面学习到的所有知识点进行总结,实现一个依赖注入功能,仿照 ASP.NET Core 访问 API,自动传递参数以及执行方法,最后返回结果。

    本章的代码已上传至 https://gitee.com/whuanle/reflection_and_properties/blob/master/C%23反射与特性(6)设计一个仿ASP.NET Core依赖注入框架.cs


    效果:

    对用户效果

    • 用户能够访问 Controller
    • 用户能够访问 Action
    • 访问 Action 时,传递参数

    程序要求效果

    • 实例化类型
    • 识别类型构造函数类型
    • 根据构造函数类型动态实例化类型并且注入
    • 动态调用合适的重载方法

    1,编写依赖注入框架

    写完后的代码大概是这样的

    笔者直接在 Program 类里面写了,代码量为 200 行左右(包括详细注释、空白隔行)。

    开始编写代码前,请先引入以下命名空间:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    

    在 Program 中,增加以下代码

            private static Assembly assembly = Assembly.GetCallingAssembly();
            private static Type[] types;
            static Program()
            {
                types = assembly.GetTypes();
            }
    

    上面代码的作用是,获取到当前程序的程序集,并且获取元数据信息。

    这是反射第一步

    1.1 路由索引

    ASP.NET Core 中的路由规则十分丰富,我们自定义各种 URL 规则。主要原理是程序在运行时,将 Controller 、Action 的 [route] 等特性收集起来,生成路由表。

    程序执行的基础是类型、方法,ASP.NET Core 中的 Controller 即是 Class,Action 即 Method。

    从前面的学习中,我们了解到,通过反射实例化和调用一个类型的成员,只需要确定类型名称、方法名称即可。

    对于路由表,我们可以假设(不是指ASP.NET Core的原理)用户访问 URL 时,先从路由表中对比,如果有结果,则将对应的 Class 、Method 拿到手,通过反射机制调用实例化类型调用函数。

    这里不实现这么复杂的结构,只实现 Controller-Action 层次的路由。

    1.1.1 判断控制器 Controller 是否存在

    Program 中,添加一个方法,用于判断当前程序集中是否存在此控制器。

            /// <summary>
            /// 判断是否有此控制器,并且返回 Type
            /// </summary>
            /// <param name="controllerName">控制器名称(不带Controller)</param>
            /// <returns></returns>
            private static (bool, Type) IsHasController(string controllerName)
            {
                // 不分大小写
    
                string name = controllerName + "Controller";
                if (!types.Any(x => x.Name.ToLower() == name.ToLower()))
                    return (false, null);
    
                return (true, types.FirstOrDefault(x => x.Name.ToLower() == name.ToLower()));
            }
    

    代码非常简单,而且有 Linq 的加持,几行代码就 OK。

    实现原理:

    判断程序集中是否具有 {var}Controller 命名的类型,例如 HomeController

    如果存在,则获取此控制器的 Type 。

    1.1.2 判断 Action 是否存在

    Action 是在 Controller 里面的(方法在类型里面),所以我们这里只需要判断以下就行。

            /// <summary>
            /// 判断一个控制器中是否具有此方法
            /// </summary>
            /// <param name="type">控制器类型</param>
            /// <param name="actionName">Action名称</param>
            /// <returns></returns>
            private static bool IsHasAction(Type type, string actionName)
            {
                // 不分大小写
    
                return type.GetMethods().Any(x => x.Name.ToLower() == actionName.ToLower());
            }
    

    实现原理:

    判断一个类型中,是否存在 {actionname} 这个方法。

    这里不返回 MethodInfo,而是返回 bool ,是因为考虑到,方法是可以重载的,我们要根据请求时的参数,确定使用哪个方法。

    所以这里只做判断,获取 MethodInfo 的过程在后面。

    1.2 依赖实例化

    意思是,获取一个类型的构造函数中,所有参数信息,并且为每一个类型实现自动创建实例。

    传入参数:

    需要进行依赖注入的类型的 Type。

    返回数据:

    构造函数参数的实例对象列表(反射都是object)。

            /// <summary>
            /// 实例化依赖
            /// </summary>
            /// <param name="type">要被实例化依赖注入的类型</param>
            public static object[] CreateType(Type type)
            {
                // 这里只使用一个构造函数
                ConstructorInfo construct = type.GetConstructors().FirstOrDefault();
    
                // 获取类型的构造函数参数
                ParameterInfo[] paramList = construct.GetParameters();
    
                // 依赖注入的对象列表
                List<object> objectList = new List<object>();
    
                // 为构造函数的每个参数类型,实例化一个类型
                foreach (ParameterInfo item in paramList)
                {
                    //获取参数类型:item.ParameterType.Name
    
                    // 获取程序中,哪个类型实现了 item 的接口
    
                    Type who = types.FirstOrDefault(x => x.GetInterfaces().Any(z => z.Name == item.ParameterType.Name));
    
                    // 实例化
                    object create = Activator.CreateInstance(who, new object[] { });
                    objectList.Add(create);
                }
                return objectList.ToArray();
            }
    

    这里有两个点:

    ① 对于一个类型来说,可能有多个构造函数;

    ② 使用 ASP.NET Core 编写一个控制器时,估计没谁会写两个构造函数吧。。。

    基于以上两点,我们只要一个构造函数就行,不需要考虑很多情况,我们默认:一个控制器只允许定义一个构造函数,不能定义多个构造函数。

    过程实现原理:

    获取到构造函数后,接着获取构造函数中的参数列表(ParameterInfo[])。

    这里又有几个问题

    • 参数是接口类型

    • 参数是抽象类型

    • 参数是正常的 Class 类型

    那么,按照以上划分,要考虑的情况更加多了。这里我们根据依赖倒置原则,我们约定,构造函数中的类型,只允许是接口。

    因为这里没有 IOC 容器,只是简单的反射实现,所以我们不需要考虑那么多情况(200行代码还想怎么样。。。)。

    后面我们查找有哪个类型实现了此接口,就把这个类型实例化做参数传递进去。

    注:后面会持续推出更多实战型教程,敬请期待;可以关注微信订阅号 《NCC 开源社区》,获取最新资讯。

    1.3 实例化类型、依赖注入、调用方法

    目前来到了依赖注入的最后阶段,实例化一个类型、注入依赖、调用方法。

            /// <summary>
            /// 实现依赖注入、调用方法
            /// </summary>
            /// <param name="type">类型</param>
            /// <param name="actionName">方法名称</param>
            /// <param name="paramList">调用方法的参数列表</param>
            /// <returns></returns>
            private static object StartASPNETCORE(Type type, string actionName, params object[] paramList)
            {
                // 获取 Action 重载方法 
                // 名字一样,参数个数一致
                MethodInfo method = type.GetMethods()
                    .FirstOrDefault(x => x.Name.ToLower() == actionName.ToLower()
                    && x.GetParameters().Length == paramList.Length);
    
                // 参数有问题,找不到合适的 Action 重载进行调用
                // 报 405 
                if (method == null)
                    return "405";
    
                // 实例化控制器
    
                // 获取依赖对象
                object[] inject = CreateType(type);
                // 注入依赖,实例化对象
                object example = Activator.CreateInstance(type, inject);
    
                // 执行方法并且返回执行结果
                object result;
                try
                {
                    result = method.Invoke(example, paramList);
                    return result;
                }
                catch
                {
                    // 报 500
                    result = "500";
                    return result;
                }
            }
    

    实现原理:

    通过 CreateType 方法,已经拿到实例化类型的构造函数的参数对象了。

    这里确定调用哪个重载方法的方式,是通过参数的多少,因为这里控制台输入只能获取 string,更加复杂通过参数类型获取重载方法,可以自行另外测试。

    调用一个方法大概以下几个步骤(不分顺序):

    获取类型实例;

    获取类型 Type;

    获取方法 MethodInfo;

    方法的参数对象;

                // 获取依赖对象
                object[] inject = CreateType(type);
                // 注入依赖,实例化对象
                object example = Activator.CreateInstance(type, inject);
    

    上面代码中,就是实现非常简单的依赖注入过程。

    剩下的就是调用方法,通过参数多少去调用相应的重载方法了。

    2,编写控制器和参数类型

    2.1 编写类型

    编写一个接口

        /// <summary>
        /// 接口
        /// </summary>
        public interface ITest
        {
            string Add(string a, string b);
        }
    
    

    实现接口

        /// <summary>
        /// 实现
        /// </summary>
        public class Test : ITest
        {
            public string Add(string a, string b)
            {
                Console.WriteLine("Add方法被执行");
                return a + b;
            }
        }
    

    2.2 实现控制器

    我们按照 ASP.NET Core 写一个控制器的大概形式,实现一个低仿的山寨控制器。

        /// <summary>
        /// 需要自动实例化并且进行依赖注入的类
        /// </summary>
        public class MyClassController
        {
            private ITest _test;
            public MyClassController(ITest test)
            {
                _test = test;
            }
            
            /// <summary>
            /// 这是一个 Action
            /// </summary>
            /// <returns></returns>
            public string Action(string a, string b)
            {
                // 校验http请求的参数
                if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b))
                    return "验证不通过";
                //开始运行
                var result = _test.Add(a, b);
                Console.WriteLine("NCC社区", "牛逼");
                // 响应结果
                return result;
            }
        }
    

    这是常见的依赖注入使用场景:

            private ITest _test;
            public MyClassController(ITest test)
            {
                _test = test;
            }
    

    可以是一个数据库上下文,可以各种类型。

    由于控制台输入获取到的是 string,为了减少麻烦,里面只使用的 Action 方法,参数类型都是 string

    3,实现低配山寨 ASP.NET Core

    好吧,我承认我这跟ASP.NET Core没关系,这个这是一个非常简单的功能。

    主要就是仿照 StartUp ,实现请求流程和数据返回。

            static void Main(string[] args)
            {
    
                while (true)
                {
                    string read = string.Empty;
                    Console.WriteLine("用户你好,你要访问的控制器(不需要带Controller)");
    
                    read = Console.ReadLine();
    
                    // 检查是否具有此控制器并且获取 Type
    
                    var hasController = IsHasController(read);
    
                    // 找不到控制器,报 404 ,让用户重新请求
                    if (!hasController.Item1)
                    {
                        Console.WriteLine("404");
                        continue;
                    }
    
                    Console.WriteLine("控制器存在,请接着输入要访问的 Action");
    
                    read = Console.ReadLine();
    
                    // 检查是否具有此 Action 并且获取 Type
                    bool hasAction = IsHasAction(hasController.Item2, read);
    
                    // 找不到,继续报 404 
                    if (hasAction == false)
                    {
                        Console.WriteLine("404");
                        continue;
                    }
    
                    // 目前为止,URL存在,那么就是传递参数了
    
                    Console.WriteLine("用户你好,URL 存在,请输入参数");
                    Console.WriteLine("输入每个参数按一下回车键,结束输入请输入0再按下回车键");
    
                    // 开始接收用户输入的参数
                    List<object> paramList = new List<object>();
                    while (true)
                    {
                        string param = Console.ReadLine();
                        if (param == "0")
                            break;
                        paramList.Add(param);
                    }
    
                    Console.WriteLine("输入结束,正在发送 http 请求 
    ");
    
                    // 用户的请求已经校验通过并且开始,现在来继续仿 ASP.NET Core 执行
    
                    object response = StartASPNETCORE(hasController.Item2, read, paramList.ToArray());
    
                    Console.WriteLine("执行结果是:");
                    Console.WriteLine(response);
    
    
                    Console.ReadKey();
                }
    

    实现过程和原理:

    • 判断 URL 是否存在(路由)
    • 接收用户输入的参数
    • 依赖注入实现
    • 调用方法,传输参数,返回实现结果
  • 相关阅读:
    【python小练】0020
    【python小练】0017-将xls文件内容写入xml文件中
    【python小练】图片爬虫之BeautifulSoup4
    【python小练】0013
    【python小练】0014题 和 0015 题
    【python小练】0012题
    【python小练】0011题
    【python小练】0010
    【python小练】0005
    2018.09.11python学习第1天
  • 原文地址:https://www.cnblogs.com/whuanle/p/12181308.html
Copyright © 2011-2022 走看看