Microsoft.Toolkit.Win32/Microsoft.Toolkit.Win32.UI..../MetadataProviderDiscovery.cs

193 строки
7.4 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using WUX = Windows.UI.Xaml;
namespace Microsoft.Toolkit.Win32.UI.XamlHost
{
/// <summary>
/// MetadataProviderDiscovery is responsible for loading all metadata providers for custom UWP XAML
/// types. In this implementation, reflection is used at runtime to probe for metadata providers in
/// the working directory, allowing any type that includes metadata (compiled in to a .NET framework
/// assembly) to be used without explicit metadata handling by the application developer. This
/// internal class will be amended or removed when additional type loading support is available.
/// </summary>
internal static class MetadataProviderDiscovery
{
private static readonly List<Type> FilteredTypes = new List<Type>
{
typeof(XamlApplication),
typeof(WUX.Markup.IXamlMetadataProvider)
};
/// <summary>
/// Probes working directory for all available metadata providers
/// </summary>
/// <returns>List of UWP XAML metadata providers</returns>
internal static IEnumerable<WUX.Markup.IXamlMetadataProvider> DiscoverMetadataProviders()
{
// Get all assemblies loaded in app domain and placed side-by-side from all DLL and EXE
var loadedAssemblies = GetAssemblies();
#if NET462
var uniqueAssemblies = new HashSet<Assembly>(loadedAssemblies, EqualityComparerFactory<Assembly>.CreateComparer(
a => a.GetName().FullName.GetHashCode(),
(a, b) => a.GetName().FullName.Equals(b.GetName().FullName, StringComparison.OrdinalIgnoreCase)));
#else
var uniqueAssemblies = new HashSet<Assembly>(loadedAssemblies, EqualityComparerFactory<Assembly>.CreateComparer(
a => a.GetName().FullName.GetHashCode(StringComparison.InvariantCulture),
(a, b) => a.GetName().FullName.Equals(b.GetName().FullName, StringComparison.OrdinalIgnoreCase)));
#endif
// Load all types loadable from the assembly, ignoring any types that could not be resolved due to an issue in the dependency chain
foreach (var assembly in uniqueAssemblies)
{
foreach (var provider in LoadTypesFromAssembly(assembly))
{
yield return provider;
if (typeof(WUX.Application).IsAssignableFrom(provider.GetType()))
{
System.Diagnostics.Debug.WriteLine("Xaml application has been created");
yield break;
}
}
}
}
private static IEnumerable<Assembly> GetAssemblies()
{
yield return Assembly.GetExecutingAssembly();
// Get assemblies already loaded in the current app domain
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
yield return a;
}
// Reflection-based runtime metadata probing
var currentDirectory = new FileInfo(typeof(MetadataProviderDiscovery).Assembly.Location).Directory;
foreach (var assembly in GetAssemblies(currentDirectory, "*.exe"))
{
yield return assembly;
}
foreach (var assembly in GetAssemblies(currentDirectory, "*.dll"))
{
yield return assembly;
}
}
private static IEnumerable<Assembly> GetAssemblies(DirectoryInfo folder, string fileFilter)
{
foreach (var file in folder.EnumerateFiles(fileFilter))
{
Assembly a = null;
try
{
a = Assembly.LoadFrom(file.FullName);
}
catch (FileLoadException)
{
// These exceptions are expected
}
catch (BadImageFormatException)
{
// DLL is not loadable by CLR (e.g. Native)
}
if (a != null)
{
yield return a;
}
}
}
/// <summary>
/// Loads all types from the specified assembly and caches metadata providers
/// </summary>
/// <param name="assembly">Target assembly to load types from</param>
/// <returns>The set of <seealso cref="WUX.Markup.IXamlMetadataProvider"/> found</returns>
private static IEnumerable<WUX.Markup.IXamlMetadataProvider> LoadTypesFromAssembly(Assembly assembly)
{
// Load types inside the executing assembly
foreach (var type in GetLoadableTypes(assembly))
{
// TODO: More type checking here
// Not interface, not abstract, not generic, etc.
if (typeof(WUX.Markup.IXamlMetadataProvider).IsAssignableFrom(type))
{
var provider = (WUX.Markup.IXamlMetadataProvider)Activator.CreateInstance(type);
yield return provider;
}
}
}
// Algorithm from StackOverflow answer here:
// http://stackoverflow.com/questions/7889228/how-to-prevent-reflectiontypeloadexception-when-calling-assembly-gettypes
private static IEnumerable<Type> GetLoadableTypes(Assembly assembly)
{
if (assembly == null)
{
throw new ArgumentNullException(nameof(assembly));
}
try
{
var asmTypes = assembly.DefinedTypes
.Select(t => t.AsType());
var filteredTypes = asmTypes.Where(t => !FilteredTypes.Contains(t));
return filteredTypes;
}
catch (ReflectionTypeLoadException)
{
return Enumerable.Empty<Type>();
}
catch (FileLoadException)
{
return Enumerable.Empty<Type>();
}
}
private static class EqualityComparerFactory<T>
{
private class MyComparer : IEqualityComparer<T>
{
private readonly Func<T, int> _getHashCodeFunc;
private readonly Func<T, T, bool> _equalsFunc;
public MyComparer(Func<T, int> getHashCodeFunc, Func<T, T, bool> equalsFunc)
{
_getHashCodeFunc = getHashCodeFunc;
_equalsFunc = equalsFunc;
}
public bool Equals(T x, T y) => _equalsFunc(x, y);
public int GetHashCode(T obj) => _getHashCodeFunc(obj);
}
public static IEqualityComparer<T> CreateComparer(Func<T, int> getHashCodeFunc, Func<T, T, bool> equalsFunc)
{
if (getHashCodeFunc == null)
{
throw new ArgumentNullException(nameof(getHashCodeFunc));
}
if (equalsFunc == null)
{
throw new ArgumentNullException(nameof(equalsFunc));
}
return new MyComparer(getHashCodeFunc, equalsFunc);
}
}
}
}