自动化CodeReview系列目录html
我我的比较懒,能自动作的事毫不手动作,最近在用ASP.NET Core写一个项目,过程当中会积累一些方便的工具类或框架,分享出来欢迎你们点评。数据库
若是之后有时间的话,我打算写一个系列的【实现BUG自动检测】,本文将是第一篇。框架
若是你使用过ASP.NET Core那么对依赖注入必定不陌生。
使用流程为:
1. 先注册Service,有3个方法AddTransient、AddScoped、AddSingleton
2. 再使用Service,一般在构造方法里声明ide
先来讲说产生BUG的场景
BUG场景一:
有的时候可能由于疏忽忘记注册Service直接就使用了,使用那个Service时会报异常。这种状况项目都是能够编译经过的,是一个不太容易发现的BUG,若是那个Service在测试时没有覆盖到这个BUG就会被带到生产环境工具
BUG场景二:
一般有一些Service咱们只但愿它在请求做用域内被使用,例如:在服务端持有数据库链接的Service一般都是请求做用域级别的,即:在请求内第一次使用数据库时建立数据库链接,请求内会复用链接,请求结束回收链接。
对应ASP.NET Core里的注册方式以下:
services.AddScoped<IDbContext, DbContext>();post
在ASP.NET Core中AddScoped注册的Service在请求结束时会销毁。
若是你在控制器中直接引用IDbContext一切正常,如今业务须要咱们要封装一个用户管理类UserManager,它是单例的,注册代码:
services.AddScoped<IUserManager, UserManager>();测试
在写UserManager类的时候要访问数据库,顺手就引用了IDbContext(正常是不该该这么引用的可是忘记了),由于UserManager是单例会形成IDbContext永远不会释放,进而长期占用一个数据库链接。而且在编译时,运行时都不会报错,很隐蔽的一个BUGthis
好了,场景说完了,本文的主角该登场了,解决方式以下:
在Startup类的ConfigureServices方法最后加入以下代码:spa
public void ConfigureServices(IServiceCollection services){ //此处省略若干代码... //确保服务依赖的正确性,放到全部注册服务代码后调用 if (_env.IsDevelopment()) services.AssertDependencyValid(); }
对于“场景一”此方法会抛出异常:
throw new InvalidProgramException($"服务 {svceType.FullName} 的构造方法引用了未注册的服务 {paramType.FullName}");htm
对于“场景二”此方法会抛出异常:
throw new InvalidProgramException($"Singleton的服务 {svceType.FullName} 的构造方法引用了Scoped的服务 {paramType.FullName}");
您能够根据异常的提示找到具体有问题的类并修改之
完整代码以下:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Resources; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.DependencyInjection { public static class MondolServiceCollectionExtensions { /// <summary> /// DUMP服务列表 /// </summary> public static string Dump(this IServiceCollection services) { var sevList = new List<Tuple<string, string>>(); foreach (var sev in services) { sevList.Add(new Tuple<string, string>(sev.Lifetime.ToString(), sev.ServiceType.FullName)); } sevList.Sort((x, y) => { var cRs = string.CompareOrdinal(x.Item1, y.Item1); return cRs != 0 ? cRs : string.CompareOrdinal(x.Item2, y.Item2); }); return string.Join("\r\n", sevList.Select(p => $"{p.Item2} - {p.Item1}")); } /// <summary> /// 确保当前注册服务的依赖关系是正确的 /// </summary> public static void AssertDependencyValid(this IServiceCollection services) { var ignoreTypes = new[] { "Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler", "Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperDescriptorResolver" }; foreach (var svce in services) { if (svce.Lifetime == ServiceLifetime.Singleton) { //确保Singleton的服务不能依赖Scoped的服务 if (svce.ImplementationType != null) { var svceType = svce.ImplementationType; if (ignoreTypes.Contains(svceType.FullName)) continue; var ctors = svceType.GetConstructors(); foreach (var ctor in ctors) { var paramLst = ctor.GetParameters(); foreach (var param in paramLst) { var paramType = param.ParameterType; var paramTypeInfo = paramType.GetTypeInfo(); if (paramTypeInfo.IsGenericType) { if (paramType.ToString().StartsWith("System.Collections.Generic.IEnumerable`1")) { paramType = paramTypeInfo.GetGenericArguments().First(); paramTypeInfo = paramType.GetTypeInfo(); } } if (paramType == typeof(IServiceProvider)) continue; ServiceDescriptor pSvce; if (paramTypeInfo.IsGenericType) { //泛型采用模糊识别,可能有遗漏 var prefix = Regex.Match(paramType.ToString(), @"^[^`]+`\d+\[").Value; pSvce = services.FirstOrDefault(p => p.ServiceType.ToString().StartsWith(prefix)); } else { pSvce = services.FirstOrDefault(p => p.ServiceType == paramType); } if (pSvce == null) throw new InvalidProgramException($"服务 {svceType.FullName} 的构造方法引用了未注册的服务 {paramType.FullName}"); if (pSvce.Lifetime == ServiceLifetime.Scoped) throw new InvalidProgramException($"Singleton的服务 {svceType.FullName} 的构造方法引用了Scoped的服务 {paramType.FullName}"); } } } } } } } }