Skip to content

Commit d6fbbe2

Browse files
committed
Added the GetRelativePath function
Updated the AppConfigBindingRedirectReferenceResolver allowing to specify custom app.config file paths
1 parent c9ecec4 commit d6fbbe2

File tree

9 files changed

+163
-76
lines changed

9 files changed

+163
-76
lines changed

ConsoleTester/ConsoleTester.csproj

+5-4
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@
3333
<WarningLevel>4</WarningLevel>
3434
</PropertyGroup>
3535
<ItemGroup>
36-
<Reference Include="Lib, Version=1.1.0.0, Culture=neutral, PublicKeyToken=edd202a4e66b8d76, processorArchitecture=MSIL">
37-
<SpecificVersion>False</SpecificVersion>
38-
<HintPath>.\Lib.dll</HintPath>
39-
</Reference>
4036
<Reference Include="System" />
4137
<Reference Include="System.Core" />
4238
<Reference Include="System.Xml.Linq" />
@@ -52,8 +48,13 @@
5248
</ItemGroup>
5349
<ItemGroup>
5450
<None Include="App.config" />
51+
<None Include="README.md" />
5552
</ItemGroup>
5653
<ItemGroup>
54+
<ProjectReference Include="..\Lib\Lib.csproj">
55+
<Project>{1dd5f208-badc-469f-b3ed-0d4b0a0206ff}</Project>
56+
<Name>Lib</Name>
57+
</ProjectReference>
5758
<ProjectReference Include="..\src\Utils\Utils.csproj">
5859
<Project>{dc1650a1-131a-4a5b-b7b6-d7a3d7794e52}</Project>
5960
<Name>Utils</Name>

ConsoleTester/Lib.dll

-4.5 KB
Binary file not shown.

ConsoleTester/Program.cs

+18-3
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,34 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Reflection;
56
using System.Text;
67
using System.Threading.Tasks;
8+
using Xarial.XToolkit.Helpers;
79
using Xarial.XToolkit.Reflection;
810

911
namespace ConsoleTester
1012
{
1113
class Program
1214
{
15+
private static AssemblyResolver m_AssmResolver;
16+
17+
private class CustomAppConfigBindingRedirectReferenceResolver : AppConfigBindingRedirectReferenceResolver
18+
{
19+
protected override string[] GetAppConfigs(Assembly requestingAssembly)
20+
=> Directory.GetFiles(Path.GetDirectoryName(typeof(Program).Assembly.Location), "*.config");
21+
}
22+
1323
static Program()
1424
{
15-
AppDomain.CurrentDomain.RegisterGlobalAssemblyReferenceResolver(new LocalFolderReferencesResolver(
16-
Path.GetDirectoryName(typeof(Program).Assembly.Location),
17-
AssemblyMatchFilter_e.PublicKeyToken | AssemblyMatchFilter_e.Culture));
25+
System.Diagnostics.Debugger.Launch();
26+
27+
m_AssmResolver = new AssemblyResolver(AppDomain.CurrentDomain);
28+
m_AssmResolver.RegisterAssemblyReferenceResolver(new CustomAppConfigBindingRedirectReferenceResolver());
29+
30+
//AppDomain.CurrentDomain.RegisterGlobalAssemblyReferenceResolver(new LocalFolderReferencesResolver(
31+
// Path.GetDirectoryName(typeof(Program).Assembly.Location),
32+
// AssemblyMatchFilter_e.PublicKeyToken | AssemblyMatchFilter_e.Culture));
1833
}
1934

2035
static void Main(string[] args)

ConsoleTester/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# How To Test AssemblyResolver
2+
3+
* Compile the **ConsoleTester** project. This project is referencing **Lib** dll with version 1.0.0.0
4+
* Copy and overwrite the files from the **!** folder into the build folder
5+
* **Lib.dll** reference is of version 1.1.0.0
6+
* **_ConsoleTester.exe.config** provides binding redirect to this version. Note it is required to keep this file a different name from the default (e.g. **ConsoleTester.exe.config**) otherwise built-in binding redirect will be used from the .exe file and the **AssemblyResolver** will not be called (not the case for the .dll projects)
7+
~~~ xml
8+
<?xml version="1.0" encoding="utf-8" ?>
9+
<configuration>
10+
<startup>
11+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
12+
</startup>
13+
<runtime>
14+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
15+
<dependentAssembly>
16+
<assemblyIdentity name="Lib" publicKeyToken="edd202a4e66b8d76" culture="neutral" />
17+
<bindingRedirect oldVersion="0.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
18+
</dependentAssembly>
19+
</assemblyBinding>
20+
</runtime>
21+
</configuration>
22+
~~~
23+
* Run the **ConsoleTester.exe** from the Windows File Explorer and attach to the process to debug
24+
* This is done so the default build process is not overriding the target files

Lib/Properties/AssemblyInfo.cs

+2-19
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
using System.Runtime.CompilerServices;
33
using System.Runtime.InteropServices;
44

5-
// General Information about an assembly is controlled through the following
6-
// set of attributes. Change these attribute values to modify the information
7-
// associated with an assembly.
85
[assembly: AssemblyTitle("Lib")]
96
[assembly: AssemblyDescription("")]
107
[assembly: AssemblyConfiguration("")]
@@ -14,23 +11,9 @@
1411
[assembly: AssemblyTrademark("")]
1512
[assembly: AssemblyCulture("")]
1613

17-
// Setting ComVisible to false makes the types in this assembly not visible
18-
// to COM components. If you need to access a type in this assembly from
19-
// COM, set the ComVisible attribute to true on that type.
2014
[assembly: ComVisible(false)]
2115

22-
// The following GUID is for the ID of the typelib if this project is exposed to COM
2316
[assembly: Guid("1dd5f208-badc-469f-b3ed-0d4b0a0206ff")]
2417

25-
// Version information for an assembly consists of the following four values:
26-
//
27-
// Major Version
28-
// Minor Version
29-
// Build Number
30-
// Revision
31-
//
32-
// You can specify all the values or you can default the Build and Revision Numbers
33-
// by using the '*' as shown below:
34-
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("1.1.0.0")]
36-
[assembly: AssemblyFileVersion("1.1.0.0")]
18+
[assembly: AssemblyVersion("1.0.0.0")]
19+
[assembly: AssemblyFileVersion("1.0.0.0")]

Tests/Utils.Tests/FileSystemUtilsTests.cs

+21
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public void IsInDirectoryTest()
3030

3131
var r11 = FileSystemUtils.IsInDirectory(@"D:\b\c", @"D:\x\y");
3232

33+
var r12 = FileSystemUtils.IsInDirectory(@"D:\b\c\1.txt", @"D:\b");
34+
35+
var r13 = FileSystemUtils.IsInDirectory(@"D:\b\c\1.txt", @"D:\b\c\x");
36+
3337
Assert.IsFalse(r1);
3438
Assert.IsTrue(r2);
3539
Assert.IsFalse(r3);
@@ -41,6 +45,23 @@ public void IsInDirectoryTest()
4145
Assert.IsFalse(r9);
4246
Assert.IsFalse(r10);
4347
Assert.IsFalse(r11);
48+
Assert.IsTrue(r12);
49+
Assert.IsFalse(r13);
50+
}
51+
52+
[Test]
53+
public void GetRelativePathTest()
54+
{
55+
var r1 = FileSystemUtils.GetRelativePath(@"D:\a\b\c\1.txt", @"D:\a\b");
56+
var r2 = FileSystemUtils.GetRelativePath(@"D:\a\b\c\d", @"D:\a\b\");
57+
var r3 = FileSystemUtils.GetRelativePath(@"D:\x\y\z\", @"D:\x\");
58+
var r4 = FileSystemUtils.GetRelativePath(@"a\b\c\1.txt", @"a\b\");
59+
60+
Assert.AreEqual(@"c\1.txt", r1);
61+
Assert.AreEqual(@"c\d", r2);
62+
Assert.AreEqual(@"y\z\", r3);
63+
Assert.AreEqual(@"c\1.txt", r4);
64+
Assert.Throws<Exception>(() => FileSystemUtils.GetRelativePath(@"D:\a\b\c\1.txt", @"D:\a\b\c\d"));
4465
}
4566

4667
[Test]

src/Utils/FileSystemUtils.cs

+37-6
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,50 @@ public static string[] GetTopFolders(IEnumerable<string> paths)
7878
}
7979

8080
/// <summary>
81-
/// Checks if the specified directoryis in the other directory
81+
/// Checks if the specified path is in the other directory
8282
/// </summary>
83-
/// <param name="thisDir">Directory to check</param>
83+
/// <param name="thisPath">Path to check</param>
8484
/// <param name="parentDir">Directory to check agains</param>
8585
/// <returns>True of directory is within another directory</returns>
86-
public static bool IsInDirectory(string thisDir, string parentDir)
86+
public static bool IsInDirectory(string thisPath, string parentDir)
8787
{
88-
string NormalizePath(string path) => path.TrimEnd('\\') + "\\";
89-
90-
return NormalizePath(thisDir).StartsWith(NormalizePath(parentDir),
88+
return thisPath.StartsWith(NormalizeDirectoryPath(parentDir),
9189
StringComparison.CurrentCultureIgnoreCase);
9290
}
9391

92+
/// <summary>
93+
/// Finds the relative path
94+
/// </summary>
95+
/// <param name="thisPath">Path to get relative path for</param>
96+
/// <param name="relativeToDir">Relative directory</param>
97+
/// <returns>Relative path</returns>
98+
/// <exception cref="Exception"></exception>
99+
public static string GetRelativePath(string thisPath, string relativeToDir)
100+
{
101+
relativeToDir = NormalizeDirectoryPath(relativeToDir);
102+
103+
if (IsInDirectory(thisPath, relativeToDir))
104+
{
105+
return thisPath.Substring(relativeToDir.Length);
106+
}
107+
else
108+
{
109+
throw new Exception($"'{relativeToDir}' is not in the '{thisPath}' directory");
110+
}
111+
}
112+
113+
private static string NormalizeDirectoryPath(string path)
114+
{
115+
if (!path.EndsWith("\\"))
116+
{
117+
return path + "\\";
118+
}
119+
else
120+
{
121+
return path;
122+
}
123+
}
124+
94125
/// <summary>
95126
/// Opens file explorer at the specified folder
96127
/// </summary>

src/Utils/Helpers/AssemblyResolver.cs

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public interface IReferenceResolver
2020
Assembly Resolve(AppDomain appDomain, AssemblyName assmName, Assembly requestingAssembly);
2121
}
2222

23+
/// <summary>
24+
/// This is a helper class allowing to specify strategies for resolving the missing dlls
25+
/// </summary>
2326
public class AssemblyResolver
2427
{
2528
private readonly List<IReferenceResolver> m_AssemblyResolvers;

src/Utils/Reflection/AppConfigBindingRedirectReferenceResolver.cs

+53-44
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@
77

88
using System;
99
using System.IO;
10+
using System.Linq;
1011
using System.Reflection;
1112
using System.Xml;
1213
using System.Xml.Linq;
1314
using System.Xml.XPath;
1415

1516
namespace Xarial.XToolkit.Reflection
1617
{
18+
/// <summary>
19+
/// Resolver for <see cref="Helpers.AssemblyResolver"/> allowing to redirect assembly binding based on the .config files
20+
/// </summary>
21+
/// <remarks>This resolver can be useful for the plugin applications (.dlls) when the app.config files will not be considered for binding redirects
22+
/// It can also be useful when the binding redirects specified in the separate file which is not named after the application name (e.g. custom binding redirect without an automatic option)</remarks>
1723
public class AppConfigBindingRedirectReferenceResolver : AssemblyNameReferenceResolver
1824
{
1925
public AppConfigBindingRedirectReferenceResolver() : this("")
@@ -36,63 +42,66 @@ protected virtual Assembly[] GetRequestingAssemblies(Assembly requestingAssembly
3642
}
3743
}
3844

45+
protected virtual string[] GetAppConfigs(Assembly requestingAssembly)
46+
=> GetRequestingAssemblies(requestingAssembly)
47+
.Select(a => a.Location + ".config")
48+
.Where(f => File.Exists(f)).ToArray();
49+
3950
protected override AssemblyName GetReplacementAssemblyName(AssemblyName assmName, Assembly requestingAssembly,
4051
out string searchDir, out bool recursiveSearch)
4152
{
42-
var reqAssms = GetRequestingAssemblies(requestingAssembly);
43-
44-
foreach (var lookupAssm in reqAssms)
53+
foreach (var appConfigPath in GetAppConfigs(requestingAssembly))
4554
{
46-
var appConfigPath = lookupAssm.Location + ".config";
55+
if (!File.Exists(appConfigPath))
56+
{
57+
throw new FileNotFoundException($"Specified application configuration file is not available: '{appConfigPath}'");
58+
}
4759

48-
if (File.Exists(appConfigPath))
60+
using (var xmlStream = File.OpenRead(appConfigPath))
4961
{
50-
using (var xmlStream = File.OpenRead(appConfigPath))
62+
using (var reader = new XmlTextReader(xmlStream))
5163
{
52-
using (var reader = new XmlTextReader(xmlStream))
64+
var doc = XDocument.Load(reader);
65+
66+
var namespaceManager = new XmlNamespaceManager(reader.NameTable);
67+
namespaceManager.AddNamespace("b", "urn:schemas-microsoft-com:asm.v1");
68+
69+
var name = assmName.Name;
70+
var publicKeyToken = GetPublicKeyToken(assmName);
71+
var culture = GetCulture(assmName);
72+
73+
var bindRedirect = doc.XPathSelectElement(
74+
$"//configuration/runtime/b:assemblyBinding/b:dependentAssembly[b:assemblyIdentity[@name = '{name}'][@publicKeyToken = '{publicKeyToken}'][@culture = '{culture}']]/b:bindingRedirect", namespaceManager);
75+
76+
if (bindRedirect != null)
5377
{
54-
var doc = XDocument.Load(reader);
78+
var oldVersionVal = bindRedirect.Attribute("oldVersion").Value;
79+
var newVersionVal = bindRedirect.Attribute("newVersion").Value;
5580

56-
var namespaceManager = new XmlNamespaceManager(reader.NameTable);
57-
namespaceManager.AddNamespace("b", "urn:schemas-microsoft-com:asm.v1");
81+
Version oldVersionMin = null;
82+
Version oldVersionMax = null;
5883

59-
var name = assmName.Name;
60-
var publicKeyToken = GetPublicKeyToken(assmName);
61-
var culture = GetCulture(assmName);
84+
if (oldVersionVal.Contains("-"))
85+
{
86+
var oldVersRange = oldVersionVal.Split('-');
87+
oldVersionMin = Version.Parse(oldVersRange[0]);
88+
oldVersionMax = Version.Parse(oldVersRange[1]);
89+
}
90+
else
91+
{
92+
oldVersionMin = Version.Parse(oldVersionVal);
93+
oldVersionMax = Version.Parse(oldVersionVal);
94+
}
6295

63-
var bindRedirect = doc.XPathSelectElement(
64-
$"//configuration/runtime/b:assemblyBinding/b:dependentAssembly[b:assemblyIdentity[@name = '{name}'][@publicKeyToken = '{publicKeyToken}'][@culture = '{culture}']]/b:bindingRedirect", namespaceManager);
96+
var newVersion = Version.Parse(newVersionVal);
6597

66-
if (bindRedirect != null)
98+
if (assmName.Version >= oldVersionMin && assmName.Version <= oldVersionMax)
6799
{
68-
var oldVersionVal = bindRedirect.Attribute("oldVersion").Value;
69-
var newVersionVal = bindRedirect.Attribute("newVersion").Value;
70-
71-
Version oldVersionMin = null;
72-
Version oldVersionMax = null;
73-
74-
if (oldVersionVal.Contains("-"))
75-
{
76-
var oldVersRange = oldVersionVal.Split('-');
77-
oldVersionMin = Version.Parse(oldVersRange[0]);
78-
oldVersionMax = Version.Parse(oldVersRange[1]);
79-
}
80-
else
81-
{
82-
oldVersionMin = Version.Parse(oldVersionVal);
83-
oldVersionMax = Version.Parse(oldVersionVal);
84-
}
85-
86-
var newVersion = Version.Parse(newVersionVal);
87-
88-
if (assmName.Version >= oldVersionMin && assmName.Version <= oldVersionMax)
89-
{
90-
var searchAssmName = new AssemblyName($"{name}, Version={newVersion}, Culture={culture}, PublicKeyToken={publicKeyToken}");
91-
92-
searchDir = Path.GetDirectoryName(lookupAssm.Location);
93-
recursiveSearch = true;
94-
return searchAssmName;
95-
}
100+
var searchAssmName = new AssemblyName($"{name}, Version={newVersion}, Culture={culture}, PublicKeyToken={publicKeyToken}");
101+
102+
searchDir = Path.GetDirectoryName(appConfigPath);
103+
recursiveSearch = true;
104+
return searchAssmName;
96105
}
97106
}
98107
}

0 commit comments

Comments
 (0)