C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web

内容纲要

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

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

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

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

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

《C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web》

效果:

对用户效果

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

程序要求效果

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

《C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web》

1,编写依赖注入框架

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

《C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web》

笔者直接在 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 请求 \n");

                // 用户的请求已经校验通过并且开始,现在来继续仿 ASP.NET Core 执行

                object response = StartASPNETCORE(hasController.Item2, read, paramList.ToArray());

                Console.WriteLine("执行结果是:");
                Console.WriteLine(response);

                Console.ReadKey();
            }

实现过程和原理:

  • 判断 URL 是否存在(路由)
  • 接收用户输入的参数
  • 依赖注入实现
  • 调用方法,传输参数,返回实现结果
点赞
  1. Hermine说道:

    Hi there, I found your blog by means of Google whilst looking for a related topic,
    your website got here up, it looks great. I've bookmarked it in my google bookmarks.

    Hi there, just became alert to your blog through Google, and located that it's really informative.
    I'm going to watch out for brussels. I will appreciate in the
    event you continue this in future. Lots of folks will be benefited
    from your writing. Cheers!

  2. Josephmig说道:

    对不起,这些不属于主题内容,我在考虑做一个多信息的高中学生的网站。如果你知道我在哪里可以找到有关的信息,请告诉我。
    优质写作服务

  3. Josephmig说道:

    选学校论文撰写 公司建议的论文作者是很难:-( 但你 可以得到 100% 免费 的样章,并学会相关的知识!
    и‹±иЇ­дЅњж–‡

发表评论

电子邮件地址不会被公开。 必填项已用*标注