I am using the Process.Start() method to launch a specific executable as needed. The executable is tailor-made, there are many of these, and the paths & names are in the database together with the job they do.
Is there another way to launch them in-process, if they all implement an interface? I want to be able to debug them and have better type safety when calling them.
I can set a library project in each solution, then instantiate that library and have it do the job instead of the executable. The question is about a mechanism that would allow me to build against the interface then load the library by its name at runtime.
You can use something like the following if you want custom solution. Otherwise, you can use modular patterns implemented in Prism with Unity or MEF.
public interface IPlugin
{
void Start();
}
// You can change this to support loading based on dll file name
// Use one of the other available Assembly load options
public void Load(string fullAssemblyName)
{
var assembly = System.Reflection.Assembly.Load(fullAssemblyName);
// Assuming only one class implements IPlugin
var pluginType = assembly.GetTypes()
.FirstOrDefault(t => t.GetInterfaces()
.Any(i=> i == typeof(IPlugin)));
// Concrete class implementing IPlugin must have default empty constructor
// for following instance creation to work
var plugin = Activator.CreateInstance(pluginType) as IPlugin;
plugin.Start();
}
Related
I have a main console application and a plugin as a dll. Plugins must implement IPlugin interface. These are three projects
1) Main console application project
2) Plugin project
3) Abstraction project which has IPlugin interface which is used by both (1) & (2) projects
The plugins are copied to a folder under main console application. So project structure is like below. Here the ...Messaging folder has the main plugin dll and it's references.
Note that IPlugin.dll is in two places (under main console app and plugin folder)
Then I load the plugin dynamically using AssemblyLoadContext below
class JobLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public JobLoadContext(string pluginPath)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly? Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}
In the main console application I use above class to Load the plugins and it's dependencies which is in the subdirectory. Once the plugin is loaded; I query for types implementing Iplugin interface and calls a Init() method in the plugin. That is wired up as below.
var loadContext = new JobLoadContext(path);
var asm = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path)));
Once the assembly is loaded I query for types that implements Iplugin interface like;
var moduleInitializerType = asm.GetTypes().FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
//other code to call Init() method inside plugin that implements IPlugin interface
The issue is moduleInitializerType variable is always null! However if I type the asm.GetTypes()….. Statement in watch window it shows the type.
I feel this is related to how the loadcontext works. Note that there is a IPlugin.Dll loaded by main application and another IPlugin.dll loaded to my custom loadcontext (even though they are same version etc…). So from main application, when I ask for types that implement IPlugin.dll like;
var moduleInitializerType = asm.GetTypes().FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
The typeof(Iplugin) may be refering to the dll in main application folder. But the MyPlugin.dll loaded by my custom loadcontext has a reference to IPlugin interface in it's own directory. This may be the conflict why it can't find the class implementing IPlugin interface. However it's very strange that the same statement works in the watch window. If I let the application to just run it crashes anyway.
I tried using AssemblyLoadContext.Default.LoadFromAssemblyName(….) to load my plugins but in that case it fails to load some dependency dlls in the plugin folder. All of these problems go away If I add my plugin project to main application and compile.
Is there a way to may be merge loadcontexts properly so I copy my custom loadcontext data to AssemblyContext.Default which contains static references to main project?
Has anyone come across this problem?
Your guess is right, check this article: https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/loading-managed#algorithm
Because you explictly loaded IPlugin.Dll from the plugin folder, in fact it was already loaded into AssemblyLoadContext.Default by your application, any other AssemblyLoadContext instances will look for assemblies from AssemblyLoadContext.Default after Load fails, so you need to skip the step (or simply delete IPlugin.Dll in the plugin folder).
protected override Assembly? Load(AssemblyName assemblyName)
{
if (assemblyName.Name == "IPlugin")
return null;
....
}
I'm implementing a framework for C# on ASP.NET using Dotnet 6. I want part of the framework to be extensible by outside parties; they just need to implement a few classes and we can integrate their work via Nuget or direct assembly reference.
Part of what they need to complete is a Registration Class so they can define how their engine should be registered with the dependency injection container. This is an example of what I'd expect 3rd parties to supply:
public class EchoServiceRegistration : IRegisterDI
{
public IServiceCollection Register(IServiceCollection serviceCollection)
{
serviceCollection.TryAddSingleton<EchoEngine>();
return serviceCollection;
}
}
In the consuming application, I'm looking for all of the classes that implement the IRegisterDI interface using the AppDomain class, which is a riff off this SO answer https://stackoverflow.com/a/26750/2573109. (Note: I switched to using name-based matching just for troubleshooting and will change it back to a better implementation once properly solved):
List<Type> allRegistrationClasses = AppDomain
.CurrentDomain
.GetAssemblies()
.SelectMany(it => it.GetTypes())
.Where(tp => tp.GetInterfaces()
.Any(inter => inter.Name == nameof(IRegisterDI)))
.ToList();
As written, this returns 0 types.
On the next troubleshooting iteration, I proved that the registration class is available to the caller. So, I manually created an instance of the EchoServiceRegistration class, as seen below. This time, the AppDomain contains a single entry for EchoServiceRegistration (as expected).
var allRegistrationClasses = AppDomain.CurrentDomain.GetAssemblies().SelectMany(it => it.GetTypes()).Where(tp => tp.GetInterfaces().Any(inter => inter.Name == nameof(IRegisterDI))).ToList();
var echoServiceRegistration = new EchoServiceRegistration();
echoServiceRegistration.Register(builder.Services);
if (allRegistrationClasses.Count is not 1) throw new InvalidOperationException(); // Does not throw an exception
To prove I didn't accidentally fix something, I commented out the two lines related to Echo Service Registration, and allRegistrationClasses again contains 0 records.
var allRegistrationClasses = AppDomain.CurrentDomain.GetAssemblies().SelectMany(it => it.GetTypes()).Where(tp => tp.GetInterfaces().Any(inter => inter.Name == nameof(IRegisterDI))).ToList();
// var echoServiceRegistration = new EchoServiceRegistration();
// echoServiceRegistration.Register(builder.Services);
if (allRegistrationClasses.Count is not 1) throw new InvalidOperationException(); // Throws an exception
My gut reaction is I don't understand how the AppDomain determines what assemblies to load. I started reading the documentation Microsoft provides about it, but it seems like a deep topic and I won't have a more clear understanding without quite a bit of dedicated reading.
How do I guarantee the full set of classes are available when calling this during the DI Container build? Please let me know if I can provide any additional detail or clarity.
It looks to me like on your first test, the assembly is not loaded into the app domain because it's not used.
On your second test, you are manually creating an instance so you are forcing the loading of the assembly. Note that even if the assembly is added as a reference, it won't be loaded unless it's actually needed.
You need to explicitly load the assemblies for them to be scanned. One way of doing this would be to have all extension libraries live in a particular folder from which you can then load them before registration:
var files = Directory.GetFiles(#"C:\my-framework\extensions");
foreach (var file in files)
{
var extension = Path.GetExtension(file);
if(extension == ".dll")
{
var asm = Assembly.LoadFile(file);
Console.WriteLine($"Loaded extension [{asm.FullName}]");
}
}
Once the assemblies are loaded, you should get the desired results.
This is an overly-simplified example but it should be enough to get you going.
In my solution I have three projects like this.
I copy Common.dll and Service.dll in a folder like d:\libs and use the below code with type scan
ObjectFactory.Initialize(x =>
{
x.Scan(xx =>
{
xx.AssembliesFromPath(#"d:\libs");
xx.LookForRegistries();
});
});
//I have PersonService that implement IPersonService
namespace Common
{
public interface IPersonService
{
string SayHello(string name);
}
}
namespace Services
{
public class PersonService : IPersonService
{
public string SayHello(string name)
{
return string.Format("Hello {0}", name);
}
}
}
After Initialize my dependencies when I want get instance from IPerson I get this error
var personService = ObjectFactory.GetInstance<IPersonService>();
{"No default Instance is registered and cannot be automatically determined for type 'Common.IPersonService'\r\n\r\nThere is no configuration specified for Common.IPersonService\r\n\r\n1.) Container.GetInstance(Common.IPersonService)\r\n"}
Add xx.WithDefaultConventions(); as well.
And when you are designing a plugin system by using StructureMap, the host project shouldn't have a reference to any of the plugins. Only the interface/contract plugin should be referenced and this assembly shouldn't be copied to d:\libs folder. In other words, the current application domain shouldn't have 2 instances of any assemblies. So if you want to use IPersonService interface in the host program directly, add a reference to Common.dll and don't copy it to d:\libs folder to avoid duplication. And now the host project shouldn't have a reference to Services.dll too.
You can use the WithDefaultConventions() in your call to Scan which will internally use a DefaultConventionScanner.
You can read the source code or look at the documentation but it does this:
var interfaceName = "I" + concreteType.Name;
return concreteType.GetInterfaces().FirstOrDefault(t => t.Name == interfaceName);
During a default convention scan, each concrete type scanned will look for the first interface of the class name preceded by the letter "I". Therefore, while scanning, IFooService with be automatically registered and assigned if FooService is found during the scan, therefore allowing you to not have to explicitly say
For<IFooService>().Use<FooService>()
The exception you are seeing will always be thrown when there is no registered concrete implementation for the type you requested.
The types you expected to be registered are not because you are scaning with xx.LookForRegistries() which looks for StructureMap registries (you can read more about registries here - http://structuremap.github.io/registration/registry-dsl/). If you create such registries with proper registrations then SM will find them and registeres.
As already mentioned the altrernative is to use xx.WithDefaultConventions(). Take a look at this edge case when using default convention the expected type was not registered - https://stackoverflow.com/a/27449018/4336786
So here is my issue. I have a complex archetecture of interfaces and abstract classes that I am trying to load up via Assembly.LoadFrom("x.dll"). When certain types that have an interface implementation where the implementation is explicit in a base class are trying to be loaded, I am getting a TypeLoadException saying:
Method 'MyMethod' in type 'MyPart2DerivedType' from assembly 'MyPart2Assembly, version...' does not have an implementation. I am trying to understand why this is as I have gone through several articles and have even attempted to delete the obj files and dlls manually. Here are the references to what I have done so far:
Solution to TypeLoadException
TypeLoadException says 'no implementation', but it is implemented
Visual Studio Forumns: TypeLoadException
Private accessors and explicit interface implementation
So here is my example code:
//This is in project 1
public interface IFooPart1
{
void DoStuff();
}
//This is in project 2
public interface IFooPart2
{
void DoOtherStuff();
}
//This is in project 3
public interface IFooPart3: IFooPart1, IFooPart2
{
void DoEvenMoreStuff();
}
//This is in project 4
public abstract class MyBaseType: IFooPart1, IFooPart2
{
void IFooPart1.DoStuff()
{
DoStuffInternal();
}
void IFooPart2.DoOtherStuff()
{
DoOtherStuffInternal();
}
}
//This is in project 5
public class MyDerivedType: MyBaseType, IFooPart3
{
public void DoEvenMoreStuff()
{
//Logic here...
}
}
//Only has references to projects 1, 2, & 3 (only interfaces)
public class Program
{
void Main(params string[] args)
{
//Get the path to the actual dll
string assemblyDll = args[0];
//Gets the class name to load (full name, eg: MyNameSpace.MyDerivedType)
string classNameToLoad = args[1];
//This part works...
var fooAssembly = Assembly.LoadFrom(assemblyDll);
//Here we throw a TypeLoadException stating
// Method 'DoStuff' in type 'MyDerivedType' from assembly 'Project 5...' does
// not have an implementation.
Type myDerivedTypeExpected = Assembly.GetType(classNameToLoad);
}
}
Note: If I move the explicit implementation to MyDerivedType instead of MyBaseType it works... but I don't get why I would have to do that. Seems like I should be able to. This code is only an example, the actual code has a factory that returns the loaded class but only via the interface type. (eg: var myDerivedType = GetInstance();)
Okay for everyone that is interested in my stupid fix. Here was my problem:
Project6 (which was the console app) has PROJECT references to the other projects, not references to the dlls in the location that they are supposed to build to. The other projects actually were being built to a specific repository area. So, the console application was using it's own version of the dll's when it was trying to automatically load the dependancies. This evidently made some other type way down there that was being dynamically loaded to not be loaded because it was not in the same folder as the dlls that were there...
So in short, Assembly.LoadFrom might cause you to load an assembly twice, but .NET treats it like a different assembly!!! This may introduce some real odd errors when trying to dynamically load types!!!
Please learn from my frustration/mistake. Fiends don't let freinds DI alone (code review is key to catching this stupid stuff).
I've had a similar problem caused by one of my projects having a reference to an older version of a nuget package dependency. The older version didn't have an implementation for one of the methods.
My MVC project uses Autofac 3.0 and I want to RegisterType by reflection.
First defined one interface and implementation named like IRegisterDemo and RegisterDemo less parameters.
I tried to use builder.RegisterType(typeof(RegisterDemo)).As(typeof(IRegisterDemo)) in my Application_Start method, it successed, but it's not my purpose.
I want to define one attribute like UseAutofacAttribute to reflect the types and use RegisterType(ImplType).As(InterfaceType), when I wrote the code in Application_Start, it worked, so I built another project to do this further and referenced the project dll in my MVC project, and in Application_Start just run one static method, unfortunately it failed.
So I want to know the reason and how to change?
If you want to make your registrations attribute based, I'd suggest you use Autofac's MEF adapter. It will let you decorate your types with the ExportAttribute and be picked up by your ContainerBuilder.
Example
[Export(typeof(IRegisterDemo))] // <-- This exports RegisterDemo as IRegisterDemo
public class RegisterDemo : IRegisterDemo { }
// Example method to register assemblies with ContainerBuilder
public void RegisterPartsFromReferencedAssemblies(ContainerBuilder builder)
{
// Get referenced assemblies
var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>();
// Create an AssemblyCatalog from each assembly
var assemblyCatalogs = assemblies.Select(x => new AssemblyCatalog(x));
// Combine all AssemblyCatalogs into an AggregateCatalog
var catalog = new AggregateCatalog(assemblyCatalogs);
// Register the catalog with the ContainerBuilder
builder.RegisterComposablePartCatalog(catalog);
}
This example shows how to export a type and how to register referenced assemblies with the ContainerBuilder. You can then do container.Resolve<IRegisterDemo>() to get the exported part (or have it injected into a class).
NOTE: This will register exported parts from all referenced assemblies, including:
Assemblies specified in the assemblies element of the Web.config file
Assemblies built from custom code in the App_Code directory
Assemblies in other top-level folders