├── .gitignore ├── README.md └── src ├── addin-loadassemblybypath ├── app │ ├── ComponentHost.cs │ ├── Program.cs │ └── app.csproj ├── lib │ ├── Class1.cs │ └── lib.csproj └── lib2 │ ├── Class2.cs │ └── lib2.csproj ├── build.ps1 ├── customloadcontext ├── ComponentHost.cs ├── Program.cs └── customloadcontext.csproj ├── gutenapp ├── dialogue │ ├── Class1.cs │ └── dialogue.csproj ├── gutenapp │ ├── ComponentContext.cs │ ├── ComponentResolution.cs │ ├── ComponentResolver.cs │ ├── ComponentResolverBinDirectoryStrategy.cs │ ├── ComponentResolverProductionStrategy.cs │ ├── DirectoryInfoExtensions.cs │ ├── IComponentResolver.cs │ ├── Program.cs │ └── gutenapp.csproj ├── interfaces │ ├── IProvider.cs │ └── interfaces.csproj ├── mostcommonwords │ ├── MostCommonWords.cs │ └── mostcommonwords.csproj └── wordcount │ ├── WordCount.cs │ └── wordcount.csproj ├── lib ├── Class1.cs ├── lib.csproj └── out │ ├── lib.deps.json │ └── lib.dll ├── loadassemblybypath ├── Program.cs └── loadassemblybypath.csproj ├── test-prod.ps1 └── test.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | .vscode/ 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Ignore the published app from our test scritps 34 | out/ 35 | testdir/ 36 | 37 | # Visual Studio 2017 auto generated files 38 | Generated\ Files/ 39 | 40 | # MSTest test Results 41 | [Tt]est[Rr]esult*/ 42 | [Bb]uild[Ll]og.* 43 | 44 | # NUNIT 45 | *.VisualState.xml 46 | TestResult.xml 47 | 48 | # Build Results of an ATL Project 49 | [Dd]ebugPS/ 50 | [Rr]eleasePS/ 51 | dlldata.c 52 | 53 | # Benchmark Results 54 | BenchmarkDotNet.Artifacts/ 55 | 56 | # .NET Core 57 | project.lock.json 58 | project.fragment.lock.json 59 | artifacts/ 60 | 61 | # StyleCop 62 | StyleCopReport.xml 63 | 64 | # Files built by Visual Studio 65 | *_i.c 66 | *_p.c 67 | *_i.h 68 | *.ilk 69 | *.meta 70 | *.obj 71 | *.iobj 72 | *.pch 73 | *.pdb 74 | *.ipdb 75 | *.pgc 76 | *.pgd 77 | *.rsp 78 | *.sbr 79 | *.tlb 80 | *.tli 81 | *.tlh 82 | *.tmp 83 | *.tmp_proj 84 | *.log 85 | *.vspscc 86 | *.vssscc 87 | .builds 88 | *.pidb 89 | *.svclog 90 | *.scc 91 | 92 | # Chutzpah Test files 93 | _Chutzpah* 94 | 95 | # Visual C++ cache files 96 | ipch/ 97 | *.aps 98 | *.ncb 99 | *.opendb 100 | *.opensdf 101 | *.sdf 102 | *.cachefile 103 | *.VC.db 104 | *.VC.VC.opendb 105 | 106 | # Visual Studio profiler 107 | *.psess 108 | *.vsp 109 | *.vspx 110 | *.sap 111 | 112 | # Visual Studio Trace Files 113 | *.e2e 114 | 115 | # TFS 2012 Local Workspace 116 | $tf/ 117 | 118 | # Guidance Automation Toolkit 119 | *.gpState 120 | 121 | # ReSharper is a .NET coding add-in 122 | _ReSharper*/ 123 | *.[Rr]e[Ss]harper 124 | *.DotSettings.user 125 | 126 | # JustCode is a .NET coding add-in 127 | .JustCode 128 | 129 | # TeamCity is a build add-in 130 | _TeamCity* 131 | 132 | # DotCover is a Code Coverage Tool 133 | *.dotCover 134 | 135 | # AxoCover is a Code Coverage Tool 136 | .axoCover/* 137 | !.axoCover/settings.json 138 | 139 | # Visual Studio code coverage results 140 | *.coverage 141 | *.coveragexml 142 | 143 | # NCrunch 144 | _NCrunch_* 145 | .*crunch*.local.xml 146 | nCrunchTemp_* 147 | 148 | # MightyMoose 149 | *.mm.* 150 | AutoTest.Net/ 151 | 152 | # Web workbench (sass) 153 | .sass-cache/ 154 | 155 | # Installshield output folder 156 | [Ee]xpress/ 157 | 158 | # DocProject is a documentation generator add-in 159 | DocProject/buildhelp/ 160 | DocProject/Help/*.HxT 161 | DocProject/Help/*.HxC 162 | DocProject/Help/*.hhc 163 | DocProject/Help/*.hhk 164 | DocProject/Help/*.hhp 165 | DocProject/Help/Html2 166 | DocProject/Help/html 167 | 168 | # Click-Once directory 169 | publish/ 170 | 171 | # Publish Web Output 172 | *.[Pp]ublish.xml 173 | *.azurePubxml 174 | # Note: Comment the next line if you want to checkin your web deploy settings, 175 | # but database connection strings (with potential passwords) will be unencrypted 176 | *.pubxml 177 | *.publishproj 178 | 179 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 180 | # checkin your Azure Web App publish settings, but sensitive information contained 181 | # in these scripts will be unencrypted 182 | PublishScripts/ 183 | 184 | # NuGet Packages 185 | *.nupkg 186 | # The packages folder can be ignored because of Package Restore 187 | **/[Pp]ackages/* 188 | # except build/, which is used as an MSBuild target. 189 | !**/[Pp]ackages/build/ 190 | # Uncomment if necessary however generally it will be regenerated when needed 191 | #!**/[Pp]ackages/repositories.config 192 | # NuGet v3's project.json files produces more ignorable files 193 | *.nuget.props 194 | *.nuget.targets 195 | 196 | # Microsoft Azure Build Output 197 | csx/ 198 | *.build.csdef 199 | 200 | # Microsoft Azure Emulator 201 | ecf/ 202 | rcf/ 203 | 204 | # Windows Store app package directories and files 205 | AppPackages/ 206 | BundleArtifacts/ 207 | Package.StoreAssociation.xml 208 | _pkginfo.txt 209 | *.appx 210 | 211 | # Visual Studio cache files 212 | # files ending in .cache can be ignored 213 | *.[Cc]ache 214 | # but keep track of directories ending in .cache 215 | !*.[Cc]ache/ 216 | 217 | # Others 218 | ClientBin/ 219 | ~$* 220 | *~ 221 | *.dbmdl 222 | *.dbproj.schemaview 223 | *.jfm 224 | *.pfx 225 | *.publishsettings 226 | orleans.codegen.cs 227 | 228 | # Including strong name files can present a security risk 229 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 230 | #*.snk 231 | 232 | # Since there are multiple workflows, uncomment next line to ignore bower_components 233 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 234 | #bower_components/ 235 | 236 | # RIA/Silverlight projects 237 | Generated_Code/ 238 | 239 | # Backup & report files from converting an old project file 240 | # to a newer Visual Studio version. Backup files are not needed, 241 | # because we have git ;-) 242 | _UpgradeReport_Files/ 243 | Backup*/ 244 | UpgradeLog*.XML 245 | UpgradeLog*.htm 246 | ServiceFabricBackup/ 247 | *.rptproj.bak 248 | 249 | # SQL Server files 250 | *.mdf 251 | *.ldf 252 | *.ndf 253 | 254 | # Business Intelligence projects 255 | *.rdl.data 256 | *.bim.layout 257 | *.bim_*.settings 258 | *.rptproj.rsuser 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush 299 | .cr/ 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Assembly Loading with .NET Core 2 | 3 | Loading assemblies is an important part of many programs. In most cases, your static dependencies will be loaded automatically, but dynamic dependencies require active use of various assembly loader APIs. For any program that exposes an add-in model, assembly loading is a critical part of the application architecture. 4 | 5 | Many .NET developers are familar with the .NET Framework assembly loading model, using [Assembly](https://docs.microsoft.com/dotnet/api/system.reflection.assembly?view=netframework-4.7.2) and [AppDomain](https://docs.microsoft.com/dotnet/api/system.appdomain?view=netframework-4.7.2) class APIs. .NET Core exposes a similar model, with differences. The biggest difference is that .NET Core exposes [AssemblyLoadContext](https://docs.microsoft.com/dotnet/api/system.runtime.loader.assemblyloadcontext), which has some overlap with AppDomain, but is a much lighter-weight subsystem. 6 | 7 | Resources: 8 | 9 | * [AssemblyLoadContext design document](https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/assemblyloadcontext.md) 10 | * [AssemblyLoadContext source](https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs) 11 | 12 | This document describes a set of scenarios with guidance and samples for implementing those scenarios. 13 | 14 | ## AssemblyLoaderContext assembly loading model 15 | 16 | You need to understand the basics of the new [AssemblyLoadContext](https://docs.microsoft.com/dotnet/api/system.runtime.loader.assemblyloadcontext) (ALC) type to correctly control assembly loading. 17 | 18 | The basic function and value proposition of ALC is enabling assembly loading isolation within a process. This is similar to what the AppDomain type provides with .NET Framework. Within a given ALC, you can load an assembly with a given simple name, like foo.dll, just once. You can load assemblies with that same name multiple times across multiple ALCs. The version and publisher of the assembly does not need to match across ALCs. That statics for an assembly are unique (and don't leak) across ALCs. 19 | 20 | Every assembly is loaded into an ALC. By default, assemblies are loaded into the default ALC. Assemblies that are loaded into the default ALC are visible to all other ALCs, including the values of static fields. 21 | 22 | The [AssemblyLoadContext](https://docs.microsoft.com/dotnet/api/system.runtime.loader.assemblyloadcontext) class exposes a set of assembly loading APIs. These APIs, by virtue of being instance APIs, load assemblies into the current ALC. They can load by name, file, or stream. 23 | 24 | The default ALC is available via the static `AssemblyLoadContext.Default` property. Non-default ALCs are available by using APIs that expose ALCs, which are discussed later. ALCs do not currently have names. You can only differentiate ALCs via equality comparisons, like comparing the default ALC to another ALC reference. 25 | 26 | The [Assembly](https://docs.microsoft.com/dotnet/api/system.reflection.assembly?view=netcore-2.1) class exposes a set of assembly loading APIs, the same ones that are available in the .NET Framework. These APIs have the following behavior: 27 | 28 | * Assembly.Load - loads assembly and its dependencies into the current ALC. One can consider this API neutral in nature, leaving all loading policy up to the host application. 29 | * Assembly.LoadFrom - loads assembly and its dependencies into the default ALC. Only a application or host should use this API. 30 | * Assembly.LoadFile and Assembly.Load(Byte[]) - loads assembly and its depenencies into a new ALC. 31 | 32 | ## Loading an assembly by path 33 | 34 | The simplest scenario is loading an assembly by path. The following code loads an assembly by path into the default ALC. 35 | 36 | ```csharp 37 | var assemblyLocation = "/fully/qualified-path/to.dll" 38 | var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyLocation); 39 | ``` 40 | 41 | Alternatively, you can `Assembly` class APIs. `Assembly.LoadFrom` will load assemblies into the default ALC. `Assembly.LoadFile` will load assemblies into a new ALC with each invocation. 42 | 43 | ## Loading an assembly by path as an add-in 44 | 45 | There is a simple pattern that can used to acquire a reference to the current context, using the `AssemblyLoadContext.GetLoadContext` method, as follows (assuming the calling type is `Class1`). 46 | 47 | ```csharp 48 | var context = AssemblyLoadContext.GetLoadContext(typeof(Class1).Assembly); 49 | ``` 50 | 51 | Once this reference is acquired, you can call `AssemblyLoadContext` instance methods to load assemblies from an addin. 52 | 53 | Some developers have asked for an [AssemblyLoadContext.Current](https://github.com/dotnet/coreclr/issues/10233) API to load dependencies. This API doesn't seem necessary. 54 | -------------------------------------------------------------------------------- /src/addin-loadassemblybypath/app/ComponentHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.Loader; 4 | 5 | public class ComponentHost : AssemblyLoadContext 6 | { 7 | protected override Assembly Load(AssemblyName assemblyName) 8 | { 9 | return Assembly.Load(assemblyName); 10 | } 11 | } -------------------------------------------------------------------------------- /src/addin-loadassemblybypath/app/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.Loader; 4 | using static System.Console; 5 | 6 | namespace loaderexample 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | 13 | var assemblyLocation = "/Users/rlander/git/dotnet-core-assembly-loading/src/addin-loadassemblybypath/lib/bin/Debug/netcoreapp2.1/lib.dll"; 14 | WriteLine("Load lib.dll"); 15 | var host = new ComponentHost(); 16 | var assembly = host.LoadFromAssemblyPath(assemblyLocation); 17 | var type = assembly.GetType("loaderexample.Class1"); 18 | WriteLine("Call GetString method:"); 19 | var method = type.GetMethod("GetString"); 20 | var returnValue = method.Invoke(null, null); 21 | WriteLine(returnValue); 22 | WriteLine("Print loaded assemblies in 'host' AssemblyLoadContext:"); 23 | WriteLine("Call GetOtherString method:"); 24 | var method2 = type.GetMethod("GetOtherString"); 25 | var returnValue2 = method2.Invoke(null, null); 26 | WriteLine(returnValue2); 27 | WriteLine("Print loaded assemblies in 'host' AssemblyLoadContext:"); 28 | 29 | foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) 30 | { 31 | // only display assemblies not loaded in the Default AssemblyLoadContext 32 | if (AssemblyLoadContext.GetLoadContext(asm) == host) 33 | { 34 | WriteLine(asm.FullName); 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/addin-loadassemblybypath/app/app.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/addin-loadassemblybypath/lib/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.Loader; 4 | 5 | namespace loaderexample 6 | { 7 | public class Class1 8 | { 9 | public static string GetString() 10 | { 11 | return "Bow ties are cool."; 12 | } 13 | 14 | public static string GetOtherString() 15 | { 16 | var assemblyLocation = "/Users/rlander/git/dotnet-core-assembly-loading/src/addin-loadassemblybypath/lib2/bin/Debug/netstandard2.0/lib2.dll"; 17 | var context = AssemblyLoadContext.GetLoadContext(typeof(Class1).Assembly); 18 | var assembly = context.LoadFromAssemblyPath(assemblyLocation); 19 | var type = assembly.GetType("Class2"); 20 | var method = type.GetMethod("GetOtherString"); 21 | var returnValue = (string)method.Invoke(null, null); 22 | return returnValue; 23 | } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/addin-loadassemblybypath/lib/lib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/addin-loadassemblybypath/lib2/Class2.cs: -------------------------------------------------------------------------------- 1 | public class Class2 2 | { 3 | public static string GetOtherString() 4 | { 5 | return "Before I go, I just want to tell you: you were fantastic. Absolutely fantastic. And you know what? So was I."; 6 | } 7 | } -------------------------------------------------------------------------------- /src/addin-loadassemblybypath/lib2/lib2.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/build.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | $BasePath = (Get-Item -Path "./").FullName 4 | $BaseGutenapp = Join-Path $BasePath "gutenapp" 5 | $GutenappDir = Join-Path $BaseGutenapp "gutenapp" 6 | $Gutenapp = Join-Path $GutenappDir "gutenapp.csproj" 7 | $WordcountDir = Join-Path $BaseGutenapp "wordcount" 8 | $Wordcount = Join-Path $WordcountDir "wordcount.csproj" 9 | $MostcommonwordsDir = Join-Path $BaseGutenapp "mostcommonwords" 10 | $Mostcommonwords = Join-Path $MostcommonwordsDir "mostcommonwords.csproj" 11 | 12 | dotnet build $Gutenapp 13 | dotnet build $Wordcount 14 | dotnet build $Mostcommonwords 15 | -------------------------------------------------------------------------------- /src/customloadcontext/ComponentHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.Loader; 4 | 5 | public class ComponentHost : AssemblyLoadContext 6 | { 7 | protected override Assembly Load(AssemblyName assemblyName) 8 | { 9 | return Assembly.Load(assemblyName); 10 | } 11 | } -------------------------------------------------------------------------------- /src/customloadcontext/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.Loader; 4 | using static System.Console; 5 | 6 | namespace loaderexample 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | var assemblyLocation = "/Users/rlander/git/dotnet-core-assembly-loading/src/lib/out/lib.dll"; 13 | WriteLine("Load lib.dll"); 14 | var host = new ComponentHost(); 15 | var assembly = host.LoadFromAssemblyPath(assemblyLocation); 16 | var type = assembly.GetType("lib.Class1"); 17 | WriteLine("Call GetString method:"); 18 | var method = type.GetMethod("GetString"); 19 | var returnValue = method.Invoke(null, null); 20 | WriteLine(returnValue); 21 | 22 | foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) 23 | { 24 | // only display assemblies not loaded in the Default AssemblyLoadContext 25 | if (AssemblyLoadContext.GetLoadContext(asm) == AssemblyLoadContext.Default) 26 | { 27 | continue; 28 | } 29 | 30 | WriteLine(asm.FullName); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/customloadcontext/customloadcontext.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/gutenapp/dialogue/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace dialogue 4 | { 5 | public class Class1 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/gutenapp/dialogue/dialogue.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/gutenapp/gutenapp/ComponentContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.Loader; 4 | 5 | namespace ComponentHost 6 | { 7 | public class ComponentContext : AssemblyLoadContext 8 | { 9 | 10 | private ComponentResolver[] _resolvers; 11 | 12 | public ComponentContext(string component, params ComponentResolver[] resolvers) 13 | { 14 | _resolvers = resolvers; 15 | Component = component; 16 | } 17 | 18 | private ComponentContext() 19 | { 20 | } 21 | 22 | public string Component {get; private set;} 23 | 24 | protected override Assembly Load(AssemblyName assemblyName) 25 | { 26 | return Assembly.Load(assemblyName); 27 | } 28 | 29 | public Assembly LoadAssemblyWithResolver(string assemblyFile) 30 | { 31 | if (_resolvers == null) 32 | { 33 | return null; 34 | } 35 | 36 | foreach (var resolver in _resolvers) 37 | { 38 | var componentResolution = resolver.SetComponent(Component); 39 | if (!componentResolution) 40 | { 41 | continue; 42 | } 43 | var libraryResolution = resolver.FindLibrary(assemblyFile); 44 | if (libraryResolution.ResolvedComponent) 45 | { 46 | return LoadFromAssemblyPath(libraryResolution.ResolvedPath); 47 | } 48 | } 49 | 50 | return null; 51 | } 52 | 53 | public static (ComponentContext, Assembly) CreateContext(string assemblyPath) 54 | { 55 | var host = new ComponentContext(); 56 | var assembly = host.LoadFromAssemblyPath(assemblyPath); 57 | return (host, assembly); 58 | } 59 | 60 | public static (ComponentContext, Assembly, T) CreateContext(string assemblyPath, string typeName) 61 | { 62 | var (ComponentContext, assembly) = CreateContext(assemblyPath); 63 | T obj = (T)assembly.CreateInstance(typeName); 64 | return (ComponentContext, assembly, obj); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/gutenapp/gutenapp/ComponentResolution.cs: -------------------------------------------------------------------------------- 1 | namespace ComponentHost 2 | { 3 | public struct ComponentResolution 4 | { 5 | public string RequestedComponent; 6 | public bool ResolvedComponent; 7 | public string ResolvedPath; 8 | public string[] Candidates; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/gutenapp/gutenapp/ComponentResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.IO; 4 | using System.Collections.Generic; 5 | 6 | namespace ComponentHost 7 | { 8 | public class ComponentResolver : IComponentResolver 9 | { 10 | public ComponentResolver() 11 | { 12 | BaseDirectory = new DirectoryInfo(AppContext.BaseDirectory); 13 | } 14 | 15 | public DirectoryInfo BaseDirectory { get; set; } 16 | 17 | public string Component { get; set; } 18 | 19 | public virtual bool SetComponent(string component) 20 | { 21 | Component = component; 22 | return true; 23 | } 24 | 25 | public virtual ComponentResolution FindLibrary(string library) 26 | { 27 | if (string.IsNullOrWhiteSpace(library)) 28 | { 29 | throw new ArgumentException("libraryName not set"); 30 | } 31 | 32 | return ProbeDirectoryForLibrary(BaseDirectory,library); 33 | } 34 | 35 | protected ComponentResolution ProbeDirectoryForLibrary(DirectoryInfo probingDir, string library) 36 | { 37 | var fileList = probingDir.GetFiles(library); 38 | if (fileList.Length != 1) 39 | { 40 | return GetFalseResolution(library); 41 | } 42 | var resolution = new ComponentResolution(); 43 | resolution.RequestedComponent = library; 44 | resolution.ResolvedComponent = true; 45 | resolution.ResolvedPath = fileList[0].FullName; 46 | return resolution; 47 | } 48 | 49 | protected ComponentResolution GetFalseResolution(string component) 50 | { 51 | var resolution = new ComponentResolution(); 52 | resolution.RequestedComponent = component; 53 | resolution.ResolvedComponent = false; 54 | return resolution; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/gutenapp/gutenapp/ComponentResolverBinDirectoryStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using ComponentHost; 5 | 6 | namespace ComponentResolverStrategies 7 | { 8 | public class ComponentResolverBinDirectoryStrategy : ComponentResolver 9 | { 10 | 11 | // The following directory structure demonstrates an expected 12 | // directory layout during development. 13 | // Looking up from app.exe: 14 | // app.exe (appbase is here) 15 | // netcoreapp2.1 16 | // debug | release 17 | // bin 18 | // app 19 | // Looking down from app dir: 20 | // app 21 | // component 22 | // bin 23 | // debug | release 24 | // netcoreapp2.1 25 | // component.dll (component that we want is here) 26 | 27 | // In the case that release or debug *app* directories are 28 | // discovered that build kind is preferred for probing 29 | 30 | private const string DEBUG = "debug"; 31 | private const string RELEASE = "release"; 32 | private const string BIN = "bin"; 33 | private const string DLL = ".dll"; 34 | private readonly string[] TARGETS = new string[] { "netcore", "netstandard" }; 35 | private string _appTFM = string.Empty; 36 | private string _buildKind = string.Empty; 37 | 38 | public override ComponentResolution FindLibrary(string library) 39 | { 40 | if (string.IsNullOrWhiteSpace(library)) 41 | { 42 | throw new ArgumentException("libraryName not set"); 43 | } 44 | return FindLibraryInComponentBinDirectory(library); 45 | } 46 | 47 | public override bool SetComponent(string component) 48 | { 49 | var (componentDirectoryFound, componentDirectory) = TryFindComponentDirectory(component); 50 | if (!componentDirectoryFound) 51 | { 52 | return false; 53 | } 54 | Component = component; 55 | BaseDirectory = componentDirectory; 56 | return true; 57 | } 58 | 59 | private (bool componentDirectoryFound, DirectoryInfo componentDirectory) TryFindComponentDirectory(string component) 60 | { 61 | // Expected location, per probingDir: app/bin/[buildKind]/tfm 62 | var probingDir = BaseDirectory; 63 | 64 | var count = 0; 65 | var dirName = string.Empty; 66 | while (count++ < 4) 67 | { 68 | probingDir = probingDir?.Parent; 69 | 70 | if (probingDir == null) 71 | { 72 | return False(); 73 | } 74 | 75 | dirName = probingDir.Name.ToLowerInvariant(); 76 | 77 | if (dirName == DEBUG || 78 | dirName == RELEASE) 79 | { 80 | _buildKind = dirName; 81 | } 82 | else if (dirName == BIN) 83 | { 84 | // Assumption: components are in peer directories 85 | // one level above bin 86 | probingDir = probingDir.Parent?.Parent; 87 | if (probingDir == null) 88 | { 89 | return False(); 90 | } 91 | break; 92 | } 93 | else 94 | { 95 | break; 96 | } 97 | } 98 | 99 | // Expected location, per probingDir: app 100 | var (componentDirFound, componentDir) = probingDir.TryNavigateDirectoryDown(component); 101 | 102 | if (!componentDirFound) 103 | { 104 | return False(); 105 | } 106 | 107 | return (true, componentDir); 108 | 109 | (bool, DirectoryInfo) False() 110 | { 111 | return (false, BaseDirectory); 112 | } 113 | } 114 | 115 | private ComponentResolution FindLibraryInComponentBinDirectory(string library) 116 | { 117 | var candidateLibraries = new List(); 118 | 119 | // Expected location, per probingDir: app/component 120 | var probingDir = BaseDirectory; 121 | 122 | var (binFound, binDir) = probingDir.TryNavigateDirectoryDown(BIN); 123 | 124 | if (!binFound) 125 | { 126 | return False(); 127 | } 128 | 129 | // Expected location, per probingDir: app/component/bin 130 | probingDir = binDir; 131 | 132 | DirectoryInfo releaseDir = null; 133 | DirectoryInfo debugDir = null; 134 | 135 | // these directories do not have uniform casing, hence foreach 136 | foreach (var dir in probingDir.GetDirectories()) 137 | { 138 | var lowerName = dir.Name.ToLowerInvariant(); 139 | if (lowerName == RELEASE) 140 | { 141 | releaseDir = dir; 142 | } 143 | else if (lowerName == DEBUG) 144 | { 145 | debugDir = dir; 146 | } 147 | } 148 | 149 | // Expected location, per probingDir: app/component/bin/[release | debug] 150 | if (releaseDir == null && debugDir == null) 151 | { 152 | return False(); 153 | } 154 | 155 | // Probe release and debug directories 156 | // prefering release 157 | if (_buildKind == RELEASE && releaseDir != null) 158 | { 159 | probingDir = releaseDir; 160 | } 161 | else if (_buildKind == DEBUG && debugDir != null) 162 | { 163 | probingDir = debugDir; 164 | } 165 | else if (releaseDir != null) 166 | { 167 | probingDir = releaseDir; 168 | } 169 | else if (debugDir != null) 170 | { 171 | probingDir = debugDir; 172 | } 173 | else 174 | { 175 | return False(); 176 | } 177 | 178 | return ProbeForLibrariesInTargetFrameworkDirectories(probingDir, library); 179 | 180 | ComponentResolution False() 181 | { 182 | var resolution = new ComponentResolution(); 183 | resolution.ResolvedComponent = false; 184 | return resolution; 185 | } 186 | } 187 | 188 | private ComponentResolution ProbeForLibrariesInTargetFrameworkDirectories(DirectoryInfo probingDir, string library) 189 | { 190 | var resolution = new ComponentResolution(); 191 | var candidateLibs = new List(); 192 | 193 | foreach (var target in TARGETS) 194 | { 195 | var dirs = probingDir.GetDirectories($"{target}*"); 196 | foreach (var dir in dirs) 197 | { 198 | var probeResult = ProbeDirectoryForLibrary(dir, library); 199 | if (probeResult.ResolvedComponent) 200 | { 201 | candidateLibs.Add(probeResult.ResolvedPath); 202 | } 203 | } 204 | } 205 | 206 | if (candidateLibs.Count == 0) 207 | { 208 | resolution.ResolvedComponent = false; 209 | } 210 | else 211 | { 212 | resolution.ResolvedComponent = true; 213 | resolution.Candidates = candidateLibs.ToArray(); 214 | resolution.ResolvedPath = resolution.Candidates[0]; 215 | } 216 | 217 | return resolution; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/gutenapp/gutenapp/ComponentResolverProductionStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using ComponentHost; 4 | 5 | // The following directory structure demonstrates an expected 6 | // directory layout for prod layouts. 7 | // Looking up from app.exe: 8 | // app.exe (appbase is here) 9 | // app 10 | // Looking down from app dir: 11 | // app 12 | // component 13 | // component.dll (component that we want is here) 14 | 15 | namespace ComponentResolverStrategies 16 | { 17 | public class ComponentResolverProductionStrategy : ComponentResolver 18 | { 19 | 20 | public override ComponentResolution FindLibrary(string library) 21 | { 22 | if (string.IsNullOrWhiteSpace(library)) 23 | { 24 | throw new ArgumentException("libraryName not set"); 25 | } 26 | 27 | var resolution = new ComponentResolution(); 28 | 29 | var (componentDirFound, componentDir) = BaseDirectory.TryNavigateDirectoryDown(Component); 30 | 31 | if (!componentDirFound) 32 | { 33 | resolution.ResolvedComponent = false; 34 | return resolution; 35 | } 36 | 37 | return ProbeDirectoryForLibrary(componentDir, library); 38 | } 39 | 40 | public override bool SetComponent(string component) 41 | { 42 | var (componentDirectoryFound, componentDirectory) = BaseDirectory.TryNavigateDirectoryDown(component); 43 | 44 | if (!componentDirectoryFound) 45 | { 46 | return false; 47 | } 48 | 49 | Component = component; 50 | BaseDirectory = componentDirectory; 51 | return true; 52 | 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/gutenapp/gutenapp/DirectoryInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | public static class DirectoryInfoExtensions 4 | { 5 | public static (bool, DirectoryInfo) TryNavigateDirectoryUp(this DirectoryInfo baseDir, string name) 6 | { 7 | if (baseDir.Parent?.Name == name) 8 | { 9 | return (true, baseDir.Parent); 10 | } 11 | return (false, baseDir); 12 | } 13 | 14 | public static (bool, DirectoryInfo) TryNavigateDirectoryDown(this DirectoryInfo baseDir, string name) 15 | { 16 | var list = baseDir.GetDirectories(name); 17 | if (list.Length == 1) 18 | { 19 | return (true, list[0]); 20 | } 21 | return (false, baseDir); 22 | } 23 | } -------------------------------------------------------------------------------- /src/gutenapp/gutenapp/IComponentResolver.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using ComponentHost; 3 | 4 | namespace ComponentHost 5 | { 6 | public interface IComponentResolver 7 | { 8 | ComponentResolution FindLibrary(string library); 9 | bool SetComponent(string component); 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/gutenapp/gutenapp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Runtime.Loader; 7 | using System.Threading.Tasks; 8 | using interfaces; 9 | using ComponentHost; 10 | using ComponentResolverStrategies; 11 | 12 | namespace guttenapp 13 | { 14 | class Program 15 | { 16 | static async Task Main(string[] args) 17 | { 18 | var books = new Dictionary { 19 | {"Pride and Prejudice", "http://www.gutenberg.org/files/1342/1342-0.txt"}, 20 | {"The War That Will End War", "http://www.gutenberg.org/files/57481/57481-0.txt"}, 21 | {"Alice’s Adventures in Wonderland","http://www.gutenberg.org/files/11/11-0.txt"}, 22 | {"Dracula","http://www.gutenberg.org/cache/epub/345/pg345.txt"}, 23 | {"The Iliad of Homer","http://www.gutenberg.org/cache/epub/6130/pg6130.txt"}, 24 | {"Dubliners","http://www.gutenberg.org/files/2814/2814-0.txt"}, 25 | {"Gulliver's Travels","http://www.gutenberg.org/files/829/829-0.txt"} 26 | }; 27 | 28 | var wordcountContext = new ComponentContext("wordcount", 29 | new ComponentResolverProductionStrategy(), 30 | new ComponentResolverBinDirectoryStrategy() 31 | ); 32 | 33 | var mostcommonwordsContext = new ComponentContext("mostcommonwords", 34 | new ComponentResolverProductionStrategy(), 35 | new ComponentResolverBinDirectoryStrategy() 36 | ); 37 | 38 | var wordCountAsm = wordcountContext.LoadAssemblyWithResolver("wordcount.dll"); 39 | var mostcommonwordsAsm = mostcommonwordsContext.LoadAssemblyWithResolver("mostcommonwords.dll"); 40 | 41 | var client = new HttpClient(); 42 | 43 | foreach(var book in books) 44 | { 45 | var url = book.Value; 46 | using(var stream = await client.GetStreamAsync(url)) 47 | using(var reader = new StreamReader(stream)) 48 | { 49 | var wordCount = (IProvider)wordCountAsm.CreateInstance("Lit.WordCount"); 50 | var mostcommonwords = (IProvider)mostcommonwordsAsm.CreateInstance("Lit.MostCommonWords"); 51 | Task line; 52 | while (!reader.EndOfStream) 53 | { 54 | line = reader.ReadLineAsync(); 55 | try 56 | { 57 | var wordCountTask = wordCount.ProcessTextAsync(line); 58 | var mostcommonwordsTask = mostcommonwords.ProcessTextAsync(line); 59 | await Task.WhenAll(wordCountTask, mostcommonwordsTask); 60 | } 61 | catch (Exception e) 62 | { 63 | Console.WriteLine(e.Message); 64 | throw e; 65 | } 66 | } 67 | 68 | var wordcountReport = wordCount.GetReport(); 69 | Console.WriteLine($"Book: {book.Key}; Word Count: {wordcountReport["count"]}"); 70 | Console.WriteLine("Most common words, with count:"); 71 | var mostcommonwordsReport = mostcommonwords.GetReport(); 72 | var orderedMostcommonwords = (IOrderedEnumerable>)mostcommonwordsReport["words"]; 73 | var mostcommonwordsCount = (int)mostcommonwordsReport["count"]; 74 | 75 | var index = 0; 76 | foreach (var word in orderedMostcommonwords) 77 | { 78 | if (index++ >= 10) 79 | { 80 | break; 81 | } 82 | Console.WriteLine($"{word.Key}; {word.Value}"); 83 | } 84 | } 85 | } 86 | 87 | Console.WriteLine(); 88 | foreach(var asm in AppDomain.CurrentDomain.GetAssemblies()) 89 | { 90 | var context = AssemblyLoadContext.GetLoadContext(asm); 91 | var def = AssemblyLoadContext.Default; 92 | var isDefaultContext = context == def; 93 | var isWordcountContext = context == wordcountContext; 94 | var isMostcommonwordsContext = context == mostcommonwordsContext; 95 | 96 | if (asm.FullName.StartsWith("System") && isDefaultContext) 97 | { 98 | continue; 99 | } 100 | 101 | Console.WriteLine($"{asm.FullName} {asm.Location}"); 102 | Console.WriteLine($"Default: {isDefaultContext}; WordCount: {isWordcountContext}; MostCommonWords: {isMostcommonwordsContext}"); 103 | } 104 | 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/gutenapp/gutenapp/gutenapp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exe 9 | netcoreapp2.1 10 | latest 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/gutenapp/interfaces/IProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace interfaces 6 | { 7 | public interface IProvider 8 | { 9 | Task ProcessTextAsync(Task text); 10 | Dictionary GetReport(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/gutenapp/interfaces/interfaces.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/gutenapp/mostcommonwords/MostCommonWords.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using System.Linq; 5 | using interfaces; 6 | 7 | namespace Lit 8 | { 9 | public class MostCommonWords : IProvider 10 | { 11 | private Dictionary _wordsCount = new Dictionary(); 12 | 13 | public Dictionary GetReport() 14 | { 15 | var orderedList = _wordsCount.OrderByDescending( e=> { return e.Value;}); 16 | var report = new Dictionary(); 17 | report.Add("words",orderedList); 18 | report.Add("count",_wordsCount.Count); 19 | return report; 20 | } 21 | 22 | public async Task ProcessTextAsync(Task text) 23 | { 24 | var line = await text; 25 | return UpdateWordCounts(line); 26 | } 27 | 28 | private int UpdateWordCounts(string text) 29 | { 30 | var wasSpace = false; 31 | var lengthTest = text.Length -1; 32 | var start = 0; 33 | var index = 0; 34 | var newwords = 0; 35 | 36 | if (text == string.Empty) 37 | { 38 | return 0; 39 | } 40 | 41 | Span buffer = stackalloc char[100]; 42 | 43 | while (true) 44 | { 45 | 46 | var c = text[index]; 47 | var isSpace = c == ' '; 48 | string loweredWord = String.Empty; 49 | 50 | if (index >= lengthTest && index > 0) 51 | { 52 | var span = text.AsSpan(start); 53 | 54 | if (text.Length >100) 55 | { 56 | loweredWord = span.ToString().ToLowerInvariant(); 57 | } 58 | else 59 | { 60 | span.ToLowerInvariant(buffer); 61 | loweredWord = buffer.Slice(0, text.Length).ToString(); 62 | } 63 | newwords += AddWord(loweredWord); 64 | break; 65 | } 66 | else if (isSpace && !wasSpace) 67 | { 68 | var end = index - start; 69 | var span = text.AsSpan(start, end); 70 | if (text.Length > 100) 71 | { 72 | loweredWord = span.ToString().ToLowerInvariant(); 73 | } 74 | else 75 | { 76 | span.ToLowerInvariant(buffer); 77 | loweredWord = buffer.Slice(0, end).ToString(); 78 | } 79 | wasSpace = true; 80 | start = index + 1; 81 | newwords += AddWord(loweredWord); 82 | } 83 | else if (!isSpace && wasSpace) 84 | { 85 | wasSpace = false; 86 | } 87 | index++; 88 | } 89 | return newwords; 90 | } 91 | 92 | private int AddWord(string word) 93 | { 94 | if (word == string.Empty) 95 | { 96 | return 0; 97 | } 98 | else if (_wordsCount.ContainsKey(word)) 99 | { 100 | _wordsCount[word]++; 101 | return 1; 102 | } 103 | else 104 | { 105 | _wordsCount.Add(word, 1); 106 | return 0; 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/gutenapp/mostcommonwords/mostcommonwords.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | netcoreapp2.1 9 | latest 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/gutenapp/wordcount/WordCount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using interfaces; 5 | 6 | namespace Lit 7 | { 8 | public class WordCount : IProvider 9 | { 10 | int _count = 0; 11 | int _lines = 0; 12 | public WordCount(){} 13 | 14 | public Task ProcessTextAsync(Task text) 15 | { 16 | _lines++; 17 | return CountAddAsync(text); 18 | } 19 | 20 | public Dictionary GetReport() 21 | { 22 | var d = new Dictionary(); 23 | d.Add("count",_count); 24 | d.Add("lines", _lines); 25 | return d; 26 | } 27 | 28 | public int CountAdd(string text) 29 | { 30 | var wasSpace = false; 31 | var count = 0; 32 | foreach(var t in text) 33 | { 34 | var isSpace = Char.IsWhiteSpace(t); 35 | if (isSpace && 36 | !wasSpace) 37 | { 38 | count++; 39 | wasSpace = true; 40 | } 41 | else if (!isSpace && wasSpace) 42 | { 43 | wasSpace = false; 44 | } 45 | } 46 | 47 | if (count > 0 && !wasSpace) 48 | { 49 | count++; 50 | } 51 | _count += count; 52 | return count; 53 | } 54 | 55 | public async Task CountAddAsync(Task text) 56 | { 57 | var t = await text; 58 | if (string.IsNullOrWhiteSpace(t)) 59 | { 60 | return 0; 61 | } 62 | var count = CountAdd(t); 63 | return count; 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/gutenapp/wordcount/wordcount.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | netstandard2.0 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/lib/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace lib 4 | { 5 | public class Class1 6 | { 7 | public static string GetString() 8 | { 9 | return "Bow ties are cool."; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/lib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/lib/out/lib.deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeTarget": { 3 | "name": ".NETStandard,Version=v2.0/release", 4 | "signature": "cfe1dc2a80602aef150a12815387068463a61a0d" 5 | }, 6 | "compilationOptions": {}, 7 | "targets": { 8 | ".NETStandard,Version=v2.0": {}, 9 | ".NETStandard,Version=v2.0/release": { 10 | "lib/1.0.0": { 11 | "dependencies": { 12 | "NETStandard.Library": "2.0.3" 13 | }, 14 | "runtime": { 15 | "lib.dll": {} 16 | } 17 | }, 18 | "Microsoft.NETCore.Platforms/1.1.0": {}, 19 | "NETStandard.Library/2.0.3": { 20 | "dependencies": { 21 | "Microsoft.NETCore.Platforms": "1.1.0" 22 | } 23 | } 24 | } 25 | }, 26 | "libraries": { 27 | "lib/1.0.0": { 28 | "type": "project", 29 | "serviceable": false, 30 | "sha512": "" 31 | }, 32 | "Microsoft.NETCore.Platforms/1.1.0": { 33 | "type": "package", 34 | "serviceable": true, 35 | "sha512": "sha512-RTmkgwugaI7tV0PjAwBX4ZKHbv/lMuIKUBeyIB0aosbGALFKIxiGvseJDF5ui5RBGbEJ5AvxZsGI0Rr6ZEfwaw==", 36 | "path": "microsoft.netcore.platforms/1.1.0", 37 | "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" 38 | }, 39 | "NETStandard.Library/2.0.3": { 40 | "type": "package", 41 | "serviceable": true, 42 | "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", 43 | "path": "netstandard.library/2.0.3", 44 | "hashPath": "netstandard.library.2.0.3.nupkg.sha512" 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/lib/out/lib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richlander/dotnet-core-assembly-loading/5888536f099dc9db214383c767ab9eb1724c114a/src/lib/out/lib.dll -------------------------------------------------------------------------------- /src/loadassemblybypath/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Runtime.Loader; 5 | using static System.Console; 6 | 7 | namespace loadlibrary 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | var assemblyLocation = "/Users/rlander/git/dotnet-core-assembly-loading/src/lib/out/lib.dll"; 14 | WriteLine("Load lib.dll"); 15 | var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyLocation); 16 | var type = assembly.GetType("lib.Class1"); 17 | WriteLine("Call GetString method:"); 18 | var method = type.GetMethod("GetString"); 19 | var returnValue = method.Invoke(null,null); 20 | WriteLine(returnValue); 21 | WriteLine("Print loaded (non-platform) assemblies:"); 22 | foreach(var asm in AppDomain.CurrentDomain.GetAssemblies()) 23 | { 24 | if (asm.FullName.StartsWith("System") || asm.FullName.StartsWith("netstandard") ) 25 | { 26 | continue; 27 | } 28 | 29 | WriteLine(asm.FullName); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/loadassemblybypath/loadassemblybypath.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test-prod.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | $BasePath = (Get-Item -Path "./").FullName 4 | $BaseGutenapp = Join-Path $BasePath "gutenapp" 5 | $GutenappDir = Join-Path $BaseGutenapp "gutenapp" 6 | $Gutenapp = Join-Path $GutenappDir "gutenapp.csproj" 7 | $WordcountDir = Join-Path $BaseGutenapp "wordcount" 8 | $Wordcount = Join-Path $WordcountDir "wordcount.csproj" 9 | $MostcommonwordsDir = Join-Path $BaseGutenapp "mostcommonwords" 10 | $Mostcommonwords = Join-Path $MostcommonwordsDir "mostcommonwords.csproj" 11 | 12 | dotnet publish $Gutenapp -c release -o out 13 | dotnet publish $Wordcount -c release -o out 14 | dotnet publish $Mostcommonwords -c release -o out 15 | 16 | Set-Location $BasePath 17 | 18 | $testdir = Join-Path . testdir 19 | New-Item -ItemType Directory -Path $testdir -Force 20 | Copy-Item ([System.IO.Path]::Combine($GutenappDir, "out", "*")) $testdir -Force 21 | New-Item -ItemType Directory -Path (Join-Path $testdir wordcount) -Force 22 | Copy-Item ([System.IO.Path]::Combine($WordcountDir, "out", "*")) (Join-Path $testdir wordcount) -Force 23 | New-Item -ItemType Directory -Path (Join-Path $testdir mostcommonwords) -Force 24 | Copy-Item ([System.IO.Path]::Combine($MostcommonwordsDir, "out", "*")) (Join-Path $testdir mostcommonwords) -Force 25 | 26 | dotnet (Join-Path $testdir gutenapp.dll) -------------------------------------------------------------------------------- /src/test.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | $BasePath = (Get-Item -Path "./").FullName 4 | $BaseGutenapp = Join-Path $BasePath "gutenapp" 5 | $GutenappDir = Join-Path $BaseGutenapp "gutenapp" 6 | $Gutenapp = Join-Path $GutenappDir "gutenapp.csproj" 7 | 8 | 9 | .\build.ps1 10 | 11 | dotnet run --project $Gutenapp -c debug 12 | dotnet run --project $Gutenapp -c release --------------------------------------------------------------------------------