A few months ago I built a very simply still effective plugin system with autofac modules in ASP.Net Core 1.1 like this
In startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var builder = new ContainerBuilder();
builder.RegisterModule<AutofacModule>();
builder.Populate(services);
var ApplicationContainer = builder.Build();
return new AutofacServiceProvider(ApplicationContainer);
}
then in AutofacModule.cs
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<AuthMessageSender>().As<IEmailSender>();
builder.RegisterType<AuthMessageSender>().As<ISmsSender>();
builder.RegisterType<HttpClient>().As<HttpClient>();
foreach (var assembly in LoadAllAssemblies())
{
builder.RegisterAssemblyModules(assembly);
}
}
private IEnumerable<Assembly> LoadAllAssemblies()
{
string assemblyPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "");
var allAssemblies = new List<Assembly>();
foreach (string dll in Directory.GetFiles(assemblyPath, "*.dll"))
{
try
{
var assembly = Assembly.LoadFile(dll);
if (!assembly.GetTypes().Any(type => type.IsSubclassOf(typeof(Autofac.Module))))
{
continue;
}
allAssemblies.Add(assembly);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
continue;
}
}
return allAssemblies;
}
then I could load modules from assemblies like this:
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<UserService>().As<IUserService>();
builder.RegisterType<DashboardService>().As<IDashboardService>();
builder.RegisterType<DetailPageService>().As<IDetailPanelService>();
builder.RegisterType<PlanningService>().As<IPlanningService>();
builder.RegisterType<PlanningRepository>().As<IPlanningRepository>();
}
}
I had to copy assemblies to the running web app bin directory to work, or reference all assemblies in the main web project. All the assemblies were .Net Framework Class Libraries.
After a while I updated the whole solution to Asp.Net Core 2.0, and Autofac module containing assemblies changed to .Net Core Class Libraries. Now, autofac modules are still found, and registered via RegisterAssemblyModules, but load method does not called anymore. I realized, if I create another .Net Framework Library project with autofac module, and reference it, it is still loading as expected, and load method called. So it seems to me this problem is related to .Net Core Libraries.
Anybody can explain me this, knows any solution, workaround, or can recommend another architecture for plugin system with autofac?
Related
I realize this question is asked a lot. But I cannot figure out the solution to this problem.
The thing i am trying to resolve is create three seperate interfaces (Singleton Scoped, InstancePerRequest).
And register all the services under their implemented interface, without having to manually add them to DI container.
Autofac Scanning Assemblies for certain class type
Autofac register assembly types
Autofac assembly scanning - .NET Core
Before you say duplicate hear me out.
I have provided a solution to the question asked. But i would like to search the whole solution and not be restricted to a project. I have addded my autofac module inside the Services project so it registers what exists inside services projects. Please see answer below for a better understanding.
I have tried to implement a multi project scanning see code below. It does not work.
Here is my Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Here is my Configure container in my Startup Class. I have left out all others since this is just a new ASP.NET Core Project.
public void ConfigureContainer(ContainerBuilder builder)
{
//builder.RegisterType<EpisodeServices>().As<IEpisodeService>().InstancePerLifetimeScope(); This works
var executingDirectory = AppDomain.CurrentDomain.BaseDirectory;
//The sth is my Solution's header for example Sth.Core, Sth.Models, Sth.Services all are childs to the Sth Solution
string[] files = Directory.GetFiles(executingDirectory, "Sth.*.dll", SearchOption.AllDirectories);
var listOfAssemblies = new List<Assembly>();
foreach (var file in files)
listOfAssemblies.Add(Assembly.LoadFile(file));
builder
.RegisterAssemblyTypes(listOfAssemblies.ToArray())
.Where(t => t.GetInterfaces().Any(i => i.IsAssignableFrom(typeof(ISthScopedService))))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
So far so good.
Here is an example Service so you can fully see my implementation.
public class EpisodeServices : IEpisodeService
{
public IList<Episode> GetEpisodes()
{
return new List<Episode>
{
new Episode { Id = 1, Name = "Some Name", Description = "Some Description" }
};
}
}
And here is the interface:
public interface IEpisodeService : ISthScopedService
{
IList<Episode> GetEpisodes();
}
Here is the injection to the controller
public class EpisodeController : Controller
{
private readonly IEpisodeService _episodeService;
public EpisodeController(IEpisodeService episodeService)
{
_episodeService = episodeService;
}
public IActionResult Index()
{
var data = _episodeService.GetEpisodes();
return Content(data[0].Name);
}
}
If i run this like this i get an Invalid Operation Exception: Unable to resolve service for type namespace.IEpisodeService while attempting to activate EpisodeController.
Could someone provide more implementation details on how to achieve this?
The solution provided does not resolve solution scanning. But it uses assembly scanning. You may as well view this as a step-by-step guide on how to achieve this. Our Solution Looks sth like this.
Sth.Core --> Class Library.
Sth.Services --> Class Library(Assembly scanning will happen here).
Sth.Web --> ASP.NET Core MVC project.
The Sth.Core has our three-lifetime interfaces which our Services need to inherit from.
- ISthInstanceService
- ISthScopedService
- ISthSingletonService
Sth.Web.Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory()) //Add this
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Sth.Web,Startup.cs
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new SthModule()); // This is the autofac Module. We add it later at the Services Project
}
Now for our Services Project.Here we need to create our autofac module. The one asp.net app instantiated.
public class SthModule : Module
{
protected override void Load(ContainerBuilder builder)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
builder
.RegisterAssemblyTypes(assemblies)
.Where(t => t.GetInterfaces().Any(i => i.IsAssignableFrom(typeof(ISthScopedService))))
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); // Add similar for the other two lifetimes
base.Load(builder);
}
}
The above code will scan all the Services project and add all the classes that inherit from the ISthScopedService and register them to the container.
Sth.Service.EpisodeService.cs
public class EpisodeServices : IEpisodeService
{
public IList<Episode> GetEpisodes()
{
return new List<Episode>
{
new Episode { Id = 1, Name = "Imposter Syndrome", Description = "Imposter syndrome" }
};
}
}
And the interface.
public interface IEpisodeService : ISthCommonService
{
IList<Episode> GetEpisodes();
}
Now we have implemented assembly scanning(auto-registration) for our services project.
According to this tutorial I should:
Register your context with dependency injection
The Tutorial describes that I should locate the method
ConfigureServices() and put in there the code advised.
Here's my startup.cs:
using Microsoft.Owin;
using Owin;
[assembly: OwinStartupAttribute(typeof(MyProject.Startup))]
namespace MyProject
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
so I don't know where to correctly put the code.
Because the project isn't compatible with .net core 2.1, it was needed to change Project->Property to .Net Framework 4.6.1
and install packages Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.Tools
I tried to add the dependency injection to global.asax.cs file as follows:
protected void Application_Start()
{
var services = new ServiceCollection();
ConfigureServices(services);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
private void ConfigureServices(IServiceCollection services)
{
var connection = #"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;ConnectRetryCount=0";
services.AddDbContext<BloggingContext>(options => options.UseSqlServer(connection));
}
I succeeded with the step and created the controller and it works, but I haven't chosen the right context (BloggingContext), so it created second database. So, I need to create a controller with BloggingContext, do you know how?
The version of the shown startup and the tutorial are in conflict with each other.
If this if for an Asp.Net Core MVC application then you can add the method your self. Startup class is part of the convention.
public partial class Startup {
//...
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//...
}
}
Reference App startup in ASP.NET Core
The ConfigureServices method
The ConfigureServices method is:
Optional.
Called by the host before the Configure method to
configure the app's services.
Where configuration options are set by convention.
If however you are trying to use .Net Core technologies in an Asp.Net MVC 5+ (which is what the GitHub project targets) then you will need to modify your approach to adapt to using .Net Core Dependency Injection with a non core platform.
First you will need a IDependencyResolver which is the DI framework used by that version of Asp.Net MVC, and a way to replace the default resolver with your own.
public sealed class CoreDependencyResolver : System.Web.Mvc.IDependencyResolver {
private readonly System.Web.Mvc.IDependencyResolver mvcInnerResolver;
private readonly IServiceProvider serviceProvider;
public CoreDependencyResolver(IServiceProvider serviceProvider, System.Web.Mvc.IDependencyResolver dependencyResolver) {
this.serviceProvider = serviceProvider;
mvcInnerResolver = dependencyResolver;
}
public object GetService(Type serviceType) {
object result = this.serviceProvider.GetService(serviceType);
if (result == null && mvcInnerResolver != null)
result = mvcInnerResolver.GetService(serviceType);
return result;
}
public IEnumerable<object> GetServices(Type serviceType) {
IEnumerable<object> result = this.serviceProvider.GetServices(serviceType);
if (result == null && mvcInnerResolver != null)
result = mvcInnerResolver.GetServices(serviceType);
return result ?? new object[0];
}
}
With the custom resolver in place, you can now configure the application to use it.
Using your current example as a starting point (review comments)
protected void Application_Start() {
var services = new ServiceCollection();
ConfigureServices(services);
//build service provider
IServiceProvider provider = services.BuildServiceProvider();
//Get the current resolver used by MVC
var current = DependencyResolver.Current;
//use that and the provider to create your custom resolver
var resolver = new CoreDependencyResolver(provider, current);
//now set the MVC framework to use the resolver that wraps the service provider
//that was created from .Net Core Dependency Injection framework.
DependencyResolver.SetResolver(resolver);
//...
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
private void ConfigureServices(IServiceCollection services) {
//... omitted for brevity (register dependencies as normal)
}
Here I am using Oracle, but you could do the same with SQL Server...
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFrameworkOracle()
.AddDbContext<OracleDbContext>(builder => builder.UseOracle(Configuration["Data:OracleDbContext"]),ServiceLifetime.Scoped)
.AddDbContext<AppsDbContext>(option => option.UseOracle(Configuration["Data:AppsDbConnection:ConnectionString"]), ServiceLifetime.Scoped);
Then in my appsettings.json, I include the connection strings...
"Data": {
"OracleDbContext": "your connection string" },
"AppsDbContext": "your connection string" }
}
It appears you're using .Net Framework, rather than .Net Core.
2 simple ideas here:
Injecting DbContext into service layer: this layer will be class Library for .Net Framework or (use .Net Standard Class library if your platform is .Net Core). This thread shows you how to perform it: Injecting DbContext into service layer
use Ninject as dependency injector if you're on .Net Framework platform. This thread shows a good example: How to handle DBContext when using Ninject
Whilst using .NET Core tooling with full framework works quite well, if you have to use MVC5 and full framework I would not try to work it that way round.
There are many .NET 4.6.1 dependency injection frameworks, in this example I will use Autofac.
Install the NuGet packages Autofac and Autofac.Mvc5.
Add an AutofacRegistration.cs class to the App_Start folder
In the Application_Start() method in Global.asax add the line AutofacRegistration.BuildContainer();
Your AutofacRegistration class is where you wire up all your dependencies for dependency injection. The full docs are here https://autofaccn.readthedocs.io/en/latest/integration/mvc.html
public class AutofacRegistration
{
public static void BuildContainer()
{
var builder = new ContainerBuilder();
// Register your MVC controllers
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// Now grab your connection string and wire up your db context
var conn = ConfigurationManager.ConnectionStrings["BloggingContext"];
builder.Register(c => new BloggingContext(conn));
// You can register any other dependencies here
// Set the dependency resolver to be Autofac.
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
This is assuming your BloggingContext has a constructor that takes the connection string as a parameter and passes it to the base class. Something like
public class BloggingContext : DbContext
{
public BloggingContext(string connectionString) : base(connectionString)
{
}
}
There is loads more in the documentation about scope etc that is worth reading but this should be the nuts and bolts of it.
I've got a .NET Core Console App that shares some common libraries with my ASP.NET Core App. I need to add Globalization to the common project and in ASP.NET I can easily create a new .resx file with the language some.en.resx
At the moment, I can register my service, and it is calling the StringLocalizer class but not returning the localised string. I managed to get it working by removing the .en from the file, but then that breaks my ASP.NET project.
Its like I need to register a default Language somewhere, but I can't work out where.
class Program
{
static void Main(string[] args)
{
ServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<MyService>();
serviceCollection.AddSingleton<LoggerFactory>();
serviceCollection
.AddLogging()
.AddLocalization();
ServiceProvider buildServiceProvider = serviceCollection.BuildServiceProvider();
MyService myService = buildServiceProvider.GetService<MyService>();
string greeting = myService.GetGreeting();
Console.WriteLine(greeting);
Console.ReadKey();
}
}
public class MyService
{
private readonly IStringLocalizer<MyService> _stringLocalizer;
public MyService(IStringLocalizer<MyService> stringLocalizer)
{
_stringLocalizer = stringLocalizer;
}
public string GetGreeting()
{
return _stringLocalizer.GetString("Greeting");
}
}
Is there any way I can do this?
I haven't managed to find a way of repeating the behaviour of ASP.NET. I've had to accept that I can't have .en.resx files and luckily with a Culture of "en" ASP.NET uses the default .resx files
I'm building a web application, where I would like separate concerns, i.e. having abstractions and implementations in different projects.
To achieve this, I've tried to implement a composition root concept, where all implementation must have an instance of ICompositionRootComposer to register services, types etc.
public interface ICompositionRootComposer
{
void Compose(ICompositionConfigurator configurator);
}
In projects that are referred directly in the build hierarchy, implementations of ICompositionRootComposer are called, and services are registered correct in the underlying IoC container.
The problem arises when I'm trying to register services in a project, where I've set up a post build task that copies the built dll to the web project's debug folder:
cp -R $(TargetDir)"assembly and symbol name"* $(SolutionDir)src/"webproject path"/bin/Debug/netcoreapp1.1
I'm loading the assembly with: (Inspiration: How to load assemblies located in a folder in .net core console app)
internal class AssemblyLoader : AssemblyLoadContext
{
private string folderPath;
internal AssemblyLoader(string folderPath)
{
this.folderPath = Path.GetDirectoryName(folderPath);
}
internal Assembly Load(string filePath)
{
FileInfo fileInfo = new FileInfo(filePath);
AssemblyName assemblyName = new AssemblyName(fileInfo.Name.Replace(fileInfo.Extension, string.Empty));
return this.Load(assemblyName);
}
protected override Assembly Load(AssemblyName assemblyName)
{
var dependencyContext = DependencyContext.Default;
var ressource = dependencyContext.CompileLibraries.FirstOrDefault(r => r.Name.Contains(assemblyName.Name));
if(ressource != null)
{
return Assembly.Load(new AssemblyName(ressource.Name));
}
var fileInfo = this.LoadFileInfo(assemblyName.Name);
if(File.Exists(fileInfo.FullName))
{
Assembly assembly = null;
if(this.TryGetAssemblyFromAssemblyName(assemblyName, out assembly))
{
return assembly;
}
return this.LoadFromAssemblyPath(fileInfo.FullName);
}
return Assembly.Load(assemblyName);
}
private FileInfo LoadFileInfo(string assemblyName)
{
string fullPath = Path.Combine(this.folderPath, $"{assemblyName}.dll");
return new FileInfo(fullPath);
}
private bool TryGetAssemblyFromAssemblyName(AssemblyName assemblyName, out Assembly assembly)
{
try
{
assembly = Default.LoadFromAssemblyName(assemblyName);
return true;
}
catch
{
assembly = null;
return false;
}
}
}
With this I'm able to load the assembly and call the projects ICompositionRootComposer implementation.
But the problem is that it doesn't seem to recognize any of my types.
When calling my configurator with
configurator.RegisterTransiantService<IFoo, Foo>();
it should register IFoo and Foo in the IoC.
But when debugging I'm not able to get info of the types, i.e via typeof(Foo) in the debug console in Visual Studio Code.
Necromancing.
You can create a wrapper class for the old Assembly.LoadFile to do that.
This has the added benefit that you can stay backward-compatible with dotnet-none-core by applying search-and-replace changes in old code-bases.
namespace System.Reflection
{
public class Assembly2
{
public static System.Reflection.Assembly LoadFile(string path)
{
System.Reflection.Assembly assembly = null;
#if NET_CORE
// Requires nuget - System.Runtime.Loader
assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
#else
assembly = System.Reflection. Assembly.LoadFile(path);
#endif
return assembly;
}
}
}
You'll need to add System.Runtime.Loader via NuGet.
I found a solution to my problem.
It turned out, that I had the property PreserveCompilationContext set to true, and that's why the debugger wouldn't register my manually copied assembly.
When I removed the property from the web project csproj file, everything worked.
Are you aware that ASP.NET Core has it's own, built-in Dependency Injection mechanism? It can be easily switched to other IoC container for your needs, I don't think that you need to reinvent it.
What you need to do here is use a reflection to make a generic method and call after that, something like this:
public void ConfigureServices(IServiceCollection services)
{
var myAssembly = LoadAssembly();
// find first interface
var firstInterfaceType = myAssembly.DefinedTypes.FirstOrDefault(t => t.IsInterface).GetType();
// find it's implementation
var firstInterfaceImplementationType = myAssembly.DefinedTypes.Where(t => t.ImplementedInterfaces.Contains(firstInterfaceType)).GetType();
// get general method info
MethodInfo method = typeof(IServiceCollection).GetMethod("AddTransient");
// provide types to generic method
MethodInfo generic = method.MakeGenericMethod(firstInterfaceType, firstInterfaceImplementationType);
// register your types
generic.Invoke(services, null);
}
I am very new to AutoFac and am trying to use it for my new project with WebApi and Business Layer with contracts and their respective implementations.
I have written the IocConfiguration for webapi and invoke from global.asax.
However, for my Business Logic how do I setup all my contracts and implementations with autofac?
I did go through some tutorials online but however I could not find anything helpful, If someone has a sample app, links that really helps.
Edit:
AutoMapper profile.
public class CustomProfile : Profile
{
protected override void Configure()
{
CreateMap<MyViewModel, MyModel>()
.ForMember(d => d.Id, s => s.MapFrom(src => src.Id));
}
}
Edit:
After few long hours spent on this I figured out how to setup AutoMapper 4.2.1 with AutoFac. Apparently I was using ConfigurationStore in AutoMapper 3.3.0 but I upgraded to 4.2.1 the profile registration changed a little bit. Below is what worked for me.
public class AutoMapperModule : Module
{
protected override void Load(ContainerBuilder builder)
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<MyProfile1>();
cfg.AddProfile<MyProfile2>();
});
base.Load(builder);
}
}
If you use constructor injection (and it`s really a good idea).
First you need is add to add reference to Autofac.WebApi2 assembly via Nuget. Lets think that your controllers are in the different Assembly that the host (Service.dll or somethink like this) then you
Services
Project with all our controllers:
public class DependenyInitializer
{
public static readonly DependenyInitializer Instance = new DependenyInitializer();
private DependenyInitializer()
{
var builder = new ContainerBuilder();
builder.RegisterModule<BusinessLayerModule>(); // register all dependencies that has been set up in that module
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
this.Container = builder.Build();
}
public IContainer Container { get; }
}
Buisness Layer
you`ll have to create a module
using System.Reflection;
using Autofac;
using DataAccessLayer;
using Module = Autofac.Module;
public class BusinessLayerModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces(); // that links all clases with the implemented interfaces (it they mapped 1:1 to each other)
}
Hosting (WebApiConfig.cs in Register(HttpConfiguration config))
var container = DependenyInitializer.Instance.Container;
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
Main compexity here is knowing that you need Autofac.WebApi2 and it`s RegisterApiControllers. Try that.