自動化CodeReview - ASP.NET Core依賴注入

自動化CodeReview系列目錄html

  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,一般在構造方法裏聲明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}");
                            }
                        }
                    }
                }
            }
        }
    }
}
相關文章
相關標籤/搜索