I'm trying to add automapping models with reflection, I created an interface IMapFrom<> and implemented it in all dtos.
Then I created class
public class MappingProfile : Profile
{
public MappingProfile(Assembly assembly)
=> ApplyMappingsFromAssembly(assembly);
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly
.GetExportedTypes()
.Where(t => t
.GetInterfaces()
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
const string mappingMethodName = "Mapping";
var methodInfo = type.GetMethod(mappingMethodName)
?? type.GetInterface("IMapFrom`1")?.GetMethod(mappingMethodName);
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
And add it in service collection
public static IServiceCollection AddAutoMapperProfile(IServiceCollection services, Assembly assembly)
=> services
.AddAutoMapper(
(_, config) => config
.AddProfile(new MappingProfile(Assembly.GetCallingAssembly())),
Array.Empty<Assembly>());
Why the class instance is not created? Because of this I can't convert the model into a dto
Try like this:
Define IMapFrom:
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
Then add Mapping profile:
public class MappingProfile : Profile
{
public MappingProfile()
{
ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(t => t.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping")
?? type.GetInterface("IMapFrom`1")?.GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
Create DI method:
public static IServiceCollection AddAutoMapperProfile(this IServiceCollection services)
{
var assembly = Assembly.GetExecutingAssembly();
services.AddAutoMapper(assembly);
return services;
}
and at the end call DI method that you created:
...
services.AddAutoMapperProfile();
...
Related
I am trying to take a namespace find all the classes that end with service then add them as a singleton to my service.
foreach(Type serviceClass in GetTypesInNamespace(Assembly.GetExecutingAssembly(), "MyProject.Services"))
{
services.AddSingleton<serviceClass.GetType()>;
}
This does not compile, so any help would be appreciated.
The best approach that I found is based on this.
Would required some mother methods and classes:
You need to call this inside you Configure:
services.RegisterAssemblyPublicNonGenericClasses()
.Where(c => c.Namespace == "")
.AsPublicImplementedInterfaces();
Create this class:
public class AutoRegisteredResult
{
internal AutoRegisteredResult(Type classType, Type interfaceType)
{
Class = classType;
Interface = interfaceType;
}
public Type Class { get; }
public Type Interface { get; }
}
And these methods:
public static IEnumerable<Type> RegisterAssemblyPublicNonGenericClasses(this IServiceCollection services,
params Assembly[] assemblies)
{
if (assemblies.Length == 0)
assemblies = new[] { Assembly.GetCallingAssembly() };
var allPublicTypes = assemblies.SelectMany(x => x.GetExportedTypes()
.Where(y => y.IsClass && !y.IsAbstract && !y.IsGenericType && !y.IsNested));
return allPublicTypes;
}
public static IList<AutoRegisteredResult> AsPublicImplementedInterfaces(this IEnumerable<Type> autoRegData)
{
var result = new List<AutoRegisteredResult>();
foreach (var classType in autoRegData)
{
var interfaces = classType.GetTypeInfo().ImplementedInterfaces;
foreach (var infc in interfaces)
{
result.Add(new AutoRegisteredResult(classType, infc));
}
}
return result;
}
I have a .NET Core WEB API project and I would like to be a container for multiples "projects" as .NET CORE Class Library.
What I have is:
Solution "Orbit" with a .NET Core Web API project on it.
Solution "SpaceRadar" with a .NET Core Class Library.
First, inside my "Orbit" project, the Startup class, what I have made so far:
public void ConfigureServices(IServiceCollection services)
{
this.ConfigureMVC(services);
this.ConfigureIOC(services);
}
private void ConfigureMVC(IServiceCollection services)
{
IMvcBuilder mvcBuilder = services.AddMvc(option => { option.EnableEndpointRouting = false; });
// for each assembly inside modules directory, add the controllers
foreach (string assemblyPath in Directory.GetFiles($"{System.AppDomain.CurrentDomain.BaseDirectory}/Modules", "*.dll", SearchOption.AllDirectories))
{
var assembly = Assembly.LoadFile(assemblyPath);
mvcBuilder.AddApplicationPart(assembly);
}
}
This part works well, since we can trigger the Controller that is defined inside my SpaceRadar project.
However I wanted to use dependency injection in my class library project, if possible by scanning the dll to get all the types that extends IScopedServices / ISingletonServices / ITransientServices.
But honestly, I have no idea where to register my interface and their respective implementation.
I tried this solution:
private void ConfigureIOC(IServiceCollection services)
{
// Store all the type that need to be injected in the IOC system
List<Type> implementationTypes = new List<Type>();
List<Type> singletons = new List<Type>();
List<Type> scopeds = new List<Type>();
List<Type> transients = new List<Type>();
// for each assembly, load it, populate the type list of things to be injected
foreach (string assemblyPath in Directory.GetFiles(System.AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.AllDirectories))
{
var assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
implementationTypes.AddRange(assembly.GetExportedTypes().Where(type => type.IsClass && (type.GetInterface("ISingletonServices") != null || type.GetInterface("IScopedServices") != null || type.GetInterface("ITransientServices") != null)));
singletons.AddRange(assembly.GetExportedTypes().Where(type => type.IsInterface && type.GetInterface("ISingletonServices") != null));
scopeds.AddRange(assembly.GetExportedTypes().Where(type => type.IsInterface && type.GetInterface("IScopedServices") != null));
transients.AddRange(assembly.GetExportedTypes().Where(type => type.IsInterface && type.GetInterface("ITransientServices") != null));
}
// Register into the service collection
foreach (Type type in singletons)
{
services.AddSingleton(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in scopeds)
{
services.AddScoped(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in transients)
{
services.AddTransient(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
}
But it seems that there is a problem of context of the assembly.
I tried also the Assembly.LoadFrom() but isn't working, the ReaderLoadContext solution found on a blog
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
namespace Orbit
{
public class ReaderLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public ReaderLoadContext(string readerLocation)
{
_resolver = new AssemblyDependencyResolver(readerLocation);
}
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;
}
}
}
And even to call a "setup" method inside the assembly
private void ConfigureIOC(IServiceCollection services)
{
// for each assembly, load it, add the controller into the mvcBuilder and populate the type list of things to be injected
foreach (string assemblyPath in Directory.GetFiles($"{System.AppDomain.CurrentDomain.BaseDirectory}/Modules", "*.dll", SearchOption.AllDirectories))
{
Assembly assembly = Assembly.LoadFrom(assemblyPath);
Type startup = assembly.GetTypes().SingleOrDefault(t => t.Name == "Startup");
if (startup != null)
{
var setupMethod = startup.GetMethod("Setup");
setupMethod.Invoke(setupMethod, new Object[] { services });
}
}
}
and inside my class library
public static void Setup(IServiceCollection services)
{
List<Type> implementationTypes = new List<Type>();
List<Type> singletons = new List<Type>();
List<Type> scopeds = new List<Type>();
List<Type> transients = new List<Type>();
foreach (string assemblyPath in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "*.dll", SearchOption.AllDirectories))
{
var assembly = Assembly.Load(assemblyPath);
// get all Singleton, Scoped and Transient interfaces
implementationTypes.AddRange(assembly.GetTypes().Where(type => type.IsClass && (type.GetInterface("ISingletonServices") != null || type.GetInterface("IScopedServices") != null || type.GetInterface("ITransientServices") != null)));
singletons.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ISingletonServices") != null));
scopeds.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("IScopedServices") != null));
transients.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ITransientServices") != null));
}
// Register into the service collection
foreach (Type type in singletons)
{
services.AddSingleton(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in scopeds)
{
services.AddScoped(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in transients)
{
services.AddTransient(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
}
I also tried the HostingStartup attribute
[assembly: HostingStartup(typeof(SpaceRadar.ServiceKeyInjection))]
namespace SpaceRadar
{
public class ServiceKeyInjection : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
List<Type> implementationTypes = new List<Type>();
List<Type> singletons = new List<Type>();
List<Type> scopeds = new List<Type>();
List<Type> transients = new List<Type>();
foreach (string assemblyPath in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "*.dll", SearchOption.AllDirectories))
{
var assembly = Assembly.Load(assemblyPath);
// get all Singleton, Scoped and Transient interfaces
implementationTypes.AddRange(assembly.GetTypes().Where(type => type.IsClass && (type.GetInterface("ISingletonServices") != null || type.GetInterface("IScopedServices") != null || type.GetInterface("ITransientServices") != null)));
singletons.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ISingletonServices") != null));
scopeds.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("IScopedServices") != null));
transients.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ITransientServices") != null));
}
// Register into the service collection
foreach (Type type in singletons)
{
services.AddSingleton(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in scopeds)
{
services.AddScoped(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
foreach (Type type in transients)
{
services.AddTransient(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
}
});
}
}
}
All this solutions doesn't allow me inside my class library to have this kind of controller:
public class RadarControllers : BaseControllers
{
private readonly IRadarServices _radarServices;
public RadarControllers(IRadarServices radarServices)
{
_radarServices = radarServices;
}
[HttpGet("radars")]
public async Task<IActionResult> GetRadars(CancellationToken cancellationToken)
{
IList<string> data = await _radarServices.GetRadars(cancellationToken);
return Ok(data);
}
}
How to proceed for this dependency injection ?
Thanks.
I finally found a way to deals with that.
The issue is that on each projet, the assembly name is the same (for instance : "Services").
.Net core does not like this and it seems that if I try to load through AssemblyLoadContext.Default.LoadFromAssemblyPath, it keep loading the same assembly no matter the path to indicate.
On the projet, I just right click > Properties and change the assembly name to an another. Thanks to that, AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath) works.
For instance, if I have project A and B, I rename the assembly names services with
A.Services
and B.Services
so The dll is now A.Services.dll instead of Services.dll
Let's say I have the following:
public interface IMyInterface<T>
{
}
public interface IMyClass<T>
{
}
public class MyClass<T, T2> : IMyClass<T2>
where T : IMyInterface<T2>
{
}
foreach(/*var impl in classes_that_implement_IInterface<T>*/)
{
//register IMyClass<T> as MyClass<typeof(impl), T>
}
e.g. If I defined
public class MyImplementation : IMyInterface<string>
{
}
the following will be registered:
IMyClass<string> as MyClass<MyImplementation, string>
Is there a neat way to do this? I can't think how to approach this :/
Ok I did this.. but it seems rather... abusive?
var container = new WindsorContainer();
container.Register(
Classes.FromThisAssembly()
.BasedOn(typeof(IMyInterface<>))
.Configure(x =>
{
var iMyInterfaceImpl = x.Implementation;
var iMyInterface = iMyInterfaceImpl
.GetTypeInfo()
.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMyInterface<>))
.Single();
var iMyInterfaceGenericArg0 = iMyInterface.GetGenericArguments()[0];
var myClassImpl = typeof(MyClass<,>).MakeGenericType(iMyInterfaceImpl, iMyInterfaceGenericArg0);
var iMyClass = typeof(IMyClass<>).MakeGenericType(iMyInterfaceGenericArg0);
container.Register(Component.For(iMyClass).ImplementedBy(myClassImpl));
})
);
container.Resolve(typeof(IMyClass<string>)); // MyClass<MyImplementation, string>
I have a generic base class and I eventually would have many derived classes.
I am trying to write a function which would return all these sub classes but everything I have tried so far has not worked.
public abstract class Index<T>
where T : class
{
public abstract string IndexName { get; }
public abstract string TypeName { get; }
public abstract Expression<Func<T, IConvertible>> IdFieldSelector { get; }
public string MakeSearchId(T item)
{
return ToSearchId(IdFieldSelector.Compile().Invoke(item));
}
public string MakeSearchId(IConvertible idValue)
{
return ToSearchId(idValue);
}
private static string ToSearchId(IConvertible idValue)
{
return idValue.ToString(CultureInfo.InvariantCulture);
}
}
sample subclass:
public class SurveyChangedIndex : Index<SurveyChanged>
{
public override string IndexName => "reviews";
public override string TypeName => "review";
public override Expression<Func<SurveyChanged, IConvertible>> IdFieldSelector => sc => sc.SurveyResponseId;
}
sample function:
var indexBase = typeof(Index<>);
var indexes = Assembly.GetAssembly(indexBase)
.GetTypes()
.Where(type =>
type != indexBase &&
!type.IsInterface &&
!type.IsAbstract &&
type.BaseType == indexBase)
.ToList();
You can do the following (C# 7 syntax follows):
public static IEnumerable<Type> GetAllDescendantsOf(
this Assembly assembly,
Type genericTypeDefinition)
{
IEnumerable<Type> GetAllAscendants(Type t)
{
var current = t;
while (current.BaseType != typeof(object))
{
yield return current.BaseType;
current = current.BaseType;
}
}
if (assembly == null)
throw new ArgumentNullException(nameof(assembly));
if (genericTypeDefinition == null)
throw new ArgumentNullException(nameof(genericTypeDefinition));
if (!genericTypeDefinition.IsGenericTypeDefinition)
throw new ArgumentException(
"Specified type is not a valid generic type definition.",
nameof(genericTypeDefinition));
return assembly.GetTypes()
.Where(t => GetAllAscendants(t).Any(d =>
d.IsGenericType &&
d.GetGenericTypeDefinition()
.Equals(genericTypeDefinition)));
}
This will return any type that inherits directly or indirectly from the specified generic type definition.
In the following scenario:
class Base { }
class Base<T>: Base { }
class Foo : Base<int> { }
class Bar : Base<string> { }
class Frob : Bar { }
class FooBar: Base { };
var genericTypeDefinition = typeof(Base<>);
var types = Assembly.GetExecutingAssembly()
.GetAllDescendantsOf(genericTypeDefinition)));
GetAllDescendantsOf will output Foo, Bar and Frob.
This should solve your code:
var indexes = Assembly.GetAssembly(indexBase)
.GetTypes()
.Where(type =>
type != indexBase &&
!type.IsInterface &&
!type.IsAbstract &&
type.BaseType.IsAssignableFrom(indexBase))
.ToList();
This worked for me:
var indexes = Assembly.GetAssembly(indexBase)
.GetTypes()
.Where(IsIndexType)
.ToArray();
with
private bool IsIndexType(Type type)
{
var indexDefinition = typeof(Index<>).GetGenericTypeDefinition();
return !type.IsAbstract
&& type.IsClass
&& type.BaseType is not null
&& type.BaseType.IsGenericType
&& type.BaseType.GetGenericTypeDefinition() == indexDefinition;
}
With structure map, you can register a convention that lets you not just tweak the type, but also intervene during object creation. How can I do this with Unity.
public class SettingsRegistration : IRegistrationConvention
{
public void Process(Type type, Registry registry)
{
if (!type.IsAbstract && typeof(ISettings).IsAssignableFrom(type))
{
registry.For(type).Use(x =>
{
var svc = x.GetInstance<ISettingService>();
return svc.LoadSetting(type);
});
}
}
}
You can do this with a combination of Unity's Registration by Convention and an InjectionFactory. I see three common options for implementation, though I'm sure there are more...
Option 1
Register all type at once with if conditions inline in a lambda expression. Though this does not scale well if you are registering many types with many custom registrations...
container.RegisterTypes(
AllClasses.FromLoadedAssemblies(),
WithMappings.FromAllInterfaces,
WithName.Default,
WithLifetime.Transient,
type =>
{
// If settings type, load the setting
if (!type.IsAbstract && typeof (ISettings).IsAssignableFrom(type))
{
return new[]
{
new InjectionFactory((c, t, n) =>
{
var svc = (ISettings) c.Resolve(t);
return svc.LoadSetting(t);
})
};
}
// Otherwise, no special consideration is needed
return new InjectionMember[0];
});
Option 2
Register only the ISettings types and supply some nice helper methods. You will need to call container.RegisterTypes multiple times, but it is much more readable...
container.RegisterTypes(
AllClasses.FromLoadedAssemblies().IsSetting(),
WithMappings.FromAllInterfaces,
WithName.Default,
WithLifetime.Transient,
SettingsRegistration.InjectionMembers);
...
public static class SettingsRegistration
{
public static IEnumerable<Type> IsSetting(this IEnumerable<Type> types)
{
return types.Where(type => !type.IsAbstract &&
typeof (ISettings).IsAssignableFrom(type));
}
public static IEnumerable<InjectionMember> InjectionMembers(Type type)
{
return new[] {new InjectionFactory(LoadSetting)};
}
public static ISettings LoadSetting(IUnityContainer container,
Type type,
string name)
{
var svc = (ISettings) container.Resolve(type, name);
return svc.LoadSetting(type);
}
}
Option 3
Or you could create a class derived from RegistrationConvention and let that class make all of the registration decisions...
container.RegisterTypes(new SettingsRegistrationConvention(
AllClasses.FromLoadedAssemblies()));
...
public class SettingsRegistrationConvention : RegistrationConvention
{
private readonly IEnumerable<Type> _scanTypes;
public SettingsRegistrationConvention(IEnumerable<Type> scanTypes)
{
if (scanTypes == null)
throw new ArgumentNullException("scanTypes");
_scanTypes = scanTypes;
}
public override IEnumerable<Type> GetTypes()
{
return _scanTypes.Where(type => !type.IsAbstract &&
typeof (ISettings).IsAssignableFrom(type));
}
public override Func<Type, IEnumerable<Type>> GetFromTypes()
{
return WithMappings.FromAllInterfaces;
}
public override Func<Type, string> GetName()
{
return WithName.Default;
}
public override Func<Type, LifetimeManager> GetLifetimeManager()
{
return WithLifetime.Transient;
}
public override Func<Type, IEnumerable<InjectionMember>> GetInjectionMembers()
{
return type => new[]
{
new InjectionFactory((c, t, n) =>
{
var svc = (ISettings) c.Resolve(t);
return svc.LoadSetting(t);
})
};
}
}