Find classes implementing interface in PreApplicationStartMethod - c#

I have the situation where I cannot edit the global.asax yet I need to wire up a ServiceStack service. I'm using PreApplicationStartMethod which works well but where it gets interesting is I need to allow other developers to plugin their own services in to my service.
In case they have their own APIs defined within their assembly, I've created an interface they can implement; my thinking is I can find the assemblies by looking for classes which implements that and pass it to ServiceStack. This also works well.
The issue however is using AppDomain.CurrentDomain.GetAssemblies() which only loads those dlls which are referenced (which won't be the case) so I looked at using BuildManager.GetReferencedAssemblies() instead but I can't do that within PreApplicationStartMethod as it throws an error:
This method cannot be called during the application's pre-start initialization phase.
Does anyone know how I can work around this? Or use Application_Start? I have included my code below in case it helps:
private static Assembly[] GetServicesFromAssemblies()
{
var serviceType = typeof(IUCommerceApiService);
var assemblies = GetAssemblies();
return assemblies
.SelectMany(s => s.GetTypes())
.Where(serviceType.IsAssignableFrom)
.Select(t => t.Assembly)
.Distinct()
.ToArray();
}
// Works until app pool refresh
private static IEnumerable<Assembly> GetAssemblies()
{
return AppDomain.CurrentDomain.GetAssemblies();
}
// Throws error when called within PreApplicationStartMethod
private static IEnumerable<Assembly> GetAssemblies()
{
return new ReadOnlyCollection<Assembly>(BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList());
}

Related

How to call DI Confgure<T>(Config) with reflection?

I have ASP.NET Core Web API.
I want to inject in DI container IOptions<MySetting> options. In Startup class it injected like this -
services.Configure<MySettings>(Configuration.GetSection("MySetting"));. Where MySettings is class with some fields and a section with the same name in appsetings.json.
Also i want inject it with other way. With reflection:
var configureMethod = typeof(OptionsConfigurationServiceCollectionExtensions).GetMethods()
.Single(m => m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(MySettings));
I sure configureMethod matches with services.Configure<MySettings>(Configuration.GetSection("MySetting"));.
But when I try to invoke this:
configureMethod.Invoke(null, new object?[] { services, configuration.GetSection("MySettings") });
I get error:
Unhandled exception. System.ArgumentException: Cannot instantiate implementation type 'System.ComponentModel.Design.DesignerOptionService' for service type 'System.ComponentModel.Design.IDesignerOptionService'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.Populate()
...
What am I doing wrong and how to fix it?
I tried add injecting with reflection for automatically registration {featureNmae}Setting classes in project, because there can be a lot of such classes in project.
In project i have many assemblies and i was getting them for suitable types with AppDomain.CurrentDomain.GetAssemblies() method.
Now i change it on Assembly.GetExecutingAssembly() and it works!

calling AddAutoMapper once per assembly instead of passing in multiple assemblies?

I have a multi layered project with a web API project and a library project. Both projects rely on AutoMapper (and AutoMapper extensions for Microsoft.Extensions.DependencyInjection). Based on this
https://docs.automapper.org/en/latest/Dependency-injection.html#asp-net-core
in the Startup file I'm setting up AutoMapper for all the layers
Assembly apiAssembly = Assembly.GetExecutingAssembly();
Assembly myLibraryAssembly = Assembly.Load("MyLibrary");
services.AddAutoMapper(apiAssembly, myLibraryAssembly);
As you can see here, the API project needs to know about all the referenced library projects by loading them via name. I would prefer a way that every project is able to register itself. Based on this sample code
https://github.com/jasontaylordev/CleanArchitecture/blob/master/src/Application/DependencyInjection.cs
I created such a file in my library project
public static class DependencyInjection
{
public static IServiceCollection AddMyLibrary(this IServiceCollection services)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly(); // MyLibrary assembly
services.AddAutoMapper(executingAssembly);
// ... setup other services
return services;
}
}
and in the API project I can now do this
Assembly executingAssembly = Assembly.GetExecutingAssembly();
services.AddAutoMapper(executingAssembly);
services.AddMyLibrary();
The code seems to work fine but AddAutoMapper will be called twice. Once for the API assembly and once for the library assembly. Should I stick to the first approach because AutoMapper should only be added once or is it fine to separate it?
The accepted answer was correct at the time but it would appear that things have changed in the recent past.
The AutoMapper.Extensions.Microsoft.DependencyInjection package has been updated to allow the call to AddAutoMapper() multiple times.
See PR Use Microsoft.Extensions.Options to configure AutoMapper for details. You will need to update the package to version 8.0.0 or higher to use it.
The code seems to work fine but AddAutoMapper will be called twice. Once for the API assembly and once for the library assembly. Should I stick to the first approach because AutoMapper should only be added once or is it fine to separate it?
You should stick to the first approach, because AddAutoMappper does nothing when called for the second, third etc. time, thus profiles and other AM related types from the assemblies passed to these calls won't be registered.
It can be seen in the beginning of the implementation of the private method which is called by all public AddAutoMapper overloads:
private static IServiceCollection AddAutoMapperClasses(IServiceCollection services, Action<IServiceProvider, IMapperConfigurationExpression> configAction,
IEnumerable<Assembly> assembliesToScan, ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
{
// Just return if we've already added AutoMapper to avoid double-registration
if (services.Any(sd => sd.ServiceType == typeof(IMapper)))
return services;
followed by the actual registration code, which at the end registers IMapper.
Currently there is an open issue Allow usage of Options Pattern to configure AutoMapper #132 with exactly the same concerns as yours.
You can use abp moudule system to make the library register for themselves.
The doc is here: https://docs.abp.io/en/abp/latest/Module-Development-Basics
But the framework is a little heavy.If you don't want to use it,
I think you can do it this way: every library register the automapper itself ,and call the other library's register function it depends on.
Each library can add a helper class to do the registration
public static class ApiRegisterHelper
{
public static Assembly GetAssembly()
{
return Assembly.GetExecutingAssembly();
}
public static IServiceCollection AddLibrary(IServiceCollection services)
{
Assembly executingAssembly = GetAssembly();
services.AddAutoMapper(executingAssembly);
DaoRegisterHelper.AddLibrary(services);
return services;
}
}
public static class DaoRegisterHelper
{
public static Assembly GetAssembly()
{
return Assembly.GetExecutingAssembly();
}
public static IServiceCollection AddLibrary(IServiceCollection services)
{
Assembly executingAssembly = GetAssembly();
services.AddAutoMapper(executingAssembly);
OtherRegisterHelper.AddLibrary();
return services;
}
}
I havn't tested it,but it might be work.Hope it helps.

How to load controllers from different versions of the same assembly

I am trying to solve the following problem and i am not exactly sure how to do it:
I am building a web server that has differenty APIs/Controllers that are loaded from .dll-Files on Startup. It will run in a linux docker container and is implemented as an ASP-NET Webapplication & .net Core 2.1 .
The loading of assemblies that contain controllers works fine by doing something like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc().AddApplicationPart(AssemblyLoadContext.Default.LoadFromAssemblyPath("/PATH/APIPlugin.dll"));
}
This application must have versioned REST-APIs that means: I need to load the same assembly multiple times in different versions. then i need to have some kind of routing between the versions.
For example:
/api/data/latest/
/api/data/v1/
I cannot use AssemblyLoadContext.Default.LoadFromAssemblyPath to load multiple versions of the same assembly. I also tried to grab the controller from the assembly and creating an instance of it like this:
var controllerAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath("/PATH/APIPlugin.dll");
var pluginType = controllerAssembly ExportedTypes.First<Type>();
var pluginInstance = (ControllerBase)Activator.CreateInstance(pluginType);
services.Add(new ServiceDescriptor(pluginType, pluginInstance));
This throws no exception but ultimately does not work. But i am pretty new to ASP.Net so this might very well be nonsense and i would have to find a solutuion to route between the different versions, even if it would work like this.
My Question:
How would one approach this requirement ?
Is it a god idea/possible to load multiple Controllers from the "same" assembly ? If yes, how would one achieve this?
Or would it be a better solution to have one controller that does all the routuing and load some self-defined implementation from the assemblies. So that the controller would route between the versions, and api-methods?
I was able to find a sultion while tinkering around:
public class ControllerPluginProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
var basePath = AppContext.BaseDirectory;
var pluginPath = Path.Combine(basePath, "plugins");
foreach (var file in Directory.GetFiles(pluginPath, "*.dll")){
var assembly = Assembly.LoadFile(file);
var controllers = assembly.GetExportedTypes().Where(t => typeof(ControllerBase).IsAssignableFrom(t));
foreach (var candidate in controllers)
{
feature.Controllers.Add(candidate.GetTypeInfo());
}
}
}
}
In Startup:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc().ConfigureApplicationPartManager(m =>
m.FeatureProviders.Add(new ControllerPluginProvider()));
}
This lead to the following error when the same assembly, and therefore a Controller with the same name was loaded: Attribute routes with the same name 'Get' must have the same template
I was able to fix it, and also add versioning with the versioning library:
https://github.com/microsoft/aspnet-api-versioning
[ApiVersion("2.0")]
[ApiController]
[Route("api/v{version:apiVersion}/MyController")]
public class MyControllerController : ControllerBase
{
}
Now the only step that is missing, is routing the /latest/ path to the most recent controller of the given type. I have not found a solution to this yet, but this should be doable.

StructureMap not finding generic Types when used with scan.TheCallingAssembly

I have an Application-Assembly, which contains an amount of generic IMapper Interfaces. Each implementation has said Types and two methods to map the objects in each direction.
Using AutoMapper, I went ahead using their documentation and created an IocRegistry-Class in said Application-Assembly, which should register all Types:
public class IocRegistry : Registry
{
public IocRegistry()
{
Scan(
scan =>
{
scan.TheCallingAssembly(); // Scan this assembly
scan.WithDefaultConventions();
scan.AddAllTypesOf(typeof(IMapper<,>));
});
}
}
And if I use the "WhatDidIScan"-method, I can see the configuration:
IocRegistry Scanner #1 Assemblies
Mmu.Ddws.Application, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Conventions
Default I[Name]/[Name] registration convention
Find and register all types implementing Mmu.Ddws.Application.Common.Mapping.IMapper`2
Yet, as soon as I try to get an instance of such a Mapper, I get the error it didn't find it.
Interesting enough, if I'm registerering the Interfaces on my WebServices-Assembly, which is the starting point, via:
internal static class IocInitialization
{
internal static IServiceProvider InitializeIoc(IServiceCollection services)
{
var container = new Container();
container.Configure(
config =>
{
config.Scan(
scan =>
{
scan.AssembliesFromApplicationBaseDirectory();
scan.LookForRegistries();
scan.WithDefaultConventions();
scan.AddAllTypesOf(typeof(IMapper<,>));
});
config.Populate(services);
});
Debug.WriteLine(container.WhatDidIScan());
var result = container.GetInstance<IServiceProvider>();
return result;
}
}
I get exactly the same convention:
Conventions
StructureMap.Graph.FindRegistriesScanner
Default I[Name]/[Name] registration convention
Find and register all types implementing Mmu.Ddws.Application.Common.Mapping.IMapper`2
And yet, this way it works. I have the Interface and the Implementations in the Application-Assembly, therefore the most discussions resolving around Interfaces in different Assemblies, doesn't seem to fit here. So my best bet would be, that scanning the calling Assembly is kinda wrong, but I couldn't find any remarks regarding this?
See this for details and an example on type scanning with open generic types: http://structuremap.github.io/generics/

Method of type loaded by reflection is not being called

I am seeing some weird behaviour with a "plugin" system I am developing for an ASP.NET Web API project. The plugins are DLLs which are copied into the bin folder. This means they are already in the current AppDomain.
Each plugin has a configuration class which implements IPluginConfiguration.
The following class configures the plugins by finding all of the classes in the current AppDomain which implement the IPluginConfiguration interface. This is called from Global.asax.cs
public class PluginConfigurator
{
/* ... */
public static void Register(HttpConfiguration config)
{
var pluginConfigs = GetPluginConfigurations();
try
{
foreach (var pluginConfigType in pluginConfigs)
{
var pluginConfig = (IPluginConfiguration) Activator.CreateInstance(pluginConfigType);
pluginConfig.Configure(config);
Logger.Log("Configured Type: {0}", pluginConfigType.FullName);
}
}
catch (ReflectionTypeLoadException e)
{
Logger.Log("Error of type: {0}", e.GetType().Name);
}
}
private static IEnumerable<Type> GetPluginConfigurations()
{
return AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(t => t.GetTypes())
.Where(type => !type.IsInterface)
.Where(type => typeof(IPluginConfiguration).IsAssignableFrom(type));
}
}
My test plugin has the following config.
public class PluginConfiguration : IPluginConfiguration
{
/* ... */
public void Configure(HttpConfiguration config)
{
try
{
Logger.Log("Configuring test plugin");
RegisterRoutes(config);
}
catch (Exception e)
{
Logger.Log("Error of type {0} in configuration", e.GetType().Name);
}
}
/* ... */
}
So I would expect my log file to say either
Configuring test plugin
Configured Type: MyNameSpace.PluginConfiguration
or
Configuring test plugin
Error of type MyNameSpace.PluginConfiguration in configuration
Configured Type: MyNameSpace.PluginConfiguration
But actually I just get
Configured Type: MyNameSpace.PluginConfiguration
There is other logging that occurs before this. I thought it could be a problem with the logging, so I made it create a file instead in the PluginConfiguration class, which it also didn't do. It appears that my PluginConfiguration class isn't being called.
I created a unit test calling the PluginConfigurator class and the method does get called.
Any suggestions of what could be causing this or how to fix it?
The code works for me (ie. I see both logging messages) when I stick it into the Application_Start() of the web app I'm working on and load a secondary DLL from the bin folder.
The first thing I would do to reassure yourself the method really is being called is explicitly throw an exception in your Configure(). In that case you should see the "Error of type:" string. (Perhaps throw a custom string in your exception to be absolutely sure.)
Once you're sure it's being called you can start working on the logging problem. My best guess, having tried and failed with desperation logging strategies before, would be permissions. Since this is presumably a web app, write permissions are going to be pretty restricted. You could also be running into ASP.NET's shadow copy and simply not finding the logfile.
In any case, you are probably not running into a mysterious called-but-not-called situation due to the reflection. It is most likely a more mundane problem.
The second thing I would do is forget trying to make a custom plugin system and use MEF instead, heh.

Categories

Resources