From 07f0a0169cb878cc4d082308e9a13071c17545af Mon Sep 17 00:00:00 2001 From: Andre Lafleur Date: Mon, 16 Mar 2026 23:20:58 +0800 Subject: [PATCH 1/2] fix: resolve .NET 8 SDK transitive dependencies from deps.json files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SdkResolver previously only used Genetec.Sdk.deps.json to resolve dependencies. Assemblies required by transitive dependencies (e.g., Genetec.Platform.Security) that were not listed in Genetec.Sdk.deps.json and not physically present in the SDK installation folder could not be found, causing authentication failures (ConnectionEstablished → Failed). The resolver now parses all .deps.json files in the SDK folder and builds an index that maps assembly names to file paths, checking the SDK folder first and falling back to the local NuGet package cache. --- Samples/Shared/SdkResolverNetCoreApp.cs | 89 ++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 8 deletions(-) diff --git a/Samples/Shared/SdkResolverNetCoreApp.cs b/Samples/Shared/SdkResolverNetCoreApp.cs index a7670e8..b78bdbe 100644 --- a/Samples/Shared/SdkResolverNetCoreApp.cs +++ b/Samples/Shared/SdkResolverNetCoreApp.cs @@ -10,14 +10,16 @@ namespace Genetec.Dap.CodeSamples; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Loader; +using System.Text.Json; using Microsoft.Win32; using System.Collections.Concurrent; public static class SdkResolver { private static string s_probingPath; - private static AssemblyDependencyResolver s_dependencyResolver; + private static Dictionary s_packageAssemblyPaths; private static readonly ConcurrentDictionary> s_loaders = new(StringComparer.OrdinalIgnoreCase); public static void Initialize() @@ -27,12 +29,75 @@ public static void Initialize() if (string.IsNullOrEmpty(s_probingPath)) throw new InvalidOperationException("SDK probing path could not be found."); - var sdkDll = Path.Combine(s_probingPath, "Genetec.Sdk.dll"); - s_dependencyResolver = File.Exists(sdkDll) ? new AssemblyDependencyResolver(sdkDll) : null; + s_packageAssemblyPaths = BuildPackageAssemblyIndex(s_probingPath); AssemblyLoadContext.Default.Resolving += OnAssemblyResolve; } + private static Dictionary BuildPackageAssemblyIndex(string probingPath) + { + var index = new Dictionary(StringComparer.OrdinalIgnoreCase); + var nugetCache = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + + if (!Directory.Exists(nugetCache)) + return index; + + foreach (var depsFile in Directory.EnumerateFiles(probingPath, "*.deps.json")) + { + try + { + using var stream = File.OpenRead(depsFile); + using var doc = JsonDocument.Parse(stream); + + if (!doc.RootElement.TryGetProperty("targets", out var targets)) + continue; + + foreach (var target in targets.EnumerateObject()) + { + foreach (var package in target.Value.EnumerateObject()) + { + if (!package.Value.TryGetProperty("runtime", out var runtime)) + continue; + + foreach (var runtimeEntry in runtime.EnumerateObject()) + { + var dllName = Path.GetFileNameWithoutExtension(runtimeEntry.Name); + if (index.ContainsKey(dllName)) + continue; + + // Check if DLL is already in the SDK folder + var localPath = Path.Combine(probingPath, Path.GetFileName(runtimeEntry.Name)); + if (File.Exists(localPath)) + { + index[dllName] = localPath; + continue; + } + + // Try to find in NuGet cache + var packageParts = package.Name.Split('/'); + if (packageParts.Length == 2) + { + var packagePath = Path.Combine(nugetCache, packageParts[0].ToLowerInvariant(), packageParts[1], runtimeEntry.Name); + if (File.Exists(packagePath)) + { + index[dllName] = packagePath; + } + } + } + } + + break; // Only process the first target + } + } + catch + { + // Skip malformed deps.json files + } + } + + return index; + } + private static Assembly OnAssemblyResolve(AssemblyLoadContext context, AssemblyName assemblyName) { string key = assemblyName.FullName ?? assemblyName.Name; @@ -54,10 +119,18 @@ private static Assembly OnAssemblyResolve(AssemblyLoadContext context, AssemblyN private static Assembly LoadAssembly(AssemblyLoadContext context, AssemblyName assemblyName) { - // 1) Try AssemblyDependencyResolver - var path = s_dependencyResolver.ResolveAssemblyToPath(assemblyName); - if (!string.IsNullOrEmpty(path) && File.Exists(path)) - return context.LoadFromAssemblyPath(path); + // 1) Try the package assembly index (built from all .deps.json files) + if (s_packageAssemblyPaths.TryGetValue(assemblyName.Name, out var resolvedPath) && File.Exists(resolvedPath)) + { + try + { + return context.LoadFromAssemblyPath(resolvedPath); + } + catch (Exception) + { + // Fall through to next resolution strategy + } + } // 2) Try direct probing folder if (!string.IsNullOrEmpty(s_probingPath)) @@ -134,4 +207,4 @@ private static string GetProbingPath() } } -#endif \ No newline at end of file +#endif From 3b3c882405256bd981d710dbd666ac66a7233d9d Mon Sep 17 00:00:00 2001 From: Andre Lafleur Date: Mon, 16 Mar 2026 23:40:59 +0800 Subject: [PATCH 2/2] fix: address code review feedback on SdkResolver - Remove unused System.Runtime.InteropServices import - Reject rooted paths from deps.json to prevent path traversal - Use NUGET_PACKAGES env var before falling back to default cache path - Continue indexing SDK folder DLLs even when NuGet cache is missing - Add null guard on s_packageAssemblyPaths in LoadAssembly - Catch specific exceptions (BadImageFormatException, JsonException, IOException) - Process all targets in deps.json instead of only the first --- Samples/Shared/SdkResolverNetCoreApp.cs | 46 ++++++++++++++++--------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/Samples/Shared/SdkResolverNetCoreApp.cs b/Samples/Shared/SdkResolverNetCoreApp.cs index b78bdbe..ce631c2 100644 --- a/Samples/Shared/SdkResolverNetCoreApp.cs +++ b/Samples/Shared/SdkResolverNetCoreApp.cs @@ -10,7 +10,6 @@ namespace Genetec.Dap.CodeSamples; using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Text.Json; using Microsoft.Win32; @@ -37,10 +36,12 @@ public static void Initialize() private static Dictionary BuildPackageAssemblyIndex(string probingPath) { var index = new Dictionary(StringComparer.OrdinalIgnoreCase); - var nugetCache = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); - if (!Directory.Exists(nugetCache)) - return index; + var nugetCache = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); + if (string.IsNullOrWhiteSpace(nugetCache)) + nugetCache = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + + bool hasNugetCache = Directory.Exists(nugetCache); foreach (var depsFile in Directory.EnumerateFiles(probingPath, "*.deps.json")) { @@ -61,12 +62,17 @@ private static Dictionary BuildPackageAssemblyIndex(string probi foreach (var runtimeEntry in runtime.EnumerateObject()) { - var dllName = Path.GetFileNameWithoutExtension(runtimeEntry.Name); + var runtimeName = runtimeEntry.Name; + + if (Path.IsPathRooted(runtimeName)) + continue; + + var dllName = Path.GetFileNameWithoutExtension(Path.GetFileName(runtimeName)); if (index.ContainsKey(dllName)) continue; // Check if DLL is already in the SDK folder - var localPath = Path.Combine(probingPath, Path.GetFileName(runtimeEntry.Name)); + var localPath = Path.Combine(probingPath, Path.GetFileName(runtimeName)); if (File.Exists(localPath)) { index[dllName] = localPath; @@ -74,25 +80,30 @@ private static Dictionary BuildPackageAssemblyIndex(string probi } // Try to find in NuGet cache - var packageParts = package.Name.Split('/'); - if (packageParts.Length == 2) + if (hasNugetCache) { - var packagePath = Path.Combine(nugetCache, packageParts[0].ToLowerInvariant(), packageParts[1], runtimeEntry.Name); - if (File.Exists(packagePath)) + var packageParts = package.Name.Split('/'); + if (packageParts.Length == 2) { - index[dllName] = packagePath; + var packagePath = Path.Combine(nugetCache, packageParts[0].ToLowerInvariant(), packageParts[1], runtimeName); + if (File.Exists(packagePath)) + { + index[dllName] = packagePath; + } } } } } - - break; // Only process the first target } } - catch + catch (JsonException) { // Skip malformed deps.json files } + catch (IOException) + { + // Skip files that cannot be read + } } return index; @@ -119,6 +130,9 @@ private static Assembly OnAssemblyResolve(AssemblyLoadContext context, AssemblyN private static Assembly LoadAssembly(AssemblyLoadContext context, AssemblyName assemblyName) { + if (s_packageAssemblyPaths == null) + return null; + // 1) Try the package assembly index (built from all .deps.json files) if (s_packageAssemblyPaths.TryGetValue(assemblyName.Name, out var resolvedPath) && File.Exists(resolvedPath)) { @@ -126,7 +140,7 @@ private static Assembly LoadAssembly(AssemblyLoadContext context, AssemblyName a { return context.LoadFromAssemblyPath(resolvedPath); } - catch (Exception) + catch (BadImageFormatException) { // Fall through to next resolution strategy } @@ -141,7 +155,7 @@ private static Assembly LoadAssembly(AssemblyLoadContext context, AssemblyName a { return context.LoadFromAssemblyPath(candidate); } - catch (Exception) + catch (BadImageFormatException) { // Ignore and try next candidate }