Skip to content

Commit fe15ecf

Browse files
committed
Nuget catalog should now handle native and platform specific DLLs better. Added support for providing assembly resolving hints.
1 parent ed105dd commit fe15ecf

File tree

6 files changed

+134
-9
lines changed

6 files changed

+134
-9
lines changed

src/Weikio.PluginFramework.Catalogs.NuGet/NugetPackagePluginCatalog.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Weikio.NugetDownloader;
77
using Weikio.PluginFramework.Abstractions;
88
using Weikio.PluginFramework.Catalogs.NuGet;
9+
using Weikio.PluginFramework.Context;
910
using Weikio.PluginFramework.TypeFinding;
1011

1112
// ReSharper disable once CheckNamespace
@@ -60,7 +61,7 @@ public NugetPackagePluginCatalog(string packageName, string packageVersion = nul
6061
foreach (var finderCriteria in criterias)
6162
{
6263
finderCriteria.Value.Tags = new List<string>() { finderCriteria.Key };
63-
64+
6465
_options.TypeFinderOptions.TypeFinderCriterias.Add(finderCriteria.Value);
6566
}
6667
}
@@ -92,16 +93,33 @@ public Plugin Get(string name, Version version)
9293
public async Task Initialize()
9394
{
9495
var nuGetDownloader = new NuGetDownloader(_options.LoggerFactory());
95-
var pluginAssemblyFileNames = await nuGetDownloader.DownloadAsync(PackagesFolder, _packageName, _packageVersion, _includePrerelease, _packageFeed, includeSecondaryRepositories: _options.IncludeSystemFeedsAsSecondary);
9696

97-
foreach (var f in pluginAssemblyFileNames)
97+
var nugetDownloadResult = await nuGetDownloader.DownloadAsync(PackagesFolder, _packageName, _packageVersion, _includePrerelease, _packageFeed,
98+
includeSecondaryRepositories: _options.IncludeSystemFeedsAsSecondary).ConfigureAwait(false);
99+
100+
foreach (var f in nugetDownloadResult.PackageAssemblyFiles)
98101
{
99102
_pluginAssemblyFilePaths.Add(Path.Combine(PackagesFolder, f));
100103
}
101104

102105
foreach (var pluginAssemblyFilePath in _pluginAssemblyFilePaths)
103106
{
104-
var options = new AssemblyPluginCatalogOptions { TypeFinderOptions = _options.TypeFinderOptions, PluginNameOptions = _options.PluginNameOptions};
107+
var options = new AssemblyPluginCatalogOptions
108+
{
109+
TypeFinderOptions = _options.TypeFinderOptions, PluginNameOptions = _options.PluginNameOptions
110+
};
111+
112+
var downloadedRuntimeDlls = nugetDownloadResult.RunTimeDlls.Where(x => x.IsRecommended).ToList();
113+
114+
var runtimeAssemblyHints = new List<RuntimeAssemblyHint>();
115+
116+
foreach (var runTimeDll in downloadedRuntimeDlls)
117+
{
118+
var runtimeAssembly = new RuntimeAssemblyHint(runTimeDll.FileName, runTimeDll.FullFilePath, runTimeDll.IsNative);
119+
runtimeAssemblyHints.Add(runtimeAssembly);
120+
}
121+
122+
options.PluginLoadContextOptions.RuntimeAssemblyHints = runtimeAssemblyHints;
105123

106124
var assemblyCatalog = new AssemblyPluginCatalog(pluginAssemblyFilePath, options);
107125
await assemblyCatalog.Initialize();

src/Weikio.PluginFramework.Catalogs.NuGet/Weikio.PluginFramework.Catalogs.NuGet.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
<ItemGroup>
1818
<PackageReference Include="MinVer" Version="2.0.*" PrivateAssets="all" />
19-
<PackageReference Include="Weikio.NugetDownloader" Version="1.1.0" />
19+
<PackageReference Include="Weikio.NugetDownloader" Version="2.0.2" />
2020
</ItemGroup>
2121

2222
<ItemGroup>

src/Weikio.PluginFramework/Context/PluginAssemblyLoadContext.cs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Data;
24
using System.IO;
35
using System.Linq;
46
using System.Reflection;
@@ -19,6 +21,7 @@ public class PluginAssemblyLoadContext : AssemblyLoadContext, ITypeFindingContex
1921
private readonly string _pluginPath;
2022
private readonly AssemblyDependencyResolver _resolver;
2123
private readonly PluginLoadContextOptions _options;
24+
private readonly List<RuntimeAssemblyHint> _runtimeAssemblyHints;
2225

2326
public PluginAssemblyLoadContext(Assembly assembly, PluginLoadContextOptions options = null) : this(assembly.Location, options)
2427
{
@@ -29,6 +32,13 @@ public PluginAssemblyLoadContext(string pluginPath, PluginLoadContextOptions opt
2932
_pluginPath = pluginPath;
3033
_resolver = new AssemblyDependencyResolver(pluginPath);
3134
_options = options ?? new PluginLoadContextOptions();
35+
36+
_runtimeAssemblyHints = _options.RuntimeAssemblyHints;
37+
38+
if (_runtimeAssemblyHints == null)
39+
{
40+
_runtimeAssemblyHints = new List<RuntimeAssemblyHint>();
41+
}
3242
}
3343

3444
public Assembly Load()
@@ -63,7 +73,20 @@ protected override Assembly Load(AssemblyName assemblyName)
6373
}
6474
}
6575

66-
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
76+
string assemblyPath;
77+
78+
var assemblyFileName = assemblyName.Name + ".dll";
79+
80+
if (_runtimeAssemblyHints.Any(x => string.Equals(assemblyFileName, x.FileName)))
81+
{
82+
Log(LogLevel.Debug, "Found assembly hint for {AssemblyName}", ex: null, assemblyName);
83+
assemblyPath = _runtimeAssemblyHints.First(x => string.Equals(assemblyFileName, x.FileName)).Path;
84+
}
85+
else
86+
{
87+
Log(LogLevel.Debug, "No assembly hint found for {AssemblyName}. Using the default resolver for locating the file", ex: null, assemblyName);
88+
assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
89+
}
6790

6891
if (assemblyPath != null)
6992
{
@@ -84,7 +107,7 @@ protected override Assembly Load(AssemblyName assemblyName)
84107
// Try to locate the required dll using AdditionalRuntimePaths
85108
foreach (var runtimePath in _options.AdditionalRuntimePaths)
86109
{
87-
var fileName = assemblyName.Name + ".dll";
110+
var fileName = assemblyFileName;
88111
var filePath = Directory.GetFiles(runtimePath, fileName, SearchOption.AllDirectories).FirstOrDefault();
89112

90113
if (filePath != null)
@@ -132,13 +155,20 @@ private bool TryUseHostApplicationAssembly(AssemblyName assemblyName)
132155

133156
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
134157
{
158+
var nativeHint = _runtimeAssemblyHints.FirstOrDefault(x => x.IsNative && string.Equals(x.FileName, unmanagedDllName));
159+
160+
if (nativeHint != null)
161+
{
162+
return LoadUnmanagedDllFromPath(nativeHint.Path);
163+
}
164+
135165
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
136166

137167
if (libraryPath != null)
138168
{
139169
return LoadUnmanagedDllFromPath(libraryPath);
140170
}
141-
171+
142172
return IntPtr.Zero;
143173
}
144174

src/Weikio.PluginFramework/Context/PluginLoadContextOptions.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public class PluginLoadContextOptions
3232
/// Gets or sets the additional runtime paths which are used when locating plugin assemblies
3333
/// </summary>
3434
public List<string> AdditionalRuntimePaths { get; set; } = Defaults.AdditionalRuntimePaths;
35+
36+
/// <summary>
37+
/// Gets or sets a list of assemblies and paths which can be used to override default assembly loading. Useful in situations where in runtime we want to load a DLL from a separate location.
38+
/// </summary>
39+
public List<RuntimeAssemblyHint> RuntimeAssemblyHints { get; set; } = Defaults.RuntimeAssemblyHints;
3540

3641
public static class Defaults
3742
{
@@ -56,7 +61,11 @@ public static class Defaults
5661
/// Gets or sets the additional runtime paths which are used when locating plugin assemblies
5762
/// </summary>
5863
public static List<string> AdditionalRuntimePaths { get; set; } = new List<string>();
64+
65+
/// <summary>
66+
/// Gets or sets a list of assemblies and paths which can be used to override default assembly loading. Useful in situations where in runtime we want to load a DLL from a separate location.
67+
/// </summary>
68+
public static List<RuntimeAssemblyHint> RuntimeAssemblyHints { get; set; } = new List<RuntimeAssemblyHint>();
5969
}
6070
}
61-
6271
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Weikio.PluginFramework.Context
2+
{
3+
public class RuntimeAssemblyHint
4+
{
5+
public string FileName { get; set; }
6+
public string Path { get; set; }
7+
public bool IsNative { get; set; }
8+
9+
public RuntimeAssemblyHint(string fileName, string path, bool isNative)
10+
{
11+
FileName = fileName;
12+
Path = path;
13+
IsNative = isNative;
14+
}
15+
}
16+
}

tests/integration/Weikio.PluginFramework.Catalogs.NuGet.Tests/NugetPackagePluginCatalogTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using System.Reflection;
@@ -8,6 +9,7 @@
89
using Weikio.PluginFramework.Abstractions;
910
using Weikio.PluginFramework.Catalogs;
1011
using Weikio.PluginFramework.Catalogs.NuGet;
12+
using Weikio.PluginFramework.TypeFinding;
1113
using Xunit;
1214

1315
namespace PluginFramework.Catalogs.NuGet.Tests
@@ -220,5 +222,55 @@ public void Dispose()
220222
NugetPluginCatalogOptions.Defaults.PluginNameOptions = new PluginNameOptions();
221223
}
222224
}
225+
226+
[Fact]
227+
public async Task CanInstallPackageWithNativeDepencencies()
228+
{
229+
var options = new NugetPluginCatalogOptions()
230+
{
231+
TypeFinderOptions = new TypeFinderOptions()
232+
{
233+
TypeFinderCriterias = new List<TypeFinderCriteria>()
234+
{
235+
new TypeFinderCriteria()
236+
{
237+
Query = (context, type) =>
238+
{
239+
if (string.Equals(type.Name, "SqlConnection"))
240+
{
241+
return true;
242+
}
243+
244+
return false;
245+
}
246+
}
247+
}
248+
}
249+
};
250+
// Arrange
251+
var catalog = new NugetPackagePluginCatalog("Microsoft.Data.SqlClient", "2.1.2", options: options);
252+
253+
// Act
254+
await catalog.Initialize();
255+
256+
var plugin = catalog.Single();
257+
258+
// This is the connection string part of the Weik.io docs. It provides readonly access to the Adventureworks database sample.
259+
// So it should be ok to have it here.
260+
dynamic conn = Activator.CreateInstance(plugin, "Server=tcp:adafydevtestdb001.database.windows.net,1433;User ID=docs;Password=3h1@*6PXrldU4F95;Integrated Security=false;Initial Catalog=adafyweikiodevtestdb001;");
261+
262+
conn.Open();
263+
264+
var cmd = conn.CreateCommand();
265+
cmd.CommandText = "select top 1 * from SalesLT.Customer";
266+
267+
var reader = cmd.ExecuteReader();
268+
while (reader.Read())
269+
{
270+
Console.WriteLine(String.Format("{0}", reader[0]));
271+
}
272+
273+
conn.Dispose();
274+
}
223275
}
224276
}

0 commit comments

Comments
 (0)