diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index 4260512c..969f375d 100644 --- a/Kyoo.Authentication/AuthenticationModule.cs +++ b/Kyoo.Authentication/AuthenticationModule.cs @@ -62,7 +62,7 @@ namespace Kyoo.Authentication } /// - public IServiceCollection Configure(IServiceCollection services, ICollection availableTypes) + public void Configure(IServiceCollection services, ICollection availableTypes) { string publicUrl = _configuration.GetValue("public_url"); @@ -148,7 +148,6 @@ namespace Kyoo.Authentication AllowedOrigins = {new Uri(publicUrl).GetLeftPart(UriPartial.Authority)} }; services.AddSingleton(cors); - return services; } /// diff --git a/Kyoo.Common/Controllers/IPlugin.cs b/Kyoo.Common/Controllers/IPlugin.cs index e9776d36..c535992d 100644 --- a/Kyoo.Common/Controllers/IPlugin.cs +++ b/Kyoo.Common/Controllers/IPlugin.cs @@ -4,7 +4,6 @@ using System.Linq; using JetBrains.Annotations; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -using Unity; namespace Kyoo.Controllers { @@ -59,30 +58,14 @@ namespace Kyoo.Controllers /// /// A configure method that will be run on plugin's startup. /// - /// A unity container to register new services. + /// A service container to register new services. /// The list of types that are available for this instance. This can be used /// for conditional type. See - /// or > - void Configure(IUnityContainer container, ICollection availableTypes) {} + /// or > + /// You can't simply check on the service collection because some dependencies might be registered after your plugin. + /// + void Configure(IServiceCollection services, ICollection availableTypes); - /// - /// An optional configure method that will be run on plugin's startup. - /// This method may be used instead or with the - /// method - /// if you use a library that configure itself with a . - /// Every service put in this container will be registered to the unity container after this method. - /// - /// An empty service container to register new services. - /// The list of types that are available for this instance. This can be used - /// for conditional type. See - /// or > - /// You should return the parameter or another container if you want. - /// This container will be added to Kyoo's unity container. - IServiceCollection Configure(IServiceCollection services, ICollection availableTypes) - { - return services; - } - /// /// An optional configuration step to allow a plugin to change asp net configurations. /// WARNING: This is only called on Kyoo's startup so you must restart the app to apply this changes. diff --git a/Kyoo.Common/Controllers/IPluginManager.cs b/Kyoo.Common/Controllers/IPluginManager.cs index 558f86bf..bd4ef513 100644 --- a/Kyoo.Common/Controllers/IPluginManager.cs +++ b/Kyoo.Common/Controllers/IPluginManager.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { @@ -29,11 +31,26 @@ namespace Kyoo.Controllers /// /// All plugins currently loaded. public ICollection GetAllPlugins(); + + /// + /// Load plugins and their dependencies from the plugin directory. + /// + /// + /// An initial plugin list to use. + /// You should not try to put plugins from the plugins directory here as they will get automatically loaded. + /// + public void LoadPlugins(ICollection plugins); + + /// + /// Configure services adding or removing services as the plugins wants. + /// + /// The service collection to populate + public void ConfigureServices(IServiceCollection services); /// - /// Load new plugins from the plugin directory. + /// Configure an asp net application applying plugins policies. /// - /// If a plugin can't be loaded because a dependency can't be resolved. - public void ReloadPlugins(); + /// The asp net application to configure + public void ConfigureAspnet(IApplicationBuilder app); } } \ No newline at end of file diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index 7211beef..349ef6a0 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -24,14 +24,8 @@ + - - - - - - ..\..\..\..\..\..\usr\share\dotnet\shared\Microsoft.AspNetCore.App\5.0.5\Microsoft.Extensions.DependencyInjection.Abstractions.dll - diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs index 5ce34cbe..c1c09165 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -1,6 +1,6 @@ using System; using Kyoo.Controllers; -using Unity; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo { @@ -12,55 +12,55 @@ namespace Kyoo /// /// Register a new task to the container. /// - /// The container + /// The container /// The type of the task /// The initial container. - public static IUnityContainer RegisterTask(this IUnityContainer container) + public static IServiceCollection AddTask(this IServiceCollection services) where T : class, ITask { - container.RegisterType(); - return container; + services.AddSingleton(); + return services; } /// /// Register a new repository to the container. /// - /// The container + /// The container + /// The lifetime of the repository. The default is scoped. /// The type of the repository. /// - /// If your repository implements a special interface, please use + /// If your repository implements a special interface, please use /// /// The initial container. - public static IUnityContainer RegisterRepository(this IUnityContainer container) + public static IServiceCollection AddRepository(this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Scoped) where T : IBaseRepository { Type repository = Utility.GetGenericDefinition(typeof(T), typeof(IRepository<>)); if (repository != null) - { - container.RegisterType(repository, typeof(T)); - container.RegisterType(repository.FriendlyName()); - } - else - container.RegisterType(typeof(T).FriendlyName()); - return container; + services.Add(ServiceDescriptor.Describe(repository, typeof(T), lifetime)); + services.Add(ServiceDescriptor.Describe(typeof(IBaseRepository), typeof(T), lifetime)); + return services; } /// /// Register a new repository with a custom mapping to the container. /// - /// + /// + /// The lifetime of the repository. The default is scoped. /// The custom mapping you have for your repository. /// The type of the repository. /// - /// If your repository does not implements a special interface, please use + /// If your repository does not implements a special interface, please use /// /// The initial container. - public static IUnityContainer RegisterRepository(this IUnityContainer container) + public static IServiceCollection AddRepository(this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Scoped) where T2 : IBaseRepository, T { - container.RegisterType(); - return container.RegisterRepository(); + services.Add(ServiceDescriptor.Describe(typeof(T), typeof(T2), lifetime)); + return services.AddRepository(lifetime); } } } \ No newline at end of file diff --git a/Kyoo.Postgresql/PostgresModule.cs b/Kyoo.Postgresql/PostgresModule.cs index f56dcac4..52453540 100644 --- a/Kyoo.Postgresql/PostgresModule.cs +++ b/Kyoo.Postgresql/PostgresModule.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using Kyoo.Controllers; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Unity; namespace Kyoo.Postgresql { @@ -57,9 +57,9 @@ namespace Kyoo.Postgresql } /// - public void Configure(IUnityContainer container, ICollection availableTypes) + public void Configure(IServiceCollection services, ICollection availableTypes) { - container.RegisterFactory(_ => new PostgresContext( + services.AddScoped(_ => new PostgresContext( _configuration.GetDatabaseConnection("postgres"), _environment.IsDevelopment())); } diff --git a/Kyoo/Controllers/PluginManager.cs b/Kyoo/Controllers/PluginManager.cs index 7c1be980..27a5b4bc 100644 --- a/Kyoo/Controllers/PluginManager.cs +++ b/Kyoo/Controllers/PluginManager.cs @@ -5,11 +5,10 @@ using System.Linq; using System.Reflection; using System.Runtime.Loader; using Kyoo.Models.Exceptions; -using Kyoo.UnityExtensions; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Unity; namespace Kyoo.Controllers { @@ -20,9 +19,9 @@ namespace Kyoo.Controllers public class PluginManager : IPluginManager { /// - /// The unity container. It is given to the Configure method of plugins. + /// The service provider. It allow plugin's activation. /// - private readonly IUnityContainer _container; + private readonly IServiceProvider _provider; /// /// The configuration to get the plugin's directory. /// @@ -40,14 +39,14 @@ namespace Kyoo.Controllers /// /// Create a new instance. /// - /// A unity container to allow plugins to register new entries + /// A service container to allow initialization of plugins /// The configuration instance, to get the plugin's directory path. /// The logger used by this class. - public PluginManager(IUnityContainer container, + public PluginManager(IServiceProvider provider, IConfiguration config, ILogger logger) { - _container = container; + _provider = provider; _config = config; _logger = logger; } @@ -72,7 +71,7 @@ namespace Kyoo.Controllers } /// - public void ReloadPlugins() + public void LoadPlugins(ICollection plugins) { string pluginFolder = _config.GetValue("plugins"); if (!Directory.Exists(pluginFolder)) @@ -80,7 +79,7 @@ namespace Kyoo.Controllers _logger.LogTrace("Loading new plugins..."); string[] pluginsPaths = Directory.GetFiles(pluginFolder, "*.dll", SearchOption.AllDirectories); - ICollection newPlugins = pluginsPaths.SelectMany(path => + plugins = pluginsPaths.SelectMany(path => { path = Path.GetFullPath(path); try @@ -90,7 +89,7 @@ namespace Kyoo.Controllers return assembly.GetTypes() .Where(x => typeof(IPlugin).IsAssignableFrom(x)) .Where(x => _plugins.All(y => y.GetType() != x)) - .Select(x => (IPlugin)_container.Resolve(x)) + .Select(x => (IPlugin)ActivatorUtilities.CreateInstance(_provider, x)) .ToArray(); } catch (Exception ex) @@ -98,32 +97,40 @@ namespace Kyoo.Controllers _logger.LogError(ex, "Could not load the plugin at {Path}", path); return Array.Empty(); } - }).ToList(); - if (!_plugins.Any()) - newPlugins.Add(new CoreModule()); - _plugins.AddRange(newPlugins); + }).Concat(plugins).ToList(); ICollection available = GetProvidedTypes(); - foreach (IPlugin plugin in newPlugins) + _plugins.AddRange(plugins.Where(plugin => { Type missing = plugin.Requires.FirstOrDefault(x => available.All(y => !y.IsAssignableTo(x))); - if (missing != null) - { - Exception error = new MissingDependencyException(plugin.Name, missing.Name); - _logger.LogCritical(error, "A plugin's dependency could not be met"); - } - else - { - plugin.Configure(_container, available); - _container.AddServices(plugin.Configure(new ServiceCollection(), available)); - } - } - + if (missing == null) + return true; + + Exception error = new MissingDependencyException(plugin.Name, missing.Name); + _logger.LogCritical(error, "A plugin's dependency could not be met"); + return false; + })); + if (!_plugins.Any()) _logger.LogInformation("No plugin enabled"); else _logger.LogInformation("Plugin enabled: {Plugins}", _plugins.Select(x => x.Name)); } + + /// + public void ConfigureServices(IServiceCollection services) + { + ICollection available = GetProvidedTypes(); + foreach (IPlugin plugin in _plugins) + plugin.Configure(services, available); + } + + /// + public void ConfigureAspnet(IApplicationBuilder app) + { + foreach (IPlugin plugin in _plugins) + plugin.ConfigureAspNet(app); + } /// /// Get the list of types provided by the currently loaded plugins. diff --git a/Kyoo/Controllers/TaskManager.cs b/Kyoo/Controllers/TaskManager.cs index 437b8b14..7ba56a07 100644 --- a/Kyoo/Controllers/TaskManager.cs +++ b/Kyoo/Controllers/TaskManager.cs @@ -9,7 +9,6 @@ using Kyoo.Models.Exceptions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Unity; namespace Kyoo.Controllers { @@ -22,7 +21,7 @@ namespace Kyoo.Controllers /// /// The service provider used to activate /// - private readonly IUnityContainer _container; + private readonly IServiceProvider _provider; /// /// The configuration instance used to get schedule information /// @@ -54,15 +53,15 @@ namespace Kyoo.Controllers /// Create a new . /// /// The list of tasks to manage - /// The service provider to request services for tasks + /// The service provider to request services for tasks /// The configuration to load schedule information. /// The logger. public TaskManager(IEnumerable tasks, - IUnityContainer container, + IServiceProvider provider, IConfiguration configuration, ILogger logger) { - _container = container; + _provider = provider; _configuration = configuration.GetSection("scheduledTasks"); _logger = logger; _tasks = tasks.Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))).ToList(); @@ -173,7 +172,7 @@ namespace Kyoo.Controllers foreach (PropertyInfo property in properties) { - object value = _container.Resolve(property.PropertyType); + object value = _provider.GetService(property.PropertyType); property.SetValue(obj, value); } } @@ -244,7 +243,7 @@ namespace Kyoo.Controllers /// public void ReloadTasks() { - _tasks = _container.Resolve>().Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))).ToList(); + // _tasks = _provider.Resolve>().Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))).ToList(); EnqueueStartupTasks(); } } diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs index 9f9810cf..8bad71e9 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -2,8 +2,7 @@ using System; using System.Collections.Generic; using Kyoo.Controllers; using Kyoo.Tasks; -using Unity; -using Unity.Lifetime; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo { @@ -52,32 +51,33 @@ namespace Kyoo public ICollection Requires => ArraySegment.Empty; /// - public void Configure(IUnityContainer container, ICollection availableTypes) + public void Configure(IServiceCollection services, ICollection availableTypes) { - container.RegisterType(new SingletonLifetimeManager()); - container.RegisterType(new SingletonLifetimeManager()); - container.RegisterType(new SingletonLifetimeManager()); - container.RegisterType(new SingletonLifetimeManager()); - container.RegisterType(new SingletonLifetimeManager()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddHostedService(x => x.GetService() as TaskManager); - container.RegisterType(new HierarchicalLifetimeManager()); + services.AddScoped(); if (ProviderCondition.Has(typeof(DatabaseContext), availableTypes)) { - container.RegisterRepository(); - container.RegisterRepository(); - container.RegisterRepository(); - container.RegisterRepository(); - container.RegisterRepository(); - container.RegisterRepository(); - container.RegisterRepository(); - container.RegisterRepository(); - container.RegisterRepository(); - container.RegisterRepository(); - container.RegisterRepository(); + services.AddRepository(); + services.AddRepository(); + services.AddRepository(); + services.AddRepository(); + services.AddRepository(); + services.AddRepository(); + services.AddRepository(); + services.AddRepository(); + services.AddRepository(); + services.AddRepository(); + services.AddRepository(); } - container.RegisterTask(); + services.AddTask(); } } } \ No newline at end of file diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 5eaff52e..ca011593 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -47,8 +47,6 @@ - - diff --git a/Kyoo/Models/LazyDi.cs b/Kyoo/Models/LazyDi.cs new file mode 100644 index 00000000..477e1ec4 --- /dev/null +++ b/Kyoo/Models/LazyDi.cs @@ -0,0 +1,12 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Kyoo.Models +{ + public class LazyDi : Lazy + { + public LazyDi(IServiceProvider provider) + : base(provider.GetRequiredService) + { } + } +} \ No newline at end of file diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index 2c73f2db..34ae9206 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -1,16 +1,12 @@ using System; using System.IO; using System.Threading.Tasks; -using Kyoo.UnityExtensions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.StaticWebAssets; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Unity; -using Unity.Microsoft.DependencyInjection; - namespace Kyoo { /// @@ -80,9 +76,6 @@ namespace Kyoo /// A new web host instance private static IWebHostBuilder CreateWebHostBuilder(string[] args) { - UnityContainer container = new(); - container.EnableDebugDiagnostic(); - return new WebHostBuilder() .UseContentRoot(AppDomain.CurrentDomain.BaseDirectory) .UseConfiguration(SetupConfig(new ConfigurationBuilder(), args).Build()) @@ -100,8 +93,6 @@ namespace Kyoo if (context.HostingEnvironment.IsDevelopment()) StaticWebAssetsLoader.UseStaticWebAssets(context.HostingEnvironment, context.Configuration); }) - // .UseUnityProvider(container) - .UseUnityServiceProvider(container) .ConfigureServices(x => x.AddRouting()) .UseKestrel(options => { options.AddServerHeader = false; }) .UseIIS() diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index d0fac3d6..730d4b37 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Kyoo.Controllers; +using Kyoo.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.SpaServices.AngularCli; @@ -10,8 +11,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Unity; -using Unity.Lifetime; namespace Kyoo { @@ -20,16 +19,38 @@ namespace Kyoo /// public class Startup { + /// + /// The configuration context + /// private readonly IConfiguration _configuration; - private readonly ILoggerFactory _loggerFactory; + /// + /// A plugin manager used to load plugins and allow them to configure services / asp net. + /// + private readonly IPluginManager _plugins; - public Startup(IConfiguration configuration, ILoggerFactory loggerFactory, IServiceProvider provider) + /// + /// Created from the DI container, those services are needed to load information and instantiate plugins.s + /// + /// + /// The ServiceProvider used to create this instance. + /// The host provider that contains only well-known services that are Kyoo independent. + /// This is used to instantiate plugins that might need a logger, a configuration or an host environment. + /// + /// The configuration context + /// A logger factory used to create a logger for the plugin manager. + public Startup(IServiceProvider hostProvider, IConfiguration configuration, ILoggerFactory loggerFactory) { _configuration = configuration; - _loggerFactory = loggerFactory; + _plugins = new PluginManager(hostProvider, _configuration, loggerFactory.CreateLogger()); + + _plugins.LoadPlugins(new [] {new CoreModule()}); } + /// + /// Configure the WebApp services context. + /// + /// The service collection to fill. public void ConfigureServices(IServiceCollection services) { string publicUrl = _configuration.GetValue("public_url"); @@ -63,23 +84,18 @@ namespace Kyoo // }); // }); // services.AddAuthentication() - - // container.Resolve(); - - services.AddSingleton(); - services.AddHostedService(x => x.GetService() as TaskManager); - } - - public void ConfigureContainer(IUnityContainer container) - { - container.RegisterType(new SingletonLifetimeManager()); - container.RegisterInstance(_configuration); - PluginManager pluginManager = new(container, _configuration, _loggerFactory.CreateLogger()); - pluginManager.ReloadPlugins(); + services.AddSingleton(_plugins); + services.AddTransient(typeof(Lazy<>), typeof(LazyDi<>)); + _plugins.ConfigureServices(services); } - public void Configure(IUnityContainer container, IApplicationBuilder app, IWebHostEnvironment env) + /// + /// Configure the asp net host. + /// + /// The asp net host to configure + /// The host environment (is the app in development mode?) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); @@ -124,9 +140,7 @@ namespace Kyoo spa.UseAngularCliServer("start"); }); - IPluginManager pluginManager = container.Resolve(); - foreach (IPlugin plugin in pluginManager.GetAllPlugins()) - plugin.ConfigureAspNet(app); + _plugins.ConfigureAspnet(app); app.UseEndpoints(endpoints => { diff --git a/Kyoo/UnityExtensions/UnityExtensions.cs b/Kyoo/UnityExtensions/UnityExtensions.cs deleted file mode 100644 index b351ec8e..00000000 --- a/Kyoo/UnityExtensions/UnityExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Reflection; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Unity; -using Unity.Microsoft.DependencyInjection; - -namespace Kyoo.UnityExtensions -{ - public static class UnityExtensions - { - public static IWebHostBuilder UseUnityProvider(this IWebHostBuilder host, UnityContainer container) - { - UnityProvider factory = new(container); - - return host.ConfigureServices((_, services) => - { - services.Replace(ServiceDescriptor.Singleton>(factory)); - services.Replace(ServiceDescriptor.Singleton>(factory)); - services.Replace(ServiceDescriptor.Singleton>(factory)); - }); - } - - public static IUnityContainer AddServices(this IUnityContainer container, IServiceCollection services) - { - return (IUnityContainer)typeof(ServiceProviderExtensions).Assembly - .GetType("Unity.Microsoft.DependencyInjection.Configuration") - !.GetMethod("AddServices", BindingFlags.Static | BindingFlags.NonPublic) - !.Invoke(null, new object[] {container, services}); - } - } -} \ No newline at end of file diff --git a/Kyoo/UnityExtensions/UnityProvider.cs b/Kyoo/UnityExtensions/UnityProvider.cs deleted file mode 100644 index 93d460f0..00000000 --- a/Kyoo/UnityExtensions/UnityProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Unity; -using Unity.Microsoft.DependencyInjection; - -namespace Kyoo.UnityExtensions -{ - public class UnityProvider : ServiceProviderFactory, IServiceProviderFactory - { - private readonly UnityContainer _container; - - - public UnityProvider(UnityContainer container) - : base(container) - { - _container = container; - } - - public UnityContainer CreateBuilder(IServiceCollection services) - { - _container.AddServices(services); - return _container; - } - - public IServiceProvider CreateServiceProvider(UnityContainer containerBuilder) - { - return CreateServiceProvider(containerBuilder as IUnityContainer); - } - } -} \ No newline at end of file