zoukankan      html  css  js  c++  java
  • 自动化CodeReview

    自动化CodeReview系列目录

    1. 自动化CodeReview - ASP.NET Core依赖注入
    2. 自动化CodeReview - ASP.NET Core请求参数验证

     我个人比较懒,能自动做的事绝不手动做,最近在用ASP.NET Core写一个项目,过程中会积累一些方便的工具类或框架,分享出来欢迎大家点评。

    如果以后有时间的话,我打算写一个系列的【实现BUG自动检测】,本文将是第一篇。


    如果你使用过ASP.NET Core那么对依赖注入一定不陌生。
    使用流程为:
    1. 先注册Service,有3个方法AddTransient、AddScoped、AddSingleton
    2. 再使用Service,通常在构造方法里声明

    先来说说产生BUG的场景
    BUG场景一:
    有的时候可能因为疏忽忘记注册Service直接就使用了,使用那个Service时会报异常。这种情况项目都是可以编译通过的,是一个不太容易发现的BUG,如果那个Service在测试时没有覆盖到这个BUG就会被带到生产环境

    BUG场景二:
    通常有一些Service我们只希望它在请求作用域内被使用,例如:在服务端持有数据库连接的Service通常都是请求作用域级别的,即:在请求内第一次使用数据库时创建数据库连接,请求内会复用连接,请求结束回收连接。
    对应ASP.NET Core里的注册方式如下:
    services.AddScoped<IDbContext, DbContext>();

    在ASP.NET Core中AddScoped注册的Service在请求结束时会销毁。
    如果你在控制器中直接引用IDbContext一切正常,现在业务需要我们要封装一个用户管理类UserManager,它是单例的,注册代码:
    services.AddScoped<IUserManager, UserManager>();

    在写UserManager类的时候要访问数据库,顺手就引用了IDbContext(正常是不应该这么引用的但是忘记了),因为UserManager是单例会造成IDbContext永远不会释放,进而长期占用一个数据库连接。并且在编译时,运行时都不会报错,很隐蔽的一个BUG


    好了,场景说完了,本文的主角该登场了,解决方式如下:
    在Startup类的ConfigureServices方法最后加入如下代码:

    public void ConfigureServices(IServiceCollection services){
        //此处省略若干代码...
    
        //确保服务依赖的正确性,放到所有注册服务代码后调用
        if (_env.IsDevelopment())
          services.AssertDependencyValid();
    } 

    对于“场景一”此方法会抛出异常:
    throw new InvalidProgramException($"服务 {svceType.FullName} 的构造方法引用了未注册的服务 {paramType.FullName}");

    对于“场景二”此方法会抛出异常:
    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("
    ", 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}");
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    

      

  • 相关阅读:
    JS命名空间的使用
    PHPexcel的用法
    python爬取百度贴吧帖子
    python自动抢票
    int 与 String 与 char 之间的互相转换
    数据库备份和恢复
    Mysql 基础
    Mysql错误:Every derived table must have its own alias
    frameset框架集
    文件的上传(TCP)
  • 原文地址:https://www.cnblogs.com/mondol/p/6271300.html
Copyright © 2011-2022 走看看