C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web
【微信平台,此文仅授权《NCC 开源社区》订阅号发布】
从前面第四篇开始,进入了实践练习;第五篇实现了实例化一个类型以及对成员方法等的调用。当然,还有一些操作尚将在后面的章节进行介绍。
因为本系列属于实践练习,所以系列文章可能比较多,内容比较长。要学会一种技术,最好的方法是跟着例子代码写一次,运行调试。
本篇文章属于阶段练习,将前面学习到的所有知识点进行总结,实现一个依赖注入功能,仿照 ASP.NET Core 访问 API,自动传递参数以及执行方法,最后返回结果。
效果:
对用户效果
- 用户能够访问 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的更多相关文章
- [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架
在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类 ...
- Objection, 一个轻量级的Objective-C依赖注入框架
简介 项目主页:https://github.com/atomicobject/objection 实例下载: https://github.com/ios122/ios122 Objection 是 ...
- Web API(六):使用Autofac实现依赖注入
在这一篇文章将会讲解如何在Web API2中使用Autofac实现依赖注入. 一.创建实体类库 1.创建单独实体类 创建DI.Entity类库,用来存放所有的实体类,新建用户实体类,其结构如下: us ...
- 如何在Visual Studio 2017中使用C# 7+语法 构建NetCore应用框架之实战篇(二):BitAdminCore框架定位及架构 构建NetCore应用框架之实战篇系列 构建NetCore应用框架之实战篇(一):什么是框架,如何设计一个框架 NetCore入门篇:(十二)在IIS中部署Net Core程序
如何在Visual Studio 2017中使用C# 7+语法 前言 之前不知看过哪位前辈的博文有点印象C# 7控制台开始支持执行异步方法,然后闲来无事,搞着,搞着没搞出来,然后就写了这篇博文,不 ...
- 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文
阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...
- C#反射与特性(七):自定义特性以及应用
目录 1,属性字段的赋值和读值 2,自定义特性和特性查找 2.1 特性规范和自定义特性 2.2 检索特性 3,设计一个数据验证工具 3.1 定义抽象验证特性类 3.2 实现多个自定义验证特性 3.3 ...
- .NET基础拾遗(4)委托、事件、反射与特性
Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...
- 一个仿windows泡泡屏保的实现
一个仿windows泡泡屏保的实现 有天看到有人在百度知道上问windows 泡泡屏保该怎么用C#做,一时有趣,就做了一个出来,对于其中几个要点总结如下: 一,屏保程序的制作要求 屏保程序的扩展名是. ...
- 设计一个较好的框架的难点之一--API兼容性的设计
设计一个好的框架和设计一个好的软件一样,需要考虑的方面很多,比如扩展性.性能.用户体验.稳健性等等,视不同的场景,每个点都可能导致成败,但他们通常并不是老板们关心的,因为在大部分情况下,他们通常都没有 ...
随机推荐
- Python 的经典入门书籍
实python非常适合初学者入门,上手很容易.我就是完全通过网上资源学了python的.最大的是3点经验:1.找一本浅显易懂,例程比较好的教程,从头到尾看下去.不要看很多本,专注于一本.把里面的例程都 ...
- Python的内置方法,abs,all,any,basestring,bin,bool,bytearray,callable,chr,cmp,complex,divmod
Python的内置方法 abs(X):返回一个数的绝对值,X可以是一个整数,长整型,或者浮点数,如果X是一个复数,此方法返回此复数的绝对值(此复数与它的共轭复数的乘积的平方根) >>> ...
- Cisco 交换机笔记
最近使用 Cisco L3(C3560X), L2(2960) 交换机搭建了 VLAN 环境,其中包括了 VLAN 的配置, VLAN 间的路由等,在此写篇笔记记录下. VLAN 结构 L3 Swit ...
- H3C 配置帧中继交换
- 获取exe和dll里面的资源
有时候需要仿照另一个程序实现一些对话框,比较笨的办法是打开那个程序,照着样子自己在VC里面画啊画.这样的效率实在有点低. 现在有很多工具可以从exe和dll里面取出图片.图片.字符串.对话框等资源.比 ...
- Ralasafe
引用:http://www.baike.com/wiki/Ralasafe Ralasafe 是用Java编写的开源(MIT协议)访问控制中间件.它能够轻松处理登录控制.URL权限控制和(业务级)数据 ...
- H3C 路由优先级
- P1028 过河问题
题目描述 为了躲避黑暗大魔王的追杀,zifeiy与他的伙伴们共N人连夜逃出了黑暗城堡,他们走到一条河的东岸边,想要过河到西岸.而东岸边有一条小船. 船太小了,一次只能乘坐两人.每个人都有一个渡河时间T ...
- P1006 输出第二个整数
题目描述 输入三个整数,整数之间由一个空格分隔,整数是32位有符号整数.把第二个输入的整数输出. 输入格式 输入三个整数,整数之间由一个空格分隔,整数是32位有符号整数. 输出格式 输出输入的三个整数 ...
- linux PCI 寻址
每个 PCI 外设有一个总线号, 一个设备号, 一个功能号标识. PCI 规范允许单个系统占 用多达 256 个总线, 但是因为 256 个总线对许多大系统是不够的, Linux 现在支持 PCI 域 ...