Reputation: 564
I am using a Razor Class Library for making a reusable complex View (which includes its controller and several View Components) that can be used across several ASP.NET Core MVC projects. The problem is that the controller use dependency injection (a custom service called "GatewayProxy" and string localization). What is the correct way to inject services into a controller inside a RCL?
Here is the structure of my RCL:
Here is the exception:
Upvotes: 5
Views: 3482
Reputation: 171
You can inject from load assemblies. my code
ISturtupInitializers.cs
public interface ISturtupInitializers {
void Compose(IServiceCollection serviceCollection);
}
TypeLoader.cs
public static readonly Lazy<HashSet<Assembly>> AllAssemblies = new Lazy<HashSet<Assembly>>(() => {
var bin_folder = Assembly.GetExecutingAssembly().GetFileInfo().Directory.FullName;
var bin_assembly_files = Directory.GetFiles(bin_folder, "*.dll", SearchOption.TopDirectoryOnly).Where(x => !SystemAssemblies.Any(xs => x.IndexOf(xs, StringComparison.CurrentCultureIgnoreCase) >= 0)).ToList();
var assemblies = new HashSet<Assembly>();
foreach (var a in bin_assembly_files)
{
try {
var assName = AssemblyName.GetAssemblyName(a);
var ass = Assembly.Load(assName);
assemblies.Add(ass);
} catch (SecurityException e) {
//ignore
} catch (FileNotFoundException e) {
//ignore
} catch (BadImageFormatException e) {
//ignore
}
catch (Exception e) {
throw;
}
}
return assemblies;
});
public static string[] SystemAssemblies = new[] {
"Microsoft.",
"System.",
"Newtonsoft."
};
public static FileInfo GetFileInfo(this Assembly assembly)
{
var uri = new Uri(assembly.CodeBase);
var path = uri.LocalPath;
return new FileInfo(path);
}
Composer.cs
public class Composer {
private static readonly Lazy<HashSet<Type>> _sturtup_initializers = new Lazy<HashSet<Type>>(() => {
var aseemblies = TypeLoader.AllAssemblies.Value.SelectMany(x=>GetAllTypesInAssembly(x, typeof(ISturtupInitializers)));
var result = new HashSet<Type>();
foreach (var item in aseemblies) {
result.Add(item);
}
return result;
});
public static void Compose(IServiceCollection services) {
var composers = _sturtup_initializers.Value;
foreach (var compose in composers) {
public static void Compose(IServiceCollection services, IConfiguration configuration) {
var composers = _sturtup_initializers.Value;
var provider = services.BuildServiceProvider();
foreach (var compose in composers) {
((IStartupInitializers) ActivatorUtilities.CreateInstance(provider, compose)).Compose(services, configuration);
}
}
}
}
private static IEnumerable<Type> GetAllTypesInAssembly(Assembly assembly, Type implementInterface) {
var types = assembly.GetTypes();
return types.Where(x => x.GetInterfaces().Any(i => i == implementInterface));
}
}
MyStartup.cs
public class ServiceCollectionExtensions : ISturtupInitializers
{
public void Compose(IServiceCollection serviceCollection) {
/** YOUR CODE **/
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services) {
/** YOUR CODE **/
Composer.Compose(services);
}
Upvotes: 1
Reputation: 12410
You mentioned how you fixed this by adding the dependencies to Startup.cs of your main project. But consider that any consumer of this reuseable library may not remember (or know) what dependencies are needed for your library.
Something you can do to solve this is to create an extension off of IServiceCollection
in your Rcl that does the dependency registration.
public static void AddMyRclServices(this IServiceCollection serviceCollection, IConfiguration config)
{
serviceCollection.AddTransient<IRclService1, RclService1>();
serviceCollection.AddScoped<IRclService2, RclService2>();
}
Then in Startup.cs
for your MVC project call the extension
using Rcl.Extensions
public void ConfigureServices(IServiceCollection services)
{
services.AddMyRclServices(config);
}
Upvotes: 9