NopCommerce源码架构详解-TypeFinder程序集类型自动查找及操作相关源码分析

今天我们来看看NopCommerce源码一个值得借鉴的设计,TypeFinder类型自动查找。

NopCommerce源码架构详解概述

文章目录

概述

NopCommerce源码架构详解-TypeFinder程序集类型自动查找及操作相关源码分析。

内容

今天我们来看看NopCommerce源码一个值得借鉴的设计,TypeFinder类型自动查找。它会自动去程序的bin目录或者AppDomain下查找满足指定类型的的所有类型的集合。比如:

var consumers = typeFinder.FindClassesOfType(typeof(IConsumer<>)).ToList();

上面代码就是查找实现接口所有的类。仅可以查找指定类型,还可以获取全部的程序集,如下:

typeFinder.GetAssemblies().ToArray();

利用以上两点就可以利用IoC容器对查找的类型进行遍历注册。下面是我从nopCommerce源码中找到关于IoC和TypeFinder使用的代码。

builder.RegisterControllers(typeFinder.GetAssemblies().ToArray());
var consumers = typeFinder.FindClassesOfType(typeof(IConsumer<>)).ToList();
foreach (var consumer in consumers)
{
    builder.RegisterType(consumer)
        .As(consumer.FindInterfaces((type, criteria) =>
        {
            var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition());
            return isMatch;
        }, typeof(IConsumer<>)))
        .InstancePerLifetimeScope();
}

从上面我们知道了Nop中TypeFinder的作用,那么它又是怎么实现的呢?下面我就来说说TypeFinder的原理。同样我们来看看这部分相关代码的类图:

2020-04-23-02-25-46

TypeFinder相关的类及作用:

1、ITypeFinder:类型查找抽象接口

2、AppDomainTypeFinder:在当前运行中的AppDomain中查找相关类型的集合

3、WebAppTypeFinder:在当前的web App的bin查找相关类型的集合

4、DependencyRegistrar:依赖注册

5、NopEngine:Nop引擎

接下来我们来看看这些类的核心代码及作用。

Nop.Core.Infrastructure.ITypeFinder

public interface ITypeFinder
{
    IList<Assembly> GetAssemblies();

    IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, bool onlyConcreteClasses = true);

    IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, IEnumerable<Assembly> assemblies, bool onlyConcreteClasses = true);

    IEnumerable<Type> FindClassesOfType<T>(bool onlyConcreteClasses = true);

    IEnumerable<Type> FindClassesOfType<T>(IEnumerable<Assembly> assemblies, bool onlyConcreteClasses = true);
}

Nop.Core.Infrastructure.AppDomainTypeFinder

public class AppDomainTypeFinder : ITypeFinder
{
    #region Fields

    private bool ignoreReflectionErrors = true;
    private bool loadAppDomainAssemblies = true;
    //忽略一些系统程序集及其它特殊程序集
    private string assemblySkipLoadingPattern = "^System|^mscorlib|^Microsoft|^AjaxControlToolkit|^Antlr3|^Autofac|^AutoMapper|^Castle|^ComponentArt|^CppCodeProvider|^DotNetOpenAuth|^EntityFramework|^EPPlus|^FluentValidation|^ImageResizer|^itextsharp|^log4net|^MaxMind|^MbUnit|^MiniProfiler|^Mono.Math|^mvcContrib|^Newtonsoft|^NHibernate|^nunit|^Org.Mentalis|^PerlRegex|^QuickGraph|^Recaptcha|^Remotion|^RestSharp|^Rhino|^Telerik|^Iesi|^TestDriven|^TestFu|^UserAgentStringLibrary|^VJSharpCodeProvider|^WebActivator|^WebDev|^WebGrease";
    private string assemblyRestrictToLoadingPattern = ".*";
    private IList<string> assemblyNames = new List<string>();

    #endregion

    #region Methods

    public IEnumerable<Type> FindClassesOfType<T>(bool onlyConcreteClasses = true)
    {
        return FindClassesOfType(typeof(T), onlyConcreteClasses);
    }

    public IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, bool onlyConcreteClasses = true)
    {
        return FindClassesOfType(assignTypeFrom, GetAssemblies(), onlyConcreteClasses);
    }

    public IEnumerable<Type> FindClassesOfType<T>(IEnumerable<Assembly> assemblies, bool onlyConcreteClasses = true)
    {
        return FindClassesOfType(typeof (T), assemblies, onlyConcreteClasses);
    }

    public IEnumerable<Type> FindClassesOfType(Type assignTypeFrom,
    IEnumerable<Assembly> assemblies, bool onlyConcreteClasses = true)
    {
        var result = new List<Type>();
        try
        {
            foreach (var a in assemblies)
            {
                Type[] types = null;
                try
                {
                    types = a.GetTypes();
                }
                catch
                {
                    //Entity Framework 6 doesn't allow getting types (throws an exception)
                    if (!ignoreReflectionErrors)
                    {
                        throw;
                    }
                }
                if (types != null)
                {
                    foreach (var t in types)
                    {
                        if (assignTypeFrom.IsAssignableFrom(t) 
                        || (assignTypeFrom.IsGenericTypeDefinition && DoesTypeImplementOpenGeneric(t, assignTypeFrom)))
                        {
                            if (!t.IsInterface)
                            {
                                if (onlyConcreteClasses)
                                {
                                    if (t.IsClass && !t.IsAbstract)
                                    {
                                        result.Add(t);
                                    }
                                }
                                else
                                {
                                    result.Add(t);
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (ReflectionTypeLoadException ex)
        {
            var msg = string.Empty;
            foreach (var e in ex.LoaderExceptions)
                msg += e.Message + Environment.NewLine;

            var fail = new Exception(msg, ex);
            Debug.WriteLine(fail.Message, fail);

            throw fail;
        }
        return result;
    }

    /// <summary>获取指定类型的所有实现类</summary>
    /// <returns>A list of assemblies that should be loaded by the Nop factory.</returns>
    public virtual IList<Assembly> GetAssemblies()
    {
        var addedAssemblyNames = new List<string>();
        var assemblies = new List<Assembly>();

        if (LoadAppDomainAssemblies)
            AddAssembliesInAppDomain(addedAssemblyNames, assemblies);
        AddConfiguredAssemblies(addedAssemblyNames, assemblies);

        return assemblies;
    }

    #endregion

    #region Utilities

    /// <summary>
    /// 遍历AppDomain下的所有程序集并与指定类型进行比较,如果匹配就加入要返回的List中
    /// </summary>
    /// <param name="addedAssemblyNames"></param>
    /// <param name="assemblies"></param>
    private void AddAssembliesInAppDomain(List<string> addedAssemblyNames, List<Assembly> assemblies)
    {
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            if (Matches(assembly.FullName))
            {
                if (!addedAssemblyNames.Contains(assembly.FullName))
                {
                    assemblies.Add(assembly);
                    addedAssemblyNames.Add(assembly.FullName);
                }
            }
        }
    }

    /// <summary>
    /// 添加特定程序集到到集合中
    /// </summary>
    /// <param name="addedAssemblyNames"></param>
    /// <param name="assemblies"></param>
    protected virtual void AddConfiguredAssemblies(List<string> addedAssemblyNames, List<Assembly> assemblies)
    {
        foreach (string assemblyName in AssemblyNames)
        {
            Assembly assembly = Assembly.Load(assemblyName);
            if (!addedAssemblyNames.Contains(assembly.FullName))
            {
                assemblies.Add(assembly);
                addedAssemblyNames.Add(assembly.FullName);
            }
        }
    }

    /// <summary>
    /// Check if a dll is one of the shipped dlls that we know don't need to be investigated.
    /// </summary>
    /// <param name="assemblyFullName">
    /// The name of the assembly to check.
    /// </param>
    /// <returns>
    /// True if the assembly should be loaded into Nop.
    /// </returns>
    public virtual bool Matches(string assemblyFullName)
    {
        return !Matches(assemblyFullName, AssemblySkipLoadingPattern)
               && Matches(assemblyFullName, AssemblyRestrictToLoadingPattern);
    }

    /// <summary>
    /// Check if a dll is one of the shipped dlls that we know don't need to be investigated.
    /// </summary>
    /// <param name="assemblyFullName">
    /// The assembly name to match.
    /// </param>
    /// <param name="pattern">
    /// The regular expression pattern to match against the assembly name.
    /// </param>
    /// <returns>
    /// True if the pattern matches the assembly name.
    /// </returns>
    protected virtual bool Matches(string assemblyFullName, string pattern)
    {
        return Regex.IsMatch(assemblyFullName, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
    }

    /// <summary>
    /// Makes sure matching assemblies in the supplied folder are loaded in the app domain.
    /// </summary>
    /// <param name="directoryPath">
    /// The physical path to a directory containing dlls to load in the app domain.
    /// </param>
    protected virtual void LoadMatchingAssemblies(string directoryPath)
    {
        var loadedAssemblyNames = new List<string>();
        foreach (Assembly a in GetAssemblies())
        {
            loadedAssemblyNames.Add(a.FullName);
        }

        if (!Directory.Exists(directoryPath))
        {
            return;
        }
                //获取指定目录下以.dll为结尾的文件
        foreach (string dllPath in Directory.GetFiles(directoryPath, "*.dll"))
        {
            try
            {
                var an = AssemblyName.GetAssemblyName(dllPath);
                if (Matches(an.FullName) && !loadedAssemblyNames.Contains(an.FullName))
                {
                    App.Load(an);//加载程序集到当前AppDomain中
                }

                //old loading stuff
                //Assembly a = Assembly.ReflectionOnlyLoadFrom(dllPath);
                //if (Matches(a.FullName) && !loadedAssemblyNames.Contains(a.FullName))
                //{
                //    App.Load(a.FullName);
                //}
            }
            catch (BadImageFormatException ex)
            {
                Trace.TraceError(ex.ToString());
            }
        }
    }

    /// <summary>
    /// Does type implement generic?
    /// </summary>
    /// <param name="type"></param>
    /// <param name="openGeneric"></param>
    /// <returns></returns>
    protected virtual bool DoesTypeImplementOpenGeneric(Type type, Type openGeneric)
    {
        try
        {
            var genericTypeDefinition = openGeneric.GetGenericTypeDefinition();
            foreach (var implementedInterface in type.FindInterfaces((objType, objCriteria) => true, null))
            {
                if (!implementedInterface.IsGenericType)
                    continue;

                var isMatch = genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition());
                return isMatch;
            }
            return false;
        }catch
        {
            return false;
        }
    }

    #endregion
}

Nop.Core.Infrastructure.WebAppTypeFinder

WebAppTypeFinder是继承于类AppDomainTypeFinder

public class WebAppTypeFinder : AppDomainTypeFinder
{
    #region Fields

    private bool _ensureBinFolderAssembliesLoaded = true;
    private bool _binFolderAssembliesLoaded = false;

    #endregion

    #region Ctor

    public WebAppTypeFinder(NopConfig config)
    {
        this._ensureBinFolderAssembliesLoaded = config.DynamicDiscovery;
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets or sets wether assemblies in the bin folder of the web application should be specificly checked for beeing loaded on application load. This is need in situations where plugins need to be loaded in the AppDomain after the application been reloaded.
    /// </summary>
    public bool EnsureBinFolderAssembliesLoaded
    {
        get { return _ensureBinFolderAssembliesLoaded; }
        set { _ensureBinFolderAssembliesLoaded = value; }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Gets a physical disk path of \Bin directory
    /// </summary>
    /// <returns>The physical path. E.g. "c:\inetpub\wwwroot\bin"</returns>
    public virtual string GetBinDirectory()
    {
        if (HostingEnvironment.IsHosted)
        {
            //hosted
            return HttpRuntime.BinDirectory;
        }
        else
        {
            //not hosted. For example, run either in unit tests
            return AppDomain.CurrentDomain.BaseDirectory;
        }
    }

    public override IList<Assembly> GetAssemblies()
    {
        if (this.EnsureBinFolderAssembliesLoaded && !_binFolderAssembliesLoaded)
        {
            _binFolderAssembliesLoaded = true;
            string binPath = GetBinDirectory();
            //binPath = _webHelper.MapPath("~/bin");
            LoadMatchingAssemblies(binPath);
        }

        return base.GetAssemblies();
    }

    #endregion
}

在Nop.Core.Infrastructure.NopEngine类中有以下方法:

protected virtual void RegisterDependencies(NopConfig config)
{
    var builder = new ContainerBuilder();
    var container = builder.Build();

    //we create new instance of ContainerBuilder
    //because Build() or Update() method can only be called once on a ContainerBuilder.

    //dependencies
    var typeFinder = new WebAppTypeFinder(config);
    builder = new ContainerBuilder();
    builder.RegisterInstance(config).As<NopConfig>().SingleInstance();
    builder.RegisterInstance(this).As<IEngine>().SingleInstance();
        //ITypeFinder接口用WebAppTypeFinder的单例对象注册绑定
        builder.RegisterInstance(typeFinder).As<ITypeFinder>().SingleInstance();
    builder.Update(container);

    //register dependencies provided by other assemblies
    builder = new ContainerBuilder();
    var drTypes = typeFinder.FindClassesOfType<IDependencyRegistrar>();
    var drInstances = new List<IDependencyRegistrar>();
    foreach (var drType in drTypes)
        drInstances.Add((IDependencyRegistrar) Activator.CreateInstance(drType));
    //sort
    drInstances = drInstances.AsQueryable().OrderBy(t => t.Order).ToList();
    foreach (var dependencyRegistrar in drInstances)
        dependencyRegistrar.Register(builder, typeFinder);
    builder.Update(container);

    this._containerManager = new ContainerManager(container);

    //set dependency resolver
    DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}

可以看到上面 typeFinder.FindClassesOfType(),查找依赖注入抽象接口的IDependencyRegistrar的全部实现类型的集合,并依次调用其方法Register。

Nop依赖注入相关原理分析看看我以前的文章NopCommerce源码架构详解-Autofac依赖注入分析

同样我们在Nop.Web.Framework.DependencyRegistrar看到下面使用ITypeFinder注册依赖对象,如下:

public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder)
{
    //...省略其它代码

    //controllers
    builder.RegisterControllers(typeFinder.GetAssemblies().ToArray());

    //...省略其它代码

    //Register event consumers
    var consumers = typeFinder.FindClassesOfType(typeof(IConsumer<>)).ToList();
    foreach (var consumer in consumers)
    {
        builder.RegisterType(consumer)
            .As(consumer.FindInterfaces((type, criteria) =>
            {
                var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition());
                return isMatch;
            }, typeof(IConsumer<>)))
            .InstancePerLifetimeScope();
    }
}
原文出处:蓝狐软件工作室【蓝狐】

原文链接:http://m.lanhusoft.com/Article/349.html

本文观点不代表 .Net中文网 立场,转载请联系原作者。

发表评论

登录后才能评论