【微信平台,此文仅授权《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 请求 \n"); // 用户的请求已经校验通过并且开始,现在来继续仿 ASP.NET Core 执行 object response = StartASPNETCORE(hasController.Item2, read, paramList.ToArray()); Console.WriteLine("执行结果是:");
Console.WriteLine(response); Console.ReadKey();
}

实现过程和原理:

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

C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web的更多相关文章

  1. [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架

    在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类 ...

  2. Objection, 一个轻量级的Objective-C依赖注入框架

    简介 项目主页:https://github.com/atomicobject/objection 实例下载: https://github.com/ios122/ios122 Objection 是 ...

  3. Web API(六):使用Autofac实现依赖注入

    在这一篇文章将会讲解如何在Web API2中使用Autofac实现依赖注入. 一.创建实体类库 1.创建单独实体类 创建DI.Entity类库,用来存放所有的实体类,新建用户实体类,其结构如下: us ...

  4. 如何在Visual Studio 2017中使用C# 7+语法 构建NetCore应用框架之实战篇(二):BitAdminCore框架定位及架构 构建NetCore应用框架之实战篇系列 构建NetCore应用框架之实战篇(一):什么是框架,如何设计一个框架 NetCore入门篇:(十二)在IIS中部署Net Core程序

    如何在Visual Studio 2017中使用C# 7+语法   前言 之前不知看过哪位前辈的博文有点印象C# 7控制台开始支持执行异步方法,然后闲来无事,搞着,搞着没搞出来,然后就写了这篇博文,不 ...

  5. 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文

    阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...

  6. C#反射与特性(七):自定义特性以及应用

    目录 1,属性字段的赋值和读值 2,自定义特性和特性查找 2.1 特性规范和自定义特性 2.2 检索特性 3,设计一个数据验证工具 3.1 定义抽象验证特性类 3.2 实现多个自定义验证特性 3.3 ...

  7. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  8. 一个仿windows泡泡屏保的实现

    一个仿windows泡泡屏保的实现 有天看到有人在百度知道上问windows 泡泡屏保该怎么用C#做,一时有趣,就做了一个出来,对于其中几个要点总结如下: 一,屏保程序的制作要求 屏保程序的扩展名是. ...

  9. 设计一个较好的框架的难点之一--API兼容性的设计

    设计一个好的框架和设计一个好的软件一样,需要考虑的方面很多,比如扩展性.性能.用户体验.稳健性等等,视不同的场景,每个点都可能导致成败,但他们通常并不是老板们关心的,因为在大部分情况下,他们通常都没有 ...

随机推荐

  1. Python 的经典入门书籍

    实python非常适合初学者入门,上手很容易.我就是完全通过网上资源学了python的.最大的是3点经验:1.找一本浅显易懂,例程比较好的教程,从头到尾看下去.不要看很多本,专注于一本.把里面的例程都 ...

  2. Python的内置方法,abs,all,any,basestring,bin,bool,bytearray,callable,chr,cmp,complex,divmod

    Python的内置方法 abs(X):返回一个数的绝对值,X可以是一个整数,长整型,或者浮点数,如果X是一个复数,此方法返回此复数的绝对值(此复数与它的共轭复数的乘积的平方根) >>> ...

  3. Cisco 交换机笔记

    最近使用 Cisco L3(C3560X), L2(2960) 交换机搭建了 VLAN 环境,其中包括了 VLAN 的配置, VLAN 间的路由等,在此写篇笔记记录下. VLAN 结构 L3 Swit ...

  4. H3C 配置帧中继交换

  5. 获取exe和dll里面的资源

    有时候需要仿照另一个程序实现一些对话框,比较笨的办法是打开那个程序,照着样子自己在VC里面画啊画.这样的效率实在有点低. 现在有很多工具可以从exe和dll里面取出图片.图片.字符串.对话框等资源.比 ...

  6. Ralasafe

    引用:http://www.baike.com/wiki/Ralasafe Ralasafe 是用Java编写的开源(MIT协议)访问控制中间件.它能够轻松处理登录控制.URL权限控制和(业务级)数据 ...

  7. H3C 路由优先级

  8. P1028 过河问题

    题目描述 为了躲避黑暗大魔王的追杀,zifeiy与他的伙伴们共N人连夜逃出了黑暗城堡,他们走到一条河的东岸边,想要过河到西岸.而东岸边有一条小船. 船太小了,一次只能乘坐两人.每个人都有一个渡河时间T ...

  9. P1006 输出第二个整数

    题目描述 输入三个整数,整数之间由一个空格分隔,整数是32位有符号整数.把第二个输入的整数输出. 输入格式 输入三个整数,整数之间由一个空格分隔,整数是32位有符号整数. 输出格式 输出输入的三个整数 ...

  10. linux PCI 寻址

    每个 PCI 外设有一个总线号, 一个设备号, 一个功能号标识. PCI 规范允许单个系统占 用多达 256 个总线, 但是因为 256 个总线对许多大系统是不够的, Linux 现在支持 PCI 域 ...