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

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

效果:

对用户效果

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


程序要求效果

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

微信图片_20220503102134.gif


1,编写依赖注入框架


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

微信图片_20220503102138.png


笔者直接在 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 是否存在(路由)
  • 接收用户输入的参数
  • 依赖注入实现
  • 调用方法,传输参数,返回实现结果
相关文章
|
14天前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
36 5
|
2月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
60 4
|
2月前
|
SQL 安全 PHP
PHP 自发布以来一直在 Web 开发领域占据重要地位,PHP 8 更是带来了属性、刚性类型等新特性。
【10月更文挑战第1天】PHP 自问世以来,凭借其易用性和灵活性,在 Web 开发领域迅速崛起。从简单的网页脚本语言逐步演进为支持面向对象编程的现代语言,尤其自 PHP 5.3 引入命名空间后,代码组织和维护变得更加高效。PHP 7 的性能优化和 PHP 8 的新特性(如属性和刚性类型)进一步巩固了其地位。框架如 Laravel、Symfony、Yii2 和 CodeIgniter 等简化了开发流程,提高了效率和安全性。
54 2
|
27天前
|
开发框架 .NET 程序员
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
Autofac 是一个轻量级的依赖注入框架,专门为 .NET 应用程序量身定做,它就像是你代码中的 "魔法师",用它来管理对象的生命周期,让你的代码更加模块化、易于测试和维护
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
|
11天前
|
开发框架 监控 .NET
C#进阶-ASP.NET WebForms调用ASMX的WebService接口
通过本文的介绍,希望您能深入理解并掌握ASP.NET WebForms中调用ASMX WebService接口的方法和技巧,并在实际项目中灵活运用这些技术,提高开发效率和应用性能。
30 5
|
1月前
|
编译器 C# 开发者
C# 9.0 新特性解析
C# 9.0 是微软在2020年11月随.NET 5.0发布的重大更新,带来了一系列新特性和改进,如记录类型、初始化器增强、顶级语句、模式匹配增强、目标类型的新表达式、属性模式和空值处理操作符等,旨在提升开发效率和代码可读性。本文将详细介绍这些新特性,并提供代码示例和常见问题解答。
48 7
C# 9.0 新特性解析
|
3月前
|
前端开发
【前端web入门第四天】02 CSS三大特性+背景图
本文详细介绍了CSS的三大特性:继承性、层叠性和优先级,并深入讲解了背景图的相关属性,包括背景属性、背景图的平铺方式、位置设定、缩放、固定以及复合属性。其中,继承性指子元素自动继承父元素的文字控制属性;层叠性指相同属性后定义覆盖前定义,不同属性可叠加;优先级涉及选择器权重,包括行内样式、ID选择器等。背景图部分则通过具体示例展示了如何设置背景图像的位置、大小及固定方式等。
267 91
|
1月前
|
C# 开发者
C# 10.0 新特性解析
C# 10.0 在性能、可读性和开发效率方面进行了多项增强。本文介绍了文件范围的命名空间、记录结构体、只读结构体、局部函数的递归优化、改进的模式匹配和 lambda 表达式等新特性,并通过代码示例帮助理解这些特性。
36 2
|
3月前
|
编译器 C# Android开发
震惊!Uno Platform 与 C# 最新特性的完美融合,你不可不知的跨平台开发秘籍!
Uno Platform 是一个强大的跨平台应用开发框架,支持 Windows、macOS、iOS、Android 和 WebAssembly,采用 C# 和 XAML 进行编程。C# 作为其核心语言,持续推出新特性,如可空引用类型、异步流、记录类型和顶级语句等,极大地提升了开发效率。要在 Uno Platform 中使用最新 C# 特性,需确保开发环境支持相应版本,并正确配置编译器选项。通过示例展示了如何在 Uno Platform 中应用可空引用类型、异步流、记录类型及顶级语句等功能,帮助开发者更好地构建高效、优质的跨平台应用。
238 59
|
2月前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
44 1