using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; using System.Text.RegularExpressions; namespace Mini.Common { 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 assemblyNames = new List(); #endregion #region Properties /// The app domain to look for types in. public virtual AppDomain App { get { return AppDomain.CurrentDomain; } } /// Gets or sets whether Nop should iterate assemblies in the app domain when loading Nop types. Loading patterns are applied when loading these assemblies. public bool LoadAppDomainAssemblies { get { return loadAppDomainAssemblies; } set { loadAppDomainAssemblies = value; } } /// Gets or sets assemblies loaded a startup in addition to those loaded in the AppDomain. public IList AssemblyNames { get { return assemblyNames; } set { assemblyNames = value; } } /// Gets the pattern for dlls that we know don't need to be investigated. public string AssemblySkipLoadingPattern { get { return assemblySkipLoadingPattern; } set { assemblySkipLoadingPattern = value; } } /// Gets or sets the pattern for dll that will be investigated. For ease of use this defaults to match all but to increase performance you might want to configure a pattern that includes assemblies and your own. /// If you change this so that Nop assemblies arn't investigated (e.g. by not including something like "^Nop|..." you may break core functionality. public string AssemblyRestrictToLoadingPattern { get { return assemblyRestrictToLoadingPattern; } set { assemblyRestrictToLoadingPattern = value; } } #endregion #region Methods public IEnumerable FindClassesOfType(bool onlyConcreteClasses = true) { return FindClassesOfType(typeof(T), onlyConcreteClasses); } public IEnumerable FindClassesOfType(Type assignTypeFrom, bool onlyConcreteClasses = true) { return FindClassesOfType(assignTypeFrom, GetAssemblies(), onlyConcreteClasses); } public IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses = true) { return FindClassesOfType(typeof(T), assemblies, onlyConcreteClasses); } public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies, bool onlyConcreteClasses = true) { var result = new List(); 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; } /// Gets the assemblies related to the current implementation. /// A list of assemblies that should be loaded by the Nop factory. public virtual IList GetAssemblies() { var addedAssemblyNames = new List(); var assemblies = new List(); if (LoadAppDomainAssemblies) AddAssembliesInAppDomain(addedAssemblyNames, assemblies); AddConfiguredAssemblies(addedAssemblyNames, assemblies); return assemblies; } #endregion #region Utilities /// /// Iterates all assemblies in the AppDomain and if it's name matches the configured patterns add it to our list. /// /// /// private void AddAssembliesInAppDomain(List addedAssemblyNames, List assemblies) { foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { if (Matches(assembly.FullName)) { if (!addedAssemblyNames.Contains(assembly.FullName)) { assemblies.Add(assembly); addedAssemblyNames.Add(assembly.FullName); } } } } /// /// Adds specifically configured assemblies. /// /// /// protected virtual void AddConfiguredAssemblies(List addedAssemblyNames, List assemblies) { foreach (string assemblyName in AssemblyNames) { Assembly assembly = Assembly.Load(assemblyName); if (!addedAssemblyNames.Contains(assembly.FullName)) { assemblies.Add(assembly); addedAssemblyNames.Add(assembly.FullName); } } } /// /// Check if a dll is one of the shipped dlls that we know don't need to be investigated. /// /// /// The name of the assembly to check. /// /// /// True if the assembly should be loaded into Nop. /// public virtual bool Matches(string assemblyFullName) { return !Matches(assemblyFullName, AssemblySkipLoadingPattern) && Matches(assemblyFullName, AssemblyRestrictToLoadingPattern); } /// /// Check if a dll is one of the shipped dlls that we know don't need to be investigated. /// /// /// The assembly name to match. /// /// /// The regular expression pattern to match against the assembly name. /// /// /// True if the pattern matches the assembly name. /// protected virtual bool Matches(string assemblyFullName, string pattern) { return Regex.IsMatch(assemblyFullName, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); } /// /// Makes sure matching assemblies in the supplied folder are loaded in the app domain. /// /// /// The physical path to a directory containing dlls to load in the app domain. /// protected virtual void LoadMatchingAssemblies(string directoryPath) { var loadedAssemblyNames = new List(); foreach (Assembly a in GetAssemblies()) { loadedAssemblyNames.Add(a.FullName); } if (!Directory.Exists(directoryPath)) { return; } 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); } //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()); } } } /// /// Does type implement generic? /// /// /// /// 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 } }