From 7fe36a7e9222e830f4920e09a85115df0bdbf190 Mon Sep 17 00:00:00 2001 From: ta264 Date: Wed, 4 Aug 2021 21:42:41 +0100 Subject: [PATCH] New: Use native dotnet host and DryIoc (cherry picked from commit d6170dbfedf27a6218afe242a0fae2eb8b368aec) --- .../src/Settings/General/GeneralSettings.js | 5 +- .../src/Settings/General/SecuritySettings.js | 3 - .../Commands/CommandController.cs | 9 +- .../ImportLists/ImportListController.cs | 6 +- .../ApiKeyAuthenticationHandler.cs | 10 +- .../AuthenticationBuilderExtensions.cs | 55 +- .../AuthenticationController.cs | 7 +- .../BasicAuthenticationHandler.cs | 3 +- .../Authentication/NoAuthenticationHandler.cs | 6 +- .../Frontend/Mappers/HtmlMapperBase.cs | 6 +- .../Frontend/Mappers/IndexHtmlMapper.cs | 2 +- .../Frontend/Mappers/LoginHtmlMapper.cs | 2 +- .../Middleware/BufferingMiddleware.cs | 30 + .../SentryTargetFixture.cs | 3 +- .../ServiceFactoryFixture.cs | 22 +- .../Composition/AssemblyLoader.cs | 71 + src/NzbDrone.Common/Composition/Container.cs | 113 - .../Composition/ContainerBuilderBase.cs | 119 - src/NzbDrone.Common/Composition/Extensions.cs | 37 + src/NzbDrone.Common/Composition/IContainer.cs | 28 - src/NzbDrone.Common/Composition/KnownTypes.cs | 31 + .../Extensions/CompositionExtensions.cs | 14 + src/NzbDrone.Common/Lidarr.Common.csproj | 2 + src/NzbDrone.Common/ServiceFactory.cs | 16 +- src/NzbDrone.Common/TinyIoC.cs | 3771 ----------------- src/NzbDrone.Console/ConsoleAlerts.cs | 15 - src/NzbDrone.Console/ConsoleApp.cs | 6 +- .../Framework/MigrationTest.cs | 3 +- src/NzbDrone.Core/Datastore/DbFactory.cs | 12 - .../Extensions/CompositionExtensions.cs | 24 + .../Framework/MigrationController.cs | 5 +- .../Migration/Framework/MigrationLogger.cs | 59 - .../Framework/MigrationLoggerProvider.cs | 34 - .../Download/DownloadClientFactory.cs | 4 +- .../Extras/ExistingExtraFileService.cs | 2 +- src/NzbDrone.Core/Extras/ExtraService.cs | 2 +- .../Extras/Metadata/MetadataFactory.cs | 4 +- .../ImportLists/ImportListFactory.cs | 3 +- src/NzbDrone.Core/Indexers/IndexerFactory.cs | 3 +- src/NzbDrone.Core/Lidarr.Core.csproj | 4 + .../Messaging/Commands/CommandQueueManager.cs | 11 +- .../Notifications/NotificationFactory.cs | 3 +- src/NzbDrone.Core/Tags/TagService.cs | 2 +- .../ThingiProvider/ProviderFactory.cs | 7 +- src/NzbDrone.Host.Test/ContainerFixture.cs | 46 +- src/NzbDrone.Host.Test/RouterTest.cs | 30 +- .../AccessControl/RemoteAccessAdapter.cs | 5 + src/NzbDrone.Host/AppLifetime.cs | 119 + src/NzbDrone.Host/ApplicationServer.cs | 134 - src/NzbDrone.Host/Bootstrap.cs | 214 +- src/NzbDrone.Host/IHostController.cs | 8 - src/NzbDrone.Host/IRemoteAccessAdapter.cs | 7 - src/NzbDrone.Host/IUserAlert.cs | 7 - src/NzbDrone.Host/Lidarr.Host.csproj | 5 +- src/NzbDrone.Host/MainAppContainerBuilder.cs | 39 - src/NzbDrone.Host/PlatformValidation.cs | 56 - src/NzbDrone.Host/SpinService.cs | 60 - src/NzbDrone.Host/Startup.cs | 201 + .../{Router.cs => UtilityModeRouter.cs} | 29 +- .../WebHost/ControllerActivator.cs | 26 - .../WebHost/WebHostController.cs | 272 -- src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs | 2 +- src/NzbDrone.Update/Lidarr.Update.csproj | 2 + src/NzbDrone.Update/UpdateApp.cs | 19 +- src/NzbDrone.Update/UpdateContainerBuilder.cs | 24 - src/NzbDrone/Lidarr.csproj | 1 + src/NzbDrone/MessageBoxUserAlert.cs | 13 - src/NzbDrone/SysTray/SysTrayApp.cs | 32 +- src/NzbDrone/WindowsApp.cs | 10 +- 69 files changed, 859 insertions(+), 5076 deletions(-) create mode 100644 src/Lidarr.Http/Middleware/BufferingMiddleware.cs create mode 100644 src/NzbDrone.Common/Composition/AssemblyLoader.cs delete mode 100644 src/NzbDrone.Common/Composition/Container.cs delete mode 100644 src/NzbDrone.Common/Composition/ContainerBuilderBase.cs create mode 100644 src/NzbDrone.Common/Composition/Extensions.cs delete mode 100644 src/NzbDrone.Common/Composition/IContainer.cs create mode 100644 src/NzbDrone.Common/Composition/KnownTypes.cs create mode 100644 src/NzbDrone.Common/Instrumentation/Extensions/CompositionExtensions.cs delete mode 100644 src/NzbDrone.Common/TinyIoC.cs delete mode 100644 src/NzbDrone.Console/ConsoleAlerts.cs create mode 100644 src/NzbDrone.Core/Datastore/Extensions/CompositionExtensions.cs delete mode 100644 src/NzbDrone.Core/Datastore/Migration/Framework/MigrationLogger.cs delete mode 100644 src/NzbDrone.Core/Datastore/Migration/Framework/MigrationLoggerProvider.cs rename src/NzbDrone.Host/{WebHost => }/AccessControl/RemoteAccessAdapter.cs (90%) create mode 100644 src/NzbDrone.Host/AppLifetime.cs delete mode 100644 src/NzbDrone.Host/ApplicationServer.cs delete mode 100644 src/NzbDrone.Host/IHostController.cs delete mode 100644 src/NzbDrone.Host/IRemoteAccessAdapter.cs delete mode 100644 src/NzbDrone.Host/IUserAlert.cs delete mode 100644 src/NzbDrone.Host/MainAppContainerBuilder.cs delete mode 100644 src/NzbDrone.Host/PlatformValidation.cs delete mode 100644 src/NzbDrone.Host/SpinService.cs create mode 100644 src/NzbDrone.Host/Startup.cs rename src/NzbDrone.Host/{Router.cs => UtilityModeRouter.cs} (76%) delete mode 100644 src/NzbDrone.Host/WebHost/ControllerActivator.cs delete mode 100644 src/NzbDrone.Host/WebHost/WebHostController.cs delete mode 100644 src/NzbDrone.Update/UpdateContainerBuilder.cs delete mode 100644 src/NzbDrone/MessageBoxUserAlert.cs diff --git a/frontend/src/Settings/General/GeneralSettings.js b/frontend/src/Settings/General/GeneralSettings.js index 803325d51..9c94319c8 100644 --- a/frontend/src/Settings/General/GeneralSettings.js +++ b/frontend/src/Settings/General/GeneralSettings.js @@ -23,10 +23,7 @@ const requiresRestartKeys = [ 'enableSsl', 'sslPort', 'sslCertPath', - 'sslCertPassword', - 'authenticationMethod', - 'username', - 'password' + 'sslCertPassword' ]; class GeneralSettings extends Component { diff --git a/frontend/src/Settings/General/SecuritySettings.js b/frontend/src/Settings/General/SecuritySettings.js index c76a7d19d..91a6ca062 100644 --- a/frontend/src/Settings/General/SecuritySettings.js +++ b/frontend/src/Settings/General/SecuritySettings.js @@ -85,7 +85,6 @@ class SecuritySettings extends Component { name="authenticationMethod" values={authenticationMethodOptions} helpText="Require Username and Password to access Lidarr" - helpTextWarning="Requires restart to take effect" onChange={onInputChange} {...authenticationMethod} /> @@ -99,7 +98,6 @@ class SecuritySettings extends Component { @@ -114,7 +112,6 @@ class SecuritySettings extends Component { diff --git a/src/Lidarr.Api.V1/Commands/CommandController.cs b/src/Lidarr.Api.V1/Commands/CommandController.cs index 331a52d01..312e0fe66 100644 --- a/src/Lidarr.Api.V1/Commands/CommandController.cs +++ b/src/Lidarr.Api.V1/Commands/CommandController.cs @@ -8,6 +8,7 @@ using Lidarr.Http.REST.Attributes; using Lidarr.Http.Validation; using Microsoft.AspNetCore.Mvc; using NzbDrone.Common; +using NzbDrone.Common.Composition; using NzbDrone.Common.Serializer; using NzbDrone.Common.TPL; using NzbDrone.Core.Datastore.Events; @@ -22,17 +23,17 @@ namespace Lidarr.Api.V1.Commands public class CommandController : RestControllerWithSignalR, IHandle { private readonly IManageCommandQueue _commandQueueManager; - private readonly IServiceFactory _serviceFactory; + private readonly KnownTypes _knownTypes; private readonly Debouncer _debouncer; private readonly Dictionary _pendingUpdates; public CommandController(IManageCommandQueue commandQueueManager, IBroadcastSignalRMessage signalRBroadcaster, - IServiceFactory serviceFactory) + KnownTypes knownTypes) : base(signalRBroadcaster) { _commandQueueManager = commandQueueManager; - _serviceFactory = serviceFactory; + _knownTypes = knownTypes; PostValidator.RuleFor(c => c.Name).NotBlank(); @@ -49,7 +50,7 @@ namespace Lidarr.Api.V1.Commands public ActionResult StartCommand(CommandResource commandResource) { var commandType = - _serviceFactory.GetImplementations(typeof(Command)) + _knownTypes.GetImplementations(typeof(Command)) .Single(c => c.Name.Replace("Command", "") .Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase)); diff --git a/src/Lidarr.Api.V1/ImportLists/ImportListController.cs b/src/Lidarr.Api.V1/ImportLists/ImportListController.cs index 89735970a..ee4ec2c6a 100644 --- a/src/Lidarr.Api.V1/ImportLists/ImportListController.cs +++ b/src/Lidarr.Api.V1/ImportLists/ImportListController.cs @@ -10,9 +10,9 @@ namespace Lidarr.Api.V1.ImportLists { public static readonly ImportListResourceMapper ResourceMapper = new ImportListResourceMapper(); - public ImportListController(ImportListFactory importListFactory, - QualityProfileExistsValidator qualityProfileExistsValidator, - MetadataProfileExistsValidator metadataProfileExistsValidator) + public ImportListController(IImportListFactory importListFactory, + QualityProfileExistsValidator qualityProfileExistsValidator, + MetadataProfileExistsValidator metadataProfileExistsValidator) : base(importListFactory, "importlist", ResourceMapper) { Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.QualityProfileId)); diff --git a/src/Lidarr.Http/Authentication/ApiKeyAuthenticationHandler.cs b/src/Lidarr.Http/Authentication/ApiKeyAuthenticationHandler.cs index edeb80a94..6213a3e9d 100644 --- a/src/Lidarr.Http/Authentication/ApiKeyAuthenticationHandler.cs +++ b/src/Lidarr.Http/Authentication/ApiKeyAuthenticationHandler.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using NzbDrone.Core.Configuration; namespace Lidarr.Http.Authentication { @@ -17,17 +18,20 @@ namespace Lidarr.Http.Authentication public string HeaderName { get; set; } public string QueryName { get; set; } - public string ApiKey { get; set; } } public class ApiKeyAuthenticationHandler : AuthenticationHandler { + private readonly string _apiKey; + public ApiKeyAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, - ISystemClock clock) + ISystemClock clock, + IConfigFileProvider config) : base(options, logger, encoder, clock) { + _apiKey = config.ApiKey; } private string ParseApiKey() @@ -56,7 +60,7 @@ namespace Lidarr.Http.Authentication return Task.FromResult(AuthenticateResult.NoResult()); } - if (Options.ApiKey == providedApiKey) + if (_apiKey == providedApiKey) { var claims = new List { diff --git a/src/Lidarr.Http/Authentication/AuthenticationBuilderExtensions.cs b/src/Lidarr.Http/Authentication/AuthenticationBuilderExtensions.cs index 13bf22f95..cd7fd31a5 100644 --- a/src/Lidarr.Http/Authentication/AuthenticationBuilderExtensions.cs +++ b/src/Lidarr.Http/Authentication/AuthenticationBuilderExtensions.cs @@ -2,7 +2,6 @@ using System; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; using NzbDrone.Core.Authentication; -using NzbDrone.Core.Configuration; namespace Lidarr.Http.Authentication { @@ -13,53 +12,37 @@ namespace Lidarr.Http.Authentication return authenticationBuilder.AddScheme(name, options); } - public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBuilder authenticationBuilder) + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder authenticationBuilder, string name) { - return authenticationBuilder.AddScheme(AuthenticationType.Basic.ToString(), options => { }); + return authenticationBuilder.AddScheme(name, options => { }); } - public static AuthenticationBuilder AddNoAuthentication(this AuthenticationBuilder authenticationBuilder) + public static AuthenticationBuilder AddNone(this AuthenticationBuilder authenticationBuilder, string name) { - return authenticationBuilder.AddScheme(AuthenticationType.None.ToString(), options => { }); + return authenticationBuilder.AddScheme(name, options => { }); } - public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services, IConfigFileProvider config) + public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services) { - var authBuilder = services.AddAuthentication(config.AuthenticationMethod.ToString()); - - if (config.AuthenticationMethod == AuthenticationType.Basic) - { - authBuilder.AddBasicAuthentication(); - } - else if (config.AuthenticationMethod == AuthenticationType.Forms) - { - authBuilder.AddCookie(AuthenticationType.Forms.ToString(), options => + return services.AddAuthentication() + .AddNone(AuthenticationType.None.ToString()) + .AddBasic(AuthenticationType.Basic.ToString()) + .AddCookie(AuthenticationType.Forms.ToString(), options => { options.AccessDeniedPath = "/login?loginFailed=true"; options.LoginPath = "/login"; options.ExpireTimeSpan = TimeSpan.FromDays(7); + }) + .AddApiKey("API", options => + { + options.HeaderName = "X-Api-Key"; + options.QueryName = "apikey"; + }) + .AddApiKey("SignalR", options => + { + options.HeaderName = "X-Api-Key"; + options.QueryName = "access_token"; }); - } - else - { - authBuilder.AddNoAuthentication(); - } - - authBuilder.AddApiKey("API", options => - { - options.HeaderName = "X-Api-Key"; - options.QueryName = "apikey"; - options.ApiKey = config.ApiKey; - }); - - authBuilder.AddApiKey("SignalR", options => - { - options.HeaderName = "X-Api-Key"; - options.QueryName = "access_token"; - options.ApiKey = config.ApiKey; - }); - - return authBuilder; } } } diff --git a/src/Lidarr.Http/Authentication/AuthenticationController.cs b/src/Lidarr.Http/Authentication/AuthenticationController.cs index 47d238b30..a95be2689 100644 --- a/src/Lidarr.Http/Authentication/AuthenticationController.cs +++ b/src/Lidarr.Http/Authentication/AuthenticationController.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Core.Authentication; using NzbDrone.Core.Configuration; namespace Lidarr.Http.Authentication @@ -35,14 +36,14 @@ namespace Lidarr.Http.Authentication { new Claim("user", user.Username), new Claim("identifier", user.Identifier.ToString()), - new Claim("UiAuth", "true") + new Claim("AuthType", AuthenticationType.Forms.ToString()) }; var authProperties = new AuthenticationProperties { IsPersistent = resource.RememberMe == "on" }; - await HttpContext.SignInAsync(new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user", "identifier")), authProperties); + await HttpContext.SignInAsync(AuthenticationType.Forms.ToString(), new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user", "identifier")), authProperties); return Redirect("/"); } @@ -51,7 +52,7 @@ namespace Lidarr.Http.Authentication public async Task Logout() { _authService.Logout(HttpContext); - await HttpContext.SignOutAsync(); + await HttpContext.SignOutAsync(AuthenticationType.Forms.ToString()); return Redirect("/"); } } diff --git a/src/Lidarr.Http/Authentication/BasicAuthenticationHandler.cs b/src/Lidarr.Http/Authentication/BasicAuthenticationHandler.cs index 7f198991e..5e2d6502a 100644 --- a/src/Lidarr.Http/Authentication/BasicAuthenticationHandler.cs +++ b/src/Lidarr.Http/Authentication/BasicAuthenticationHandler.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Core.Authentication; namespace Lidarr.Http.Authentication { @@ -58,7 +59,7 @@ namespace Lidarr.Http.Authentication { new Claim("user", user.Username), new Claim("identifier", user.Identifier.ToString()), - new Claim("UiAuth", "true") + new Claim("AuthType", AuthenticationType.Basic.ToString()) }; var identity = new ClaimsIdentity(claims, "Basic", "user", "identifier"); diff --git a/src/Lidarr.Http/Authentication/NoAuthenticationHandler.cs b/src/Lidarr.Http/Authentication/NoAuthenticationHandler.cs index 5152bc2b3..afbf8c717 100644 --- a/src/Lidarr.Http/Authentication/NoAuthenticationHandler.cs +++ b/src/Lidarr.Http/Authentication/NoAuthenticationHandler.cs @@ -5,13 +5,13 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using NzbDrone.Core.Authentication; namespace Lidarr.Http.Authentication { public class NoAuthenticationHandler : AuthenticationHandler { - public NoAuthenticationHandler(IAuthenticationService authService, - IOptionsMonitor options, + public NoAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) @@ -24,7 +24,7 @@ namespace Lidarr.Http.Authentication var claims = new List { new Claim("user", "Anonymous"), - new Claim("UiAuth", "true") + new Claim("AuthType", AuthenticationType.None.ToString()) }; var identity = new ClaimsIdentity(claims, "NoAuth", "user", "identifier"); diff --git a/src/Lidarr.Http/Frontend/Mappers/HtmlMapperBase.cs b/src/Lidarr.Http/Frontend/Mappers/HtmlMapperBase.cs index f56e1c79a..85bd9acbd 100644 --- a/src/Lidarr.Http/Frontend/Mappers/HtmlMapperBase.cs +++ b/src/Lidarr.Http/Frontend/Mappers/HtmlMapperBase.cs @@ -10,13 +10,13 @@ namespace Lidarr.Http.Frontend.Mappers public abstract class HtmlMapperBase : StaticResourceMapperBase { private readonly IDiskProvider _diskProvider; - private readonly Func _cacheBreakProviderFactory; + private readonly Lazy _cacheBreakProviderFactory; private static readonly Regex ReplaceRegex = new Regex(@"(?:(?href|src)=\"")(?.*?(?css|js|png|ico|ics|svg|json))(?:\"")(?:\s(?data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase); private string _generatedContent; protected HtmlMapperBase(IDiskProvider diskProvider, - Func cacheBreakProviderFactory, + Lazy cacheBreakProviderFactory, Logger logger) : base(diskProvider, logger) { @@ -56,7 +56,7 @@ namespace Lidarr.Http.Frontend.Mappers } var text = _diskProvider.ReadAllText(HtmlPath); - var cacheBreakProvider = _cacheBreakProviderFactory(); + var cacheBreakProvider = _cacheBreakProviderFactory.Value; text = ReplaceRegex.Replace(text, match => { diff --git a/src/Lidarr.Http/Frontend/Mappers/IndexHtmlMapper.cs b/src/Lidarr.Http/Frontend/Mappers/IndexHtmlMapper.cs index 4a22c3dd5..2f0b6d445 100644 --- a/src/Lidarr.Http/Frontend/Mappers/IndexHtmlMapper.cs +++ b/src/Lidarr.Http/Frontend/Mappers/IndexHtmlMapper.cs @@ -14,7 +14,7 @@ namespace Lidarr.Http.Frontend.Mappers public IndexHtmlMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, - Func cacheBreakProviderFactory, + Lazy cacheBreakProviderFactory, Logger logger) : base(diskProvider, cacheBreakProviderFactory, logger) { diff --git a/src/Lidarr.Http/Frontend/Mappers/LoginHtmlMapper.cs b/src/Lidarr.Http/Frontend/Mappers/LoginHtmlMapper.cs index 30499d270..c20b3755a 100644 --- a/src/Lidarr.Http/Frontend/Mappers/LoginHtmlMapper.cs +++ b/src/Lidarr.Http/Frontend/Mappers/LoginHtmlMapper.cs @@ -11,7 +11,7 @@ namespace Lidarr.Http.Frontend.Mappers { public LoginHtmlMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, - Func cacheBreakProviderFactory, + Lazy cacheBreakProviderFactory, IConfigFileProvider configFileProvider, Logger logger) : base(diskProvider, cacheBreakProviderFactory, logger) diff --git a/src/Lidarr.Http/Middleware/BufferingMiddleware.cs b/src/Lidarr.Http/Middleware/BufferingMiddleware.cs new file mode 100644 index 000000000..e7823defe --- /dev/null +++ b/src/Lidarr.Http/Middleware/BufferingMiddleware.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Lidarr.Http.Middleware +{ + public class BufferingMiddleware + { + private readonly RequestDelegate _next; + private readonly List _urls; + + public BufferingMiddleware(RequestDelegate next, List urls) + { + _next = next; + _urls = urls; + } + + public async Task InvokeAsync(HttpContext context) + { + if (_urls.Any(p => context.Request.Path.StartsWithSegments(p, StringComparison.OrdinalIgnoreCase))) + { + context.Request.EnableBuffering(); + } + + await _next(context); + } + } +} diff --git a/src/NzbDrone.Common.Test/InstrumentationTests/SentryTargetFixture.cs b/src/NzbDrone.Common.Test/InstrumentationTests/SentryTargetFixture.cs index 017bbd347..a2400c55b 100644 --- a/src/NzbDrone.Common.Test/InstrumentationTests/SentryTargetFixture.cs +++ b/src/NzbDrone.Common.Test/InstrumentationTests/SentryTargetFixture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; using FluentAssertions; @@ -21,7 +21,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests private static Exception[] FilteredExceptions = new Exception[] { new UnauthorizedAccessException(), - new TinyIoC.TinyIoCResolutionException(typeof(string)), new OutOfMemoryException() }; diff --git a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs b/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs index b10f1adee..ae6785780 100644 --- a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs +++ b/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs @@ -1,8 +1,13 @@ -using System.Linq; +using System.Linq; +using DryIoc; +using DryIoc.Microsoft.DependencyInjection; using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Core.Datastore; +using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; using NzbDrone.Host; @@ -16,11 +21,16 @@ namespace NzbDrone.Common.Test [Test] public void event_handlers_should_be_unique() { - var container = MainAppContainerBuilder.BuildContainer(new StartupContext()); - container.Register(new MainDatabase(null)); - container.Resolve().Register(); + var container = new Container(rules => rules.WithNzbDroneRules()) + .AddNzbDroneLogger() + .AutoAddServices(Bootstrap.ASSEMBLIES) + .AddDummyDatabase() + .AddStartupContext(new StartupContext("first", "second")) + .GetServiceProvider(); - Mocker.SetConstant(container); + container.GetRequiredService().Register(); + + Mocker.SetConstant(container); var handlers = Subject.BuildAll>() .Select(c => c.GetType().FullName); diff --git a/src/NzbDrone.Common/Composition/AssemblyLoader.cs b/src/NzbDrone.Common/Composition/AssemblyLoader.cs new file mode 100644 index 000000000..1b50af2bd --- /dev/null +++ b/src/NzbDrone.Common/Composition/AssemblyLoader.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.Text; +using System.Threading.Tasks; +using NzbDrone.Common.EnvironmentInfo; + +namespace NzbDrone.Common.Composition +{ + public class AssemblyLoader + { + static AssemblyLoader() + { + AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ContainerResolveEventHandler); + RegisterSQLiteResolver(); + } + + public static IEnumerable Load(IEnumerable assemblies) + { + var toLoad = assemblies.ToList(); + toLoad.Add("Lidarr.Common"); + toLoad.Add(OsInfo.IsWindows ? "Lidarr.Windows" : "Lidarr.Mono"); + + var startupPath = AppDomain.CurrentDomain.BaseDirectory; + + return toLoad.Select(x => + AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{x}.dll"))); + } + + private static Assembly ContainerResolveEventHandler(object sender, ResolveEventArgs args) + { + var resolver = new AssemblyDependencyResolver(args.RequestingAssembly.Location); + var assemblyPath = resolver.ResolveAssemblyToPath(new AssemblyName(args.Name)); + + if (assemblyPath == null) + { + return null; + } + + return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); + } + + public static void RegisterSQLiteResolver() + { + // This ensures we look for sqlite3 using libsqlite3.so.0 on Linux and not libsqlite3.so which + // is less likely to exist. + var sqliteAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath( + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "System.Data.SQLite.dll")); + + try + { + NativeLibrary.SetDllImportResolver(sqliteAssembly, LoadSqliteNativeLib); + } + catch (InvalidOperationException) + { + // This can only be set once per assembly + // Catch required for NzbDrone.Host tests + } + } + + private static IntPtr LoadSqliteNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath) + { + var mappedName = OsInfo.IsLinux && libraryName == "sqlite3" ? "libsqlite3.so.0" : libraryName; + return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath); + } + } +} diff --git a/src/NzbDrone.Common/Composition/Container.cs b/src/NzbDrone.Common/Composition/Container.cs deleted file mode 100644 index 6ae1e4bfa..000000000 --- a/src/NzbDrone.Common/Composition/Container.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using TinyIoC; - -namespace NzbDrone.Common.Composition -{ - public class Container : IContainer - { - private readonly TinyIoCContainer _container; - private readonly List _loadedTypes; - - public Container(TinyIoCContainer container, List loadedTypes) - { - _container = container; - _loadedTypes = loadedTypes; - _container.Register(this); - } - - public void Register() - where TImplementation : class, TService - where TService : class - { - _container.Register(); - } - - public void Register(T instance) - where T : class - { - _container.Register(instance); - } - - public T Resolve() - where T : class - { - return _container.Resolve(); - } - - public object Resolve(Type type) - { - return _container.Resolve(type); - } - - public void Register(Type serviceType, Type implementationType) - { - _container.Register(serviceType, implementationType); - } - - public void Register(Func factory) - where TService : class - { - _container.Register((c, n) => factory(this)); - } - - public void RegisterSingleton(Type service, Type implementation) - { - var factory = CreateSingletonImplementationFactory(implementation); - - // For Resolve and ResolveAll - _container.Register(service, factory); - - // For ctor(IEnumerable) - var enumerableType = typeof(IEnumerable<>).MakeGenericType(service); - _container.Register(enumerableType, (c, p) => - { - var instance = factory(c, p); - var result = Array.CreateInstance(service, 1); - result.SetValue(instance, 0); - return result; - }); - } - - public IEnumerable ResolveAll() - where T : class - { - return _container.ResolveAll(); - } - - public void RegisterAllAsSingleton(Type service, IEnumerable implementationList) - { - foreach (var implementation in implementationList) - { - var factory = CreateSingletonImplementationFactory(implementation); - - // For ResolveAll and ctor(IEnumerable) - _container.Register(service, factory, implementation.FullName); - } - } - - private Func CreateSingletonImplementationFactory(Type implementation) - { - const string singleImplPrefix = "singleImpl_"; - - _container.Register(implementation, implementation, singleImplPrefix + implementation.FullName).AsSingleton(); - - return (c, p) => _container.Resolve(implementation, singleImplPrefix + implementation.FullName); - } - - public bool IsTypeRegistered(Type type) - { - return _container.CanResolve(type); - } - - public IEnumerable GetImplementations(Type contractType) - { - return _loadedTypes - .Where(implementation => - contractType.IsAssignableFrom(implementation) && - !implementation.IsInterface && - !implementation.IsAbstract); - } - } -} diff --git a/src/NzbDrone.Common/Composition/ContainerBuilderBase.cs b/src/NzbDrone.Common/Composition/ContainerBuilderBase.cs deleted file mode 100644 index 093e25eb1..000000000 --- a/src/NzbDrone.Common/Composition/ContainerBuilderBase.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Loader; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Messaging; -using TinyIoC; - -namespace NzbDrone.Common.Composition -{ - public abstract class ContainerBuilderBase - { - private readonly List _loadedTypes; - - protected IContainer Container { get; } - - protected ContainerBuilderBase(IStartupContext args, List assemblies) - { - _loadedTypes = new List(); - - assemblies.Add(OsInfo.IsWindows ? "Lidarr.Windows" : "Lidarr.Mono"); - assemblies.Add("Lidarr.Common"); - - var startupPath = AppDomain.CurrentDomain.BaseDirectory; - - foreach (var assemblyName in assemblies) - { - _loadedTypes.AddRange(AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{assemblyName}.dll")).GetTypes()); - } - - AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ContainerResolveEventHandler); - RegisterSQLiteResolver(); - - Container = new Container(new TinyIoCContainer(), _loadedTypes); - AutoRegisterInterfaces(); - Container.Register(args); - } - - private static Assembly ContainerResolveEventHandler(object sender, ResolveEventArgs args) - { - var resolver = new AssemblyDependencyResolver(args.RequestingAssembly.Location); - var assemblyPath = resolver.ResolveAssemblyToPath(new AssemblyName(args.Name)); - - if (assemblyPath == null) - { - return null; - } - - return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); - } - - public static void RegisterSQLiteResolver() - { - // This ensures we look for sqlite3 using libsqlite3.so.0 on Linux and not libsqlite3.so which - // is less likely to exist. - var sqliteAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath( - Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "System.Data.SQLite.dll")); - - try - { - NativeLibrary.SetDllImportResolver(sqliteAssembly, LoadSqliteNativeLib); - } - catch (InvalidOperationException) - { - // This can only be set once per assembly - // Catch required for NzbDrone.Host tests - } - } - - private static IntPtr LoadSqliteNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath) - { - var mappedName = OsInfo.IsLinux && libraryName == "sqlite3" ? "libsqlite3.so.0" : libraryName; - return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath); - } - - private void AutoRegisterInterfaces() - { - var loadedInterfaces = _loadedTypes.Where(t => t.IsInterface).ToList(); - var implementedInterfaces = _loadedTypes.SelectMany(t => t.GetInterfaces()); - - var contracts = loadedInterfaces.Union(implementedInterfaces).Where(c => !c.IsGenericTypeDefinition && !string.IsNullOrWhiteSpace(c.FullName)) - .Where(c => !c.FullName.StartsWith("System")) - .Except(new List { typeof(IMessage), typeof(IEvent), typeof(IContainer) }).Distinct().OrderBy(c => c.FullName); - - foreach (var contract in contracts) - { - AutoRegisterImplementations(contract); - } - } - - protected void AutoRegisterImplementations() - { - AutoRegisterImplementations(typeof(TContract)); - } - - private void AutoRegisterImplementations(Type contractType) - { - var implementations = Container.GetImplementations(contractType).Where(c => !c.IsGenericTypeDefinition).ToList(); - - if (implementations.Count == 0) - { - return; - } - - if (implementations.Count == 1) - { - var impl = implementations.Single(); - Container.RegisterSingleton(contractType, impl); - } - else - { - Container.RegisterAllAsSingleton(contractType, implementations); - } - } - } -} diff --git a/src/NzbDrone.Common/Composition/Extensions.cs b/src/NzbDrone.Common/Composition/Extensions.cs new file mode 100644 index 000000000..d3cc6b52c --- /dev/null +++ b/src/NzbDrone.Common/Composition/Extensions.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; +using DryIoc; +using NzbDrone.Common.EnvironmentInfo; + +namespace NzbDrone.Common.Composition.Extensions +{ + public static class ServiceCollectionExtensions + { + public static Rules WithNzbDroneRules(this Rules rules) + { + return rules.WithMicrosoftDependencyInjectionRules() + .WithAutoConcreteTypeResolution() + .WithDefaultReuse(Reuse.Singleton); + } + + public static IContainer AddStartupContext(this IContainer container, StartupContext context) + { + container.RegisterInstance(context, ifAlreadyRegistered: IfAlreadyRegistered.Replace); + return container; + } + + public static IContainer AutoAddServices(this IContainer container, List assemblyNames) + { + var assemblies = AssemblyLoader.Load(assemblyNames); + + container.RegisterMany(assemblies, + serviceTypeCondition: type => type.IsInterface && !string.IsNullOrWhiteSpace(type.FullName) && !type.FullName.StartsWith("System"), + reuse: Reuse.Singleton); + + var knownTypes = new KnownTypes(assemblies.SelectMany(x => x.GetTypes()).ToList()); + container.RegisterInstance(knownTypes); + + return container; + } + } +} diff --git a/src/NzbDrone.Common/Composition/IContainer.cs b/src/NzbDrone.Common/Composition/IContainer.cs deleted file mode 100644 index 9e7402017..000000000 --- a/src/NzbDrone.Common/Composition/IContainer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace NzbDrone.Common.Composition -{ - public interface IContainer - { - void Register(T instance) - where T : class; - - void Register() - where TImplementation : class, TService - where TService : class; - T Resolve() - where T : class; - object Resolve(Type type); - void Register(Type serviceType, Type implementationType); - void Register(Func factory) - where TService : class; - void RegisterSingleton(Type service, Type implementation); - IEnumerable ResolveAll() - where T : class; - void RegisterAllAsSingleton(Type registrationType, IEnumerable implementationList); - bool IsTypeRegistered(Type type); - - IEnumerable GetImplementations(Type contractType); - } -} diff --git a/src/NzbDrone.Common/Composition/KnownTypes.cs b/src/NzbDrone.Common/Composition/KnownTypes.cs new file mode 100644 index 000000000..3f816633f --- /dev/null +++ b/src/NzbDrone.Common/Composition/KnownTypes.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Common.Composition +{ + public class KnownTypes + { + private List _knownTypes; + + // So unity can resolve for tests + public KnownTypes() + : this(new List()) + { + } + + public KnownTypes(List loadedTypes) + { + _knownTypes = loadedTypes; + } + + public IEnumerable GetImplementations(Type contractType) + { + return _knownTypes + .Where(implementation => + contractType.IsAssignableFrom(implementation) && + !implementation.IsInterface && + !implementation.IsAbstract); + } + } +} diff --git a/src/NzbDrone.Common/Instrumentation/Extensions/CompositionExtensions.cs b/src/NzbDrone.Common/Instrumentation/Extensions/CompositionExtensions.cs new file mode 100644 index 000000000..10b7d4af4 --- /dev/null +++ b/src/NzbDrone.Common/Instrumentation/Extensions/CompositionExtensions.cs @@ -0,0 +1,14 @@ +using DryIoc; +using NLog; + +namespace NzbDrone.Common.Instrumentation.Extensions +{ + public static class CompositionExtensions + { + public static IContainer AddNzbDroneLogger(this IContainer container) + { + container.Register(Made.Of(() => LogManager.GetLogger(Arg.Index(0)), r => r.Parent.ImplementationType.Name.ToString()), reuse: Reuse.Transient); + return container; + } + } +} diff --git a/src/NzbDrone.Common/Lidarr.Common.csproj b/src/NzbDrone.Common/Lidarr.Common.csproj index 83d85e4db..863deaa4a 100644 --- a/src/NzbDrone.Common/Lidarr.Common.csproj +++ b/src/NzbDrone.Common/Lidarr.Common.csproj @@ -5,6 +5,8 @@ ISMUSL + + diff --git a/src/NzbDrone.Common/ServiceFactory.cs b/src/NzbDrone.Common/ServiceFactory.cs index 4f7e4bb0b..06b3749e8 100644 --- a/src/NzbDrone.Common/ServiceFactory.cs +++ b/src/NzbDrone.Common/ServiceFactory.cs @@ -1,7 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using NzbDrone.Common.Composition; +using Microsoft.Extensions.DependencyInjection; namespace NzbDrone.Common { @@ -17,9 +17,9 @@ namespace NzbDrone.Common public class ServiceFactory : IServiceFactory { - private readonly IContainer _container; + private readonly System.IServiceProvider _container; - public ServiceFactory(IContainer container) + public ServiceFactory(System.IServiceProvider container) { _container = container; } @@ -27,23 +27,23 @@ namespace NzbDrone.Common public T Build() where T : class { - return _container.Resolve(); + return _container.GetRequiredService(); } public IEnumerable BuildAll() where T : class { - return _container.ResolveAll().GroupBy(c => c.GetType().FullName).Select(g => g.First()); + return _container.GetServices().GroupBy(c => c.GetType().FullName).Select(g => g.First()); } public object Build(Type contract) { - return _container.Resolve(contract); + return _container.GetRequiredService(contract); } public IEnumerable GetImplementations(Type contract) { - return _container.GetImplementations(contract); + return _container.GetServices(contract).Select(x => x.GetType()); } } } diff --git a/src/NzbDrone.Common/TinyIoC.cs b/src/NzbDrone.Common/TinyIoC.cs deleted file mode 100644 index d2a293706..000000000 --- a/src/NzbDrone.Common/TinyIoC.cs +++ /dev/null @@ -1,3771 +0,0 @@ -//=============================================================================== -// TinyIoC -// -// An easy to use, hassle free, Inversion of Control Container for small projects -// and beginners alike. -// -// https://github.com/grumpydev/TinyIoC -//=============================================================================== -// Copyright © Steven Robbins. All rights reserved. -// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY -// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT -// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE. -//=============================================================================== - -#pragma warning disable SX1101, SA1108, SA1119, SA1124, SA1200, SA1208, SA1314, SA1403, SA1503, SA1514, SA1515, SA1519, SX1309 - -#region Preprocessor Directives -// Uncomment this line if you want the container to automatically -// register the TinyMessenger messenger/event aggregator -//#define TINYMESSENGER - -// Preprocessor directives for enabling/disabling functionality -// depending on platform features. If the platform has an appropriate -// #DEFINE then these should be set automatically below. -#define EXPRESSIONS // Platform supports System.Linq.Expressions -#define COMPILED_EXPRESSIONS // Platform supports compiling expressions -#define APPDOMAIN_GETASSEMBLIES // Platform supports getting all assemblies from the AppDomain object -#define UNBOUND_GENERICS_GETCONSTRUCTORS // Platform supports GetConstructors on unbound generic types -#define GETPARAMETERS_OPEN_GENERICS // Platform supports GetParameters on open generics -#define RESOLVE_OPEN_GENERICS // Platform supports resolving open generics -#define READER_WRITER_LOCK_SLIM // Platform supports ReaderWriterLockSlim - -//// NETFX_CORE -//#if NETFX_CORE -//#endif - -// CompactFramework / Windows Phone 7 -// By default does not support System.Linq.Expressions. -// AppDomain object does not support enumerating all assemblies in the app domain. -#if PocketPC || WINDOWS_PHONE -#undef EXPRESSIONS -#undef COMPILED_EXPRESSIONS -#undef APPDOMAIN_GETASSEMBLIES -#undef UNBOUND_GENERICS_GETCONSTRUCTORS -#endif - -// PocketPC has a bizarre limitation on enumerating parameters on unbound generic methods. -// We need to use a slower workaround in that case. -#if PocketPC -#undef GETPARAMETERS_OPEN_GENERICS -#undef RESOLVE_OPEN_GENERICS -#undef READER_WRITER_LOCK_SLIM -#endif - -#if SILVERLIGHT -#undef APPDOMAIN_GETASSEMBLIES -#endif - -#if NETFX_CORE -#undef APPDOMAIN_GETASSEMBLIES -#undef RESOLVE_OPEN_GENERICS -#endif - -#if COMPILED_EXPRESSIONS -#define USE_OBJECT_CONSTRUCTOR -#endif - -#endregion -namespace TinyIoC -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - -#if EXPRESSIONS - using System.Linq.Expressions; - using NLog; - using System.Threading; - -#endif - -#if NETFX_CORE - using System.Threading.Tasks; - using Windows.Storage.Search; - using Windows.Storage; - using Windows.UI.Xaml.Shapes; -#endif - - #region SafeDictionary -#if READER_WRITER_LOCK_SLIM - public class SafeDictionary : IDisposable - { - private readonly ReaderWriterLockSlim _padlock = new ReaderWriterLockSlim(); - private readonly Dictionary _Dictionary = new Dictionary(); - - public TValue this[TKey key] - { - set - { - _padlock.EnterWriteLock(); - - try - { - TValue current; - if (_Dictionary.TryGetValue(key, out current)) - { - var disposable = current as IDisposable; - - if (disposable != null) - disposable.Dispose(); - } - - _Dictionary[key] = value; - } - finally - { - _padlock.ExitWriteLock(); - } - } - } - - public bool TryGetValue(TKey key, out TValue value) - { - _padlock.EnterReadLock(); - try - { - return _Dictionary.TryGetValue(key, out value); - } - finally - { - _padlock.ExitReadLock(); - } - } - - public bool Remove(TKey key) - { - _padlock.EnterWriteLock(); - try - { - return _Dictionary.Remove(key); - } - finally - { - _padlock.ExitWriteLock(); - } - } - - public void Clear() - { - _padlock.EnterWriteLock(); - try - { - _Dictionary.Clear(); - } - finally - { - _padlock.ExitWriteLock(); - } - } - - public IEnumerable Keys - { - get - { - _padlock.EnterReadLock(); - try - { - return new List(_Dictionary.Keys); - } - finally - { - _padlock.ExitReadLock(); - } - } - } - - #region IDisposable Members - - public void Dispose() - { - _padlock.EnterWriteLock(); - - try - { - var disposableItems = from item in _Dictionary.Values - where item is IDisposable - select item as IDisposable; - - foreach (var item in disposableItems) - { - item.Dispose(); - } - } - finally - { - _padlock.ExitWriteLock(); - } - - GC.SuppressFinalize(this); - } - - #endregion - } -#else - public class SafeDictionary : IDisposable - { - private readonly object _Padlock = new object(); - private readonly Dictionary _Dictionary = new Dictionary(); - - public TValue this[TKey key] - { - set - { - lock (_Padlock) - { - TValue current; - if (_Dictionary.TryGetValue(key, out current)) - { - var disposable = current as IDisposable; - - if (disposable != null) - disposable.Dispose(); - } - - _Dictionary[key] = value; - } - } - } - - public bool TryGetValue(TKey key, out TValue value) - { - lock (_Padlock) - { - return _Dictionary.TryGetValue(key, out value); - } - } - - public bool Remove(TKey key) - { - lock (_Padlock) - { - return _Dictionary.Remove(key); - } - } - - public void Clear() - { - lock (_Padlock) - { - _Dictionary.Clear(); - } - } - - public IEnumerable Keys - { - get - { - return _Dictionary.Keys; - } - } - #region IDisposable Members - - public void Dispose() - { - lock (_Padlock) - { - var disposableItems = from item in _Dictionary.Values - where item is IDisposable - select item as IDisposable; - - foreach (var item in disposableItems) - { - item.Dispose(); - } - } - - GC.SuppressFinalize(this); - } - - #endregion - } -#endif - #endregion - - #region Extensions - public static class AssemblyExtensions - { - public static Type[] SafeGetTypes(this Assembly assembly) - { - Type[] assemblies; - - try - { - assemblies = assembly.GetTypes(); - } - catch (System.IO.FileNotFoundException) - { - assemblies = new Type[] { }; - } - catch (NotSupportedException) - { - assemblies = new Type[] { }; - } -#if !NETFX_CORE - catch (ReflectionTypeLoadException e) - { - assemblies = e.Types.Where(t => t != null).ToArray(); - } -#endif - return assemblies; - } - } - - public static class TypeExtensions - { - private static SafeDictionary _genericMethodCache; - - static TypeExtensions() - { - _genericMethodCache = new SafeDictionary(); - } - - //#if NETFX_CORE - // /// - // /// Gets a generic method from a type given the method name, generic types and parameter types - // /// - // /// Source type - // /// Name of the method - // /// Generic types to use to make the method generic - // /// Method parameters - // /// MethodInfo or null if no matches found - // /// - // /// - // public static MethodInfo GetGenericMethod(this Type sourceType, string methodName, Type[] genericTypes, Type[] parameterTypes) - // { - // MethodInfo method; - // var cacheKey = new GenericMethodCacheKey(sourceType, methodName, genericTypes, parameterTypes); - - // // Shouldn't need any additional locking - // // we don't care if we do the method info generation - // // more than once before it gets cached. - // if (!_genericMethodCache.TryGetValue(cacheKey, out method)) - // { - // method = GetMethod(sourceType, methodName, genericTypes, parameterTypes); - // _genericMethodCache[cacheKey] = method; - // } - - // return method; - // } - //#else - /// - /// Gets a generic method from a type given the method name, binding flags, generic types and parameter types - /// - /// Source type - /// Binding flags - /// Name of the method - /// Generic types to use to make the method generic - /// Method parameters - /// MethodInfo or null if no matches found - /// - /// - public static MethodInfo GetGenericMethod(this Type sourceType, BindingFlags bindingFlags, string methodName, Type[] genericTypes, Type[] parameterTypes) - { - MethodInfo method; - var cacheKey = new GenericMethodCacheKey(sourceType, methodName, genericTypes, parameterTypes); - - // Shouldn't need any additional locking - // we don't care if we do the method info generation - // more than once before it gets cached. - if (!_genericMethodCache.TryGetValue(cacheKey, out method)) - { - method = GetMethod(sourceType, bindingFlags, methodName, genericTypes, parameterTypes); - _genericMethodCache[cacheKey] = method; - } - - return method; - } - - //#endif -#if NETFX_CORE - private static MethodInfo GetMethod(Type sourceType, BindingFlags flags, string methodName, Type[] genericTypes, Type[] parameterTypes) - { - var methods = - sourceType.GetMethods(flags).Where( - mi => string.Equals(methodName, mi.Name, StringComparison.Ordinal)).Where( - mi => mi.ContainsGenericParameters).Where(mi => mi.GetGenericArguments().Length == genericTypes.Length). - Where(mi => mi.GetParameters().Length == parameterTypes.Length).Select( - mi => mi.MakeGenericMethod(genericTypes)).Where( - mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)).ToList(); - - if (methods.Count > 1) - { - throw new AmbiguousMatchException(); - } - - return methods.FirstOrDefault(); - } -#else - private static MethodInfo GetMethod(Type sourceType, BindingFlags bindingFlags, string methodName, Type[] genericTypes, Type[] parameterTypes) - { -#if GETPARAMETERS_OPEN_GENERICS - var methods = - sourceType.GetMethods(bindingFlags).Where( - mi => string.Equals(methodName, mi.Name, StringComparison.Ordinal)).Where( - mi => mi.ContainsGenericParameters).Where(mi => mi.GetGenericArguments().Length == genericTypes.Length). - Where(mi => mi.GetParameters().Length == parameterTypes.Length).Select( - mi => mi.MakeGenericMethod(genericTypes)).Where( - mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)).ToList(); -#else - var validMethods = from method in sourceType.GetMethods(bindingFlags) - where method.Name == methodName - where method.IsGenericMethod - where method.GetGenericArguments().Length == genericTypes.Length - let genericMethod = method.MakeGenericMethod(genericTypes) - where genericMethod.GetParameters().Count() == parameterTypes.Length - where genericMethod.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes) - select genericMethod; - - var methods = validMethods.ToList(); -#endif - if (methods.Count > 1) - { - throw new AmbiguousMatchException(); - } - - return methods.FirstOrDefault(); - } -#endif - - private sealed class GenericMethodCacheKey - { - private readonly Type _sourceType; - - private readonly string _methodName; - - private readonly Type[] _genericTypes; - - private readonly Type[] _parameterTypes; - - private readonly int _hashCode; - - public GenericMethodCacheKey(Type sourceType, string methodName, Type[] genericTypes, Type[] parameterTypes) - { - _sourceType = sourceType; - _methodName = methodName; - _genericTypes = genericTypes; - _parameterTypes = parameterTypes; - _hashCode = GenerateHashCode(); - } - - public override bool Equals(object obj) - { - var cacheKey = obj as GenericMethodCacheKey; - if (cacheKey == null) - return false; - - if (_sourceType != cacheKey._sourceType) - return false; - - if (!string.Equals(_methodName, cacheKey._methodName, StringComparison.Ordinal)) - return false; - - if (_genericTypes.Length != cacheKey._genericTypes.Length) - return false; - - if (_parameterTypes.Length != cacheKey._parameterTypes.Length) - return false; - - for (int i = 0; i < _genericTypes.Length; ++i) - { - if (_genericTypes[i] != cacheKey._genericTypes[i]) - return false; - } - - for (int i = 0; i < _parameterTypes.Length; ++i) - { - if (_parameterTypes[i] != cacheKey._parameterTypes[i]) - return false; - } - - return true; - } - - public override int GetHashCode() - { - return _hashCode; - } - - private int GenerateHashCode() - { - unchecked - { - var result = _sourceType.GetHashCode(); - - result = (result * 397) ^ _methodName.GetHashCode(); - - for (int i = 0; i < _genericTypes.Length; ++i) - { - result = (result * 397) ^ _genericTypes[i].GetHashCode(); - } - - for (int i = 0; i < _parameterTypes.Length; ++i) - { - result = (result * 397) ^ _parameterTypes[i].GetHashCode(); - } - - return result; - } - } - } - } - - // @mbrit - 2012-05-22 - shim for ForEach call on List... -#if NETFX_CORE - internal static class ListExtender - { - internal static void ForEach(this List list, Action callback) - { - foreach (T obj in list) - callback(obj); - } - } -#endif - - #endregion - - #region TinyIoC Exception Types - public class TinyIoCResolutionException : Exception - { - private const string ERROR_TEXT = "Unable to resolve type: {0}"; - - public TinyIoCResolutionException(Type type) - : base(string.Format(ERROR_TEXT, type.FullName)) - { - } - - public TinyIoCResolutionException(Type type, Exception innerException) - : base(string.Format(ERROR_TEXT, type.FullName), innerException) - { - } - } - - public class TinyIoCRegistrationTypeException : Exception - { - private const string REGISTER_ERROR_TEXT = "Cannot register type {0} - abstract classes or interfaces are not valid implementation types for {1}."; - - public TinyIoCRegistrationTypeException(Type type, string factory) - : base(string.Format(REGISTER_ERROR_TEXT, type.FullName, factory)) - { - } - - public TinyIoCRegistrationTypeException(Type type, string factory, Exception innerException) - : base(string.Format(REGISTER_ERROR_TEXT, type.FullName, factory), innerException) - { - } - } - - public class TinyIoCRegistrationException : Exception - { - private const string CONVERT_ERROR_TEXT = "Cannot convert current registration of {0} to {1}"; - private const string GENERIC_CONSTRAINT_ERROR_TEXT = "Type {1} is not valid for a registration of type {0}"; - - public TinyIoCRegistrationException(Type type, string method) - : base(string.Format(CONVERT_ERROR_TEXT, type.FullName, method)) - { - } - - public TinyIoCRegistrationException(Type type, string method, Exception innerException) - : base(string.Format(CONVERT_ERROR_TEXT, type.FullName, method), innerException) - { - } - - public TinyIoCRegistrationException(Type registerType, Type implementationType) - : base(string.Format(GENERIC_CONSTRAINT_ERROR_TEXT, registerType.FullName, implementationType.FullName)) - { - } - - public TinyIoCRegistrationException(Type registerType, Type implementationType, Exception innerException) - : base(string.Format(GENERIC_CONSTRAINT_ERROR_TEXT, registerType.FullName, implementationType.FullName), innerException) - { - } - } - - public class TinyIoCWeakReferenceException : Exception - { - private const string ERROR_TEXT = "Unable to instantiate {0} - referenced object has been reclaimed"; - - public TinyIoCWeakReferenceException(Type type) - : base(string.Format(ERROR_TEXT, type.FullName)) - { - } - - public TinyIoCWeakReferenceException(Type type, Exception innerException) - : base(string.Format(ERROR_TEXT, type.FullName), innerException) - { - } - } - - public class TinyIoCConstructorResolutionException : Exception - { - private const string ERROR_TEXT = "Unable to resolve constructor for {0} using provided Expression."; - - public TinyIoCConstructorResolutionException(Type type) - : base(string.Format(ERROR_TEXT, type.FullName)) - { - } - - public TinyIoCConstructorResolutionException(Type type, Exception innerException) - : base(string.Format(ERROR_TEXT, type.FullName), innerException) - { - } - - public TinyIoCConstructorResolutionException(string message, Exception innerException) - : base(message, innerException) - { - } - - public TinyIoCConstructorResolutionException(string message) - : base(message) - { - } - } - - public class TinyIoCAutoRegistrationException : Exception - { - private const string ERROR_TEXT = "Duplicate implementation of type {0} found ({1})."; - - public TinyIoCAutoRegistrationException(Type registerType, IEnumerable types) - : base(string.Format(ERROR_TEXT, registerType, GetTypesString(types))) - { - } - - public TinyIoCAutoRegistrationException(Type registerType, IEnumerable types, Exception innerException) - : base(string.Format(ERROR_TEXT, registerType, GetTypesString(types)), innerException) - { - } - - private static string GetTypesString(IEnumerable types) - { - var typeNames = from type in types - select type.FullName; - - return string.Join(",", typeNames.ToArray()); - } - } - #endregion - - #region Public Setup / Settings Classes - /// - /// Name/Value pairs for specifying "user" parameters when resolving - /// - public sealed class NamedParameterOverloads : Dictionary - { - public static NamedParameterOverloads FromIDictionary(IDictionary data) - { - return data as NamedParameterOverloads ?? new NamedParameterOverloads(data); - } - - public NamedParameterOverloads() - { - } - - public NamedParameterOverloads(IDictionary data) - : base(data) - { - } - - private static readonly NamedParameterOverloads _Default = new NamedParameterOverloads(); - - public static NamedParameterOverloads Default => _Default; - } - - public enum UnregisteredResolutionActions - { - /// - /// Attempt to resolve type, even if the type isn't registered. - /// - /// Registered types/options will always take precedence. - /// - AttemptResolve, - - /// - /// Fail resolution if type not explicitly registered - /// - Fail, - - /// - /// Attempt to resolve unregistered type if requested type is generic - /// and no registration exists for the specific generic parameters used. - /// - /// Registered types/options will always take precedence. - /// - GenericsOnly - } - - public enum NamedResolutionFailureActions - { - AttemptUnnamedResolution, - Fail - } - - public enum DuplicateImplementationActions - { - RegisterSingle, - RegisterMultiple, - Fail - } - - /// - /// Resolution settings - /// - public sealed class ResolveOptions - { - private static readonly ResolveOptions _Default = new ResolveOptions(); - private static readonly ResolveOptions _FailUnregisteredAndNameNotFound = new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.Fail, UnregisteredResolutionAction = UnregisteredResolutionActions.Fail }; - private static readonly ResolveOptions _FailUnregisteredOnly = new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.AttemptUnnamedResolution, UnregisteredResolutionAction = UnregisteredResolutionActions.Fail }; - private static readonly ResolveOptions _FailNameNotFoundOnly = new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.Fail, UnregisteredResolutionAction = UnregisteredResolutionActions.AttemptResolve }; - - private UnregisteredResolutionActions _UnregisteredResolutionAction = UnregisteredResolutionActions.AttemptResolve; - public UnregisteredResolutionActions UnregisteredResolutionAction - { - get { return _UnregisteredResolutionAction; } - set { _UnregisteredResolutionAction = value; } - } - - private NamedResolutionFailureActions _NamedResolutionFailureAction = NamedResolutionFailureActions.Fail; - public NamedResolutionFailureActions NamedResolutionFailureAction - { - get { return _NamedResolutionFailureAction; } - set { _NamedResolutionFailureAction = value; } - } - - /// - /// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found) - /// - public static ResolveOptions Default => _Default; - - /// - /// Preconfigured option for attempting resolution of unregistered types and failing on named resolution if name not found - /// - public static ResolveOptions FailNameNotFoundOnly => _FailNameNotFoundOnly; - - /// - /// Preconfigured option for failing on resolving unregistered types and on named resolution if name not found - /// - public static ResolveOptions FailUnregisteredAndNameNotFound => _FailUnregisteredAndNameNotFound; - - /// - /// Preconfigured option for failing on resolving unregistered types, but attempting unnamed resolution if name not found - /// - public static ResolveOptions FailUnregisteredOnly => _FailUnregisteredOnly; - } - #endregion - - public sealed partial class TinyIoCContainer : IDisposable - { - #region Fake NETFX_CORE Classes -#if NETFX_CORE - private sealed class MethodAccessException : Exception - { - } - - private sealed class AppDomain - { - public static AppDomain CurrentDomain { get; private set; } - - static AppDomain() - { - CurrentDomain = new AppDomain(); - } - - // @mbrit - 2012-05-30 - in WinRT, this should be done async... - public async Task> GetAssembliesAsync() - { - var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; - - List assemblies = new List(); - - var files = await folder.GetFilesAsync(); - - foreach (StorageFile file in files) - { - if (file.FileType == ".dll" || file.FileType == ".exe") - { - AssemblyName name = new AssemblyName() { Name = System.IO.Path.GetFileNameWithoutExtension(file.Name) }; - try - { - var asm = Assembly.Load(name); - assemblies.Add(asm); - } - catch - { - // ignore exceptions here... - } - } - } - - return assemblies; - } - } -#endif - #endregion - - #region "Fluent" API - /// - /// Registration options for "fluent" API - /// - public sealed class RegisterOptions - { - private TinyIoCContainer _Container; - private TypeRegistration _Registration; - - public RegisterOptions(TinyIoCContainer container, TypeRegistration registration) - { - _Container = container; - _Registration = registration; - } - - /// - /// Make registration a singleton (single instance) if possible - /// - /// RegisterOptions - /// - public RegisterOptions AsSingleton() - { - var currentFactory = _Container.GetCurrentFactory(_Registration); - - if (currentFactory == null) - throw new TinyIoCRegistrationException(_Registration.Type, "singleton"); - - return _Container.AddUpdateRegistration(_Registration, currentFactory.SingletonVariant); - } - - /// - /// Make registration multi-instance if possible - /// - /// RegisterOptions - /// - public RegisterOptions AsMultiInstance() - { - var currentFactory = _Container.GetCurrentFactory(_Registration); - - if (currentFactory == null) - throw new TinyIoCRegistrationException(_Registration.Type, "multi-instance"); - - return _Container.AddUpdateRegistration(_Registration, currentFactory.MultiInstanceVariant); - } - - /// - /// Make registration hold a weak reference if possible - /// - /// RegisterOptions - /// - public RegisterOptions WithWeakReference() - { - var currentFactory = _Container.GetCurrentFactory(_Registration); - - if (currentFactory == null) - throw new TinyIoCRegistrationException(_Registration.Type, "weak reference"); - - return _Container.AddUpdateRegistration(_Registration, currentFactory.WeakReferenceVariant); - } - - /// - /// Make registration hold a strong reference if possible - /// - /// RegisterOptions - /// - public RegisterOptions WithStrongReference() - { - var currentFactory = _Container.GetCurrentFactory(_Registration); - - if (currentFactory == null) - throw new TinyIoCRegistrationException(_Registration.Type, "strong reference"); - - return _Container.AddUpdateRegistration(_Registration, currentFactory.StrongReferenceVariant); - } - -#if EXPRESSIONS - public RegisterOptions UsingConstructor(Expression> constructor) - { - var lambda = constructor as LambdaExpression; - if (lambda == null) - throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); - - var newExpression = lambda.Body as NewExpression; - if (newExpression == null) - throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); - - var constructorInfo = newExpression.Constructor; - if (constructorInfo == null) - throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); - - var currentFactory = _Container.GetCurrentFactory(_Registration); - if (currentFactory == null) - throw new TinyIoCConstructorResolutionException(typeof(RegisterType)); - - currentFactory.SetConstructor(constructorInfo); - - return this; - } -#endif - /// - /// Switches to a custom lifetime manager factory if possible. - /// - /// Usually used for RegisterOptions "To*" extension methods such as the ASP.Net per-request one. - /// - /// RegisterOptions instance - /// Custom lifetime manager - /// Error string to display if switch fails - /// RegisterOptions - public static RegisterOptions ToCustomLifetimeManager(RegisterOptions instance, ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) - { - if (instance == null) - throw new ArgumentNullException("instance", "instance is null."); - - if (lifetimeProvider == null) - throw new ArgumentNullException("lifetimeProvider", "lifetimeProvider is null."); - - if (string.IsNullOrEmpty(errorString)) - throw new ArgumentException("errorString is null or empty.", "errorString"); - - var currentFactory = instance._Container.GetCurrentFactory(instance._Registration); - - if (currentFactory == null) - throw new TinyIoCRegistrationException(instance._Registration.Type, errorString); - - return instance._Container.AddUpdateRegistration(instance._Registration, currentFactory.GetCustomObjectLifetimeVariant(lifetimeProvider, errorString)); - } - } - - /// - /// Registration options for "fluent" API when registering multiple implementations - /// - public sealed class MultiRegisterOptions - { - private IEnumerable _RegisterOptions; - - /// - /// Initializes a new instance of the MultiRegisterOptions class. - /// - /// Registration options - public MultiRegisterOptions(IEnumerable registerOptions) - { - _RegisterOptions = registerOptions; - } - - /// - /// Make registration a singleton (single instance) if possible - /// - /// RegisterOptions - /// - public MultiRegisterOptions AsSingleton() - { - _RegisterOptions = ExecuteOnAllRegisterOptions(ro => ro.AsSingleton()); - return this; - } - - /// - /// Make registration multi-instance if possible - /// - /// MultiRegisterOptions - /// - public MultiRegisterOptions AsMultiInstance() - { - _RegisterOptions = ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance()); - return this; - } - - private IEnumerable ExecuteOnAllRegisterOptions(Func action) - { - var newRegisterOptions = new List(); - - foreach (var registerOption in _RegisterOptions) - { - newRegisterOptions.Add(action(registerOption)); - } - - return newRegisterOptions; - } - } - #endregion - - #region Public API - #region Child Containers - public TinyIoCContainer GetChildContainer() - { - return new TinyIoCContainer(this); - } - #endregion - - #region Registration - /// - /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. - /// - /// If more than one class implements an interface then only one implementation will be registered - /// although no error will be thrown. - /// - public void AutoRegister() - { -#if APPDOMAIN_GETASSEMBLIES - AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), DuplicateImplementationActions.RegisterSingle, null); -#else - AutoRegisterInternal(new Assembly[] {this.GetType().Assembly()}, true, null); -#endif - } - - /// - /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. - /// Types will only be registered if they pass the supplied registration predicate. - /// - /// If more than one class implements an interface then only one implementation will be registered - /// although no error will be thrown. - /// - /// Predicate to determine if a particular type should be registered - public void AutoRegister(Func registrationPredicate) - { -#if APPDOMAIN_GETASSEMBLIES - AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), DuplicateImplementationActions.RegisterSingle, registrationPredicate); -#else - AutoRegisterInternal(new Assembly[] { this.GetType().Assembly()}, true, registrationPredicate); -#endif - } - - /// - /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. - /// - /// What action to take when encountering duplicate implementations of an interface/base class. - /// - public void AutoRegister(DuplicateImplementationActions duplicateAction) - { -#if APPDOMAIN_GETASSEMBLIES - AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), duplicateAction, null); -#else - AutoRegisterInternal(new Assembly[] { this.GetType().Assembly() }, ignoreDuplicateImplementations, null); -#endif - } - - /// - /// Attempt to automatically register all non-generic classes and interfaces in the current app domain. - /// Types will only be registered if they pass the supplied registration predicate. - /// - /// What action to take when encountering duplicate implementations of an interface/base class. - /// Predicate to determine if a particular type should be registered - /// - public void AutoRegister(DuplicateImplementationActions duplicateAction, Func registrationPredicate) - { -#if APPDOMAIN_GETASSEMBLIES - AutoRegisterInternal(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), duplicateAction, registrationPredicate); -#else - AutoRegisterInternal(new Assembly[] { this.GetType().Assembly() }, ignoreDuplicateImplementations, registrationPredicate); -#endif - } - - /// - /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies - /// - /// If more than one class implements an interface then only one implementation will be registered - /// although no error will be thrown. - /// - /// Assemblies to process - public void AutoRegister(IEnumerable assemblies) - { - AutoRegisterInternal(assemblies, DuplicateImplementationActions.RegisterSingle, null); - } - - /// - /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies - /// Types will only be registered if they pass the supplied registration predicate. - /// - /// If more than one class implements an interface then only one implementation will be registered - /// although no error will be thrown. - /// - /// Assemblies to process - /// Predicate to determine if a particular type should be registered - public void AutoRegister(IEnumerable assemblies, Func registrationPredicate) - { - AutoRegisterInternal(assemblies, DuplicateImplementationActions.RegisterSingle, registrationPredicate); - } - - /// - /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies - /// - /// Assemblies to process - /// What action to take when encountering duplicate implementations of an interface/base class. - /// - public void AutoRegister(IEnumerable assemblies, DuplicateImplementationActions duplicateAction) - { - AutoRegisterInternal(assemblies, duplicateAction, null); - } - - /// - /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies - /// Types will only be registered if they pass the supplied registration predicate. - /// - /// Assemblies to process - /// What action to take when encountering duplicate implementations of an interface/base class. - /// Predicate to determine if a particular type should be registered - /// - public void AutoRegister(IEnumerable assemblies, DuplicateImplementationActions duplicateAction, Func registrationPredicate) - { - AutoRegisterInternal(assemblies, duplicateAction, registrationPredicate); - } - - /// - /// Creates/replaces a container class registration with default options. - /// - /// Type to register - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType) - { - return RegisterInternal(registerType, string.Empty, GetDefaultObjectFactory(registerType, registerType)); - } - - /// - /// Creates/replaces a named container class registration with default options. - /// - /// Type to register - /// Name of registration - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType, string name) - { - return RegisterInternal(registerType, name, GetDefaultObjectFactory(registerType, registerType)); - } - - /// - /// Creates/replaces a container class registration with a given implementation and default options. - /// - /// Type to register - /// Type to instantiate that implements RegisterType - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType, Type registerImplementation) - { - return RegisterInternal(registerType, string.Empty, GetDefaultObjectFactory(registerType, registerImplementation)); - } - - /// - /// Creates/replaces a named container class registration with a given implementation and default options. - /// - /// Type to register - /// Type to instantiate that implements RegisterType - /// Name of registration - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType, Type registerImplementation, string name) - { - return RegisterInternal(registerType, name, GetDefaultObjectFactory(registerType, registerImplementation)); - } - - /// - /// Creates/replaces a container class registration with a specific, strong referenced, instance. - /// - /// Type to register - /// Instance of RegisterType to register - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType, object instance) - { - return RegisterInternal(registerType, string.Empty, new InstanceFactory(registerType, registerType, instance)); - } - - /// - /// Creates/replaces a named container class registration with a specific, strong referenced, instance. - /// - /// Type to register - /// Instance of RegisterType to register - /// Name of registration - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType, object instance, string name) - { - return RegisterInternal(registerType, name, new InstanceFactory(registerType, registerType, instance)); - } - - /// - /// Creates/replaces a container class registration with a specific, strong referenced, instance. - /// - /// Type to register - /// Type of instance to register that implements RegisterType - /// Instance of RegisterImplementation to register - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType, Type registerImplementation, object instance) - { - return RegisterInternal(registerType, string.Empty, new InstanceFactory(registerType, registerImplementation, instance)); - } - - /// - /// Creates/replaces a named container class registration with a specific, strong referenced, instance. - /// - /// Type to register - /// Type of instance to register that implements RegisterType - /// Instance of RegisterImplementation to register - /// Name of registration - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType, Type registerImplementation, object instance, string name) - { - return RegisterInternal(registerType, name, new InstanceFactory(registerType, registerImplementation, instance)); - } - - /// - /// Creates/replaces a container class registration with a user specified factory - /// - /// Type to register - /// Factory/lambda that returns an instance of RegisterType - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType, Func factory) - { - return RegisterInternal(registerType, string.Empty, new DelegateFactory(registerType, factory)); - } - - /// - /// Creates/replaces a container class registration with a user specified factory - /// - /// Type to register - /// Factory/lambda that returns an instance of RegisterType - /// Name of registation - /// RegisterOptions for fluent API - public RegisterOptions Register(Type registerType, Func factory, string name) - { - return RegisterInternal(registerType, name, new DelegateFactory(registerType, factory)); - } - - /// - /// Creates/replaces a container class registration with default options. - /// - /// Type to register - /// RegisterOptions for fluent API - public RegisterOptions Register() - where RegisterType : class - { - return Register(typeof(RegisterType)); - } - - /// - /// Creates/replaces a named container class registration with default options. - /// - /// Type to register - /// Name of registration - /// RegisterOptions for fluent API - public RegisterOptions Register(string name) - where RegisterType : class - { - return Register(typeof(RegisterType), name); - } - - /// - /// Creates/replaces a container class registration with a given implementation and default options. - /// - /// Type to register - /// Type to instantiate that implements RegisterType - /// RegisterOptions for fluent API - public RegisterOptions Register() - where RegisterType : class - where RegisterImplementation : class, RegisterType - { - return Register(typeof(RegisterType), typeof(RegisterImplementation)); - } - - /// - /// Creates/replaces a named container class registration with a given implementation and default options. - /// - /// Type to register - /// Type to instantiate that implements RegisterType - /// Name of registration - /// RegisterOptions for fluent API - public RegisterOptions Register(string name) - where RegisterType : class - where RegisterImplementation : class, RegisterType - { - return Register(typeof(RegisterType), typeof(RegisterImplementation), name); - } - - /// - /// Creates/replaces a container class registration with a specific, strong referenced, instance. - /// - /// Type to register - /// Instance of RegisterType to register - /// RegisterOptions for fluent API - public RegisterOptions Register(RegisterType instance) - where RegisterType : class - { - return Register(typeof(RegisterType), instance); - } - - /// - /// Creates/replaces a named container class registration with a specific, strong referenced, instance. - /// - /// Type to register - /// Instance of RegisterType to register - /// Name of registration - /// RegisterOptions for fluent API - public RegisterOptions Register(RegisterType instance, string name) - where RegisterType : class - { - return Register(typeof(RegisterType), instance, name); - } - - /// - /// Creates/replaces a container class registration with a specific, strong referenced, instance. - /// - /// Type to register - /// Type of instance to register that implements RegisterType - /// Instance of RegisterImplementation to register - /// RegisterOptions for fluent API - public RegisterOptions Register(RegisterImplementation instance) - where RegisterType : class - where RegisterImplementation : class, RegisterType - { - return Register(typeof(RegisterType), typeof(RegisterImplementation), instance); - } - - /// - /// Creates/replaces a named container class registration with a specific, strong referenced, instance. - /// - /// Type to register - /// Type of instance to register that implements RegisterType - /// Instance of RegisterImplementation to register - /// Name of registration - /// RegisterOptions for fluent API - public RegisterOptions Register(RegisterImplementation instance, string name) - where RegisterType : class - where RegisterImplementation : class, RegisterType - { - return Register(typeof(RegisterType), typeof(RegisterImplementation), instance, name); - } - - /// - /// Creates/replaces a container class registration with a user specified factory - /// - /// Type to register - /// Factory/lambda that returns an instance of RegisterType - /// RegisterOptions for fluent API - public RegisterOptions Register(Func factory) - where RegisterType : class - { - if (factory == null) - { - throw new ArgumentNullException("factory"); - } - - return Register(typeof(RegisterType), (c, o) => factory(c, o)); - } - - /// - /// Creates/replaces a named container class registration with a user specified factory - /// - /// Type to register - /// Factory/lambda that returns an instance of RegisterType - /// Name of registation - /// RegisterOptions for fluent API - public RegisterOptions Register(Func factory, string name) - where RegisterType : class - { - if (factory == null) - { - throw new ArgumentNullException("factory"); - } - - return Register(typeof(RegisterType), (c, o) => factory(c, o), name); - } - - /// - /// Register multiple implementations of a type. - /// - /// Internally this registers each implementation using the full name of the class as its registration name. - /// - /// Type that each implementation implements - /// Types that implement RegisterType - /// MultiRegisterOptions for the fluent API - public MultiRegisterOptions RegisterMultiple(IEnumerable implementationTypes) - { - return RegisterMultiple(typeof(RegisterType), implementationTypes); - } - - /// - /// Register multiple implementations of a type. - /// - /// Internally this registers each implementation using the full name of the class as its registration name. - /// - /// Type that each implementation implements - /// Types that implement RegisterType - /// MultiRegisterOptions for the fluent API - public MultiRegisterOptions RegisterMultiple(Type registrationType, IEnumerable implementationTypes) - { - if (implementationTypes == null) - throw new ArgumentNullException("types", "types is null."); - - foreach (var type in implementationTypes) - //#if NETFX_CORE - // if (!registrationType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) - //#else - if (!registrationType.IsAssignableFrom(type)) - //#endif - throw new ArgumentException(string.Format("types: The type {0} is not assignable from {1}", registrationType.FullName, type.FullName)); - - if (implementationTypes.Count() != implementationTypes.Distinct().Count()) - { - var queryForDuplicatedTypes = from i in implementationTypes - group i by i - into j - where j.Count() > 1 - select j.Key.FullName; - - var fullNamesOfDuplicatedTypes = string.Join(",\n", queryForDuplicatedTypes.ToArray()); - var multipleRegMessage = string.Format("types: The same implementation type cannot be specified multiple times for {0}\n\n{1}", registrationType.FullName, fullNamesOfDuplicatedTypes); - throw new ArgumentException(multipleRegMessage); - } - - var registerOptions = new List(); - - foreach (var type in implementationTypes) - { - registerOptions.Add(Register(registrationType, type, type.FullName)); - } - - return new MultiRegisterOptions(registerOptions); - } - #endregion - - #region Resolution - /// - /// Attempts to resolve a type using default options. - /// - /// Type to resolve - /// Instance of type - /// Unable to resolve the type. - public object Resolve(Type resolveType) - { - return ResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, ResolveOptions.Default); - } - - /// - /// Attempts to resolve a type using specified options. - /// - /// Type to resolve - /// Resolution options - /// Instance of type - /// Unable to resolve the type. - public object Resolve(Type resolveType, ResolveOptions options) - { - return ResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, options); - } - - /// - /// Attempts to resolve a type using default options and the supplied name. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// Name of registration - /// Instance of type - /// Unable to resolve the type. - public object Resolve(Type resolveType, string name) - { - return ResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, ResolveOptions.Default); - } - - /// - /// Attempts to resolve a type using supplied options and name. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// Name of registration - /// Resolution options - /// Instance of type - /// Unable to resolve the type. - public object Resolve(Type resolveType, string name, ResolveOptions options) - { - return ResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, options); - } - - /// - /// Attempts to resolve a type using default options and the supplied constructor parameters. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// User specified constructor parameters - /// Instance of type - /// Unable to resolve the type. - public object Resolve(Type resolveType, NamedParameterOverloads parameters) - { - return ResolveInternal(new TypeRegistration(resolveType), parameters, ResolveOptions.Default); - } - - /// - /// Attempts to resolve a type using specified options and the supplied constructor parameters. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// User specified constructor parameters - /// Resolution options - /// Instance of type - /// Unable to resolve the type. - public object Resolve(Type resolveType, NamedParameterOverloads parameters, ResolveOptions options) - { - return ResolveInternal(new TypeRegistration(resolveType), parameters, options); - } - - /// - /// Attempts to resolve a type using default options and the supplied constructor parameters and name. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// User specified constructor parameters - /// Name of registration - /// Instance of type - /// Unable to resolve the type. - public object Resolve(Type resolveType, string name, NamedParameterOverloads parameters) - { - return ResolveInternal(new TypeRegistration(resolveType, name), parameters, ResolveOptions.Default); - } - - /// - /// Attempts to resolve a named type using specified options and the supplied constructor parameters. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// Name of registration - /// User specified constructor parameters - /// Resolution options - /// Instance of type - /// Unable to resolve the type. - public object Resolve(Type resolveType, string name, NamedParameterOverloads parameters, ResolveOptions options) - { - return ResolveInternal(new TypeRegistration(resolveType, name), parameters, options); - } - - /// - /// Attempts to resolve a type using default options. - /// - /// Type to resolve - /// Instance of type - /// Unable to resolve the type. - public ResolveType Resolve() - where ResolveType : class - { - return (ResolveType)Resolve(typeof(ResolveType)); - } - - /// - /// Attempts to resolve a type using specified options. - /// - /// Type to resolve - /// Resolution options - /// Instance of type - /// Unable to resolve the type. - public ResolveType Resolve(ResolveOptions options) - where ResolveType : class - { - return (ResolveType)Resolve(typeof(ResolveType), options); - } - - /// - /// Attempts to resolve a type using default options and the supplied name. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// Name of registration - /// Instance of type - /// Unable to resolve the type. - public ResolveType Resolve(string name) - where ResolveType : class - { - return (ResolveType)Resolve(typeof(ResolveType), name); - } - - /// - /// Attempts to resolve a type using supplied options and name. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// Name of registration - /// Resolution options - /// Instance of type - /// Unable to resolve the type. - public ResolveType Resolve(string name, ResolveOptions options) - where ResolveType : class - { - return (ResolveType)Resolve(typeof(ResolveType), name, options); - } - - /// - /// Attempts to resolve a type using default options and the supplied constructor parameters. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// User specified constructor parameters - /// Instance of type - /// Unable to resolve the type. - public ResolveType Resolve(NamedParameterOverloads parameters) - where ResolveType : class - { - return (ResolveType)Resolve(typeof(ResolveType), parameters); - } - - /// - /// Attempts to resolve a type using specified options and the supplied constructor parameters. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// User specified constructor parameters - /// Resolution options - /// Instance of type - /// Unable to resolve the type. - public ResolveType Resolve(NamedParameterOverloads parameters, ResolveOptions options) - where ResolveType : class - { - return (ResolveType)Resolve(typeof(ResolveType), parameters, options); - } - - /// - /// Attempts to resolve a type using default options and the supplied constructor parameters and name. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// User specified constructor parameters - /// Name of registration - /// Instance of type - /// Unable to resolve the type. - public ResolveType Resolve(string name, NamedParameterOverloads parameters) - where ResolveType : class - { - return (ResolveType)Resolve(typeof(ResolveType), name, parameters); - } - - /// - /// Attempts to resolve a named type using specified options and the supplied constructor parameters. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Type to resolve - /// Name of registration - /// User specified constructor parameters - /// Resolution options - /// Instance of type - /// Unable to resolve the type. - public ResolveType Resolve(string name, NamedParameterOverloads parameters, ResolveOptions options) - where ResolveType : class - { - return (ResolveType)Resolve(typeof(ResolveType), name, parameters, options); - } - - /// - /// Attempts to predict whether a given type can be resolved with default options. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// Bool indicating whether the type can be resolved - public bool CanResolve(Type resolveType) - { - return CanResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, ResolveOptions.Default); - } - - /// - /// Attempts to predict whether a given named type can be resolved with default options. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Bool indicating whether the type can be resolved - private bool CanResolve(Type resolveType, string name) - { - return CanResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, ResolveOptions.Default); - } - - /// - /// Attempts to predict whether a given type can be resolved with the specified options. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// Resolution options - /// Bool indicating whether the type can be resolved - public bool CanResolve(Type resolveType, ResolveOptions options) - { - return CanResolveInternal(new TypeRegistration(resolveType), NamedParameterOverloads.Default, options); - } - - /// - /// Attempts to predict whether a given named type can be resolved with the specified options. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// Resolution options - /// Bool indicating whether the type can be resolved - public bool CanResolve(Type resolveType, string name, ResolveOptions options) - { - return CanResolveInternal(new TypeRegistration(resolveType, name), NamedParameterOverloads.Default, options); - } - - /// - /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters and default options. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// User supplied named parameter overloads - /// Bool indicating whether the type can be resolved - public bool CanResolve(Type resolveType, NamedParameterOverloads parameters) - { - return CanResolveInternal(new TypeRegistration(resolveType), parameters, ResolveOptions.Default); - } - - /// - /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters and default options. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// User supplied named parameter overloads - /// Bool indicating whether the type can be resolved - public bool CanResolve(Type resolveType, string name, NamedParameterOverloads parameters) - { - return CanResolveInternal(new TypeRegistration(resolveType, name), parameters, ResolveOptions.Default); - } - - /// - /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// User supplied named parameter overloads - /// Resolution options - /// Bool indicating whether the type can be resolved - public bool CanResolve(Type resolveType, NamedParameterOverloads parameters, ResolveOptions options) - { - return CanResolveInternal(new TypeRegistration(resolveType), parameters, options); - } - - /// - /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// User supplied named parameter overloads - /// Resolution options - /// Bool indicating whether the type can be resolved - public bool CanResolve(Type resolveType, string name, NamedParameterOverloads parameters, ResolveOptions options) - { - return CanResolveInternal(new TypeRegistration(resolveType, name), parameters, options); - } - - /// - /// Attempts to predict whether a given type can be resolved with default options. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// Bool indicating whether the type can be resolved - public bool CanResolve() - where ResolveType : class - { - return CanResolve(typeof(ResolveType)); - } - - /// - /// Attempts to predict whether a given named type can be resolved with default options. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Bool indicating whether the type can be resolved - public bool CanResolve(string name) - where ResolveType : class - { - return CanResolve(typeof(ResolveType), name); - } - - /// - /// Attempts to predict whether a given type can be resolved with the specified options. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// Resolution options - /// Bool indicating whether the type can be resolved - public bool CanResolve(ResolveOptions options) - where ResolveType : class - { - return CanResolve(typeof(ResolveType), options); - } - - /// - /// Attempts to predict whether a given named type can be resolved with the specified options. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// Resolution options - /// Bool indicating whether the type can be resolved - public bool CanResolve(string name, ResolveOptions options) - where ResolveType : class - { - return CanResolve(typeof(ResolveType), name, options); - } - - /// - /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters and default options. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// User supplied named parameter overloads - /// Bool indicating whether the type can be resolved - public bool CanResolve(NamedParameterOverloads parameters) - where ResolveType : class - { - return CanResolve(typeof(ResolveType), parameters); - } - - /// - /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters and default options. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// User supplied named parameter overloads - /// Bool indicating whether the type can be resolved - public bool CanResolve(string name, NamedParameterOverloads parameters) - where ResolveType : class - { - return CanResolve(typeof(ResolveType), name, parameters); - } - - /// - /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// User supplied named parameter overloads - /// Resolution options - /// Bool indicating whether the type can be resolved - public bool CanResolve(NamedParameterOverloads parameters, ResolveOptions options) - where ResolveType : class - { - return CanResolve(typeof(ResolveType), parameters, options); - } - - /// - /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options. - /// - /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists). - /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail. - /// - /// Note: Resolution may still fail if user defined factory registations fail to construct objects when called. - /// - /// Type to resolve - /// Name of registration - /// User supplied named parameter overloads - /// Resolution options - /// Bool indicating whether the type can be resolved - public bool CanResolve(string name, NamedParameterOverloads parameters, ResolveOptions options) - where ResolveType : class - { - return CanResolve(typeof(ResolveType), name, parameters, options); - } - - /// - /// Attemps to resolve a type using the default options - /// - /// Type to resolve - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(Type resolveType, out object resolvedType) - { - try - { - resolvedType = Resolve(resolveType); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = null; - return false; - } - } - - /// - /// Attemps to resolve a type using the given options - /// - /// Type to resolve - /// Resolution options - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(Type resolveType, ResolveOptions options, out object resolvedType) - { - try - { - resolvedType = Resolve(resolveType, options); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = null; - return false; - } - } - - /// - /// Attemps to resolve a type using the default options and given name - /// - /// Type to resolve - /// Name of registration - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(Type resolveType, string name, out object resolvedType) - { - try - { - resolvedType = Resolve(resolveType, name); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = null; - return false; - } - } - - /// - /// Attemps to resolve a type using the given options and name - /// - /// Type to resolve - /// Name of registration - /// Resolution options - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(Type resolveType, string name, ResolveOptions options, out object resolvedType) - { - try - { - resolvedType = Resolve(resolveType, name, options); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = null; - return false; - } - } - - /// - /// Attemps to resolve a type using the default options and supplied constructor parameters - /// - /// Type to resolve - /// User specified constructor parameters - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(Type resolveType, NamedParameterOverloads parameters, out object resolvedType) - { - try - { - resolvedType = Resolve(resolveType, parameters); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = null; - return false; - } - } - - /// - /// Attemps to resolve a type using the default options and supplied name and constructor parameters - /// - /// Type to resolve - /// Name of registration - /// User specified constructor parameters - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(Type resolveType, string name, NamedParameterOverloads parameters, out object resolvedType) - { - try - { - resolvedType = Resolve(resolveType, name, parameters); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = null; - return false; - } - } - - /// - /// Attemps to resolve a type using the supplied options and constructor parameters - /// - /// Type to resolve - /// Name of registration - /// User specified constructor parameters - /// Resolution options - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(Type resolveType, NamedParameterOverloads parameters, ResolveOptions options, out object resolvedType) - { - try - { - resolvedType = Resolve(resolveType, parameters, options); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = null; - return false; - } - } - - /// - /// Attemps to resolve a type using the supplied name, options and constructor parameters - /// - /// Type to resolve - /// Name of registration - /// User specified constructor parameters - /// Resolution options - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(Type resolveType, string name, NamedParameterOverloads parameters, ResolveOptions options, out object resolvedType) - { - try - { - resolvedType = Resolve(resolveType, name, parameters, options); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = null; - return false; - } - } - - /// - /// Attemps to resolve a type using the default options - /// - /// Type to resolve - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(out ResolveType resolvedType) - where ResolveType : class - { - try - { - resolvedType = Resolve(); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = default(ResolveType); - return false; - } - } - - /// - /// Attemps to resolve a type using the given options - /// - /// Type to resolve - /// Resolution options - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(ResolveOptions options, out ResolveType resolvedType) - where ResolveType : class - { - try - { - resolvedType = Resolve(options); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = default(ResolveType); - return false; - } - } - - /// - /// Attemps to resolve a type using the default options and given name - /// - /// Type to resolve - /// Name of registration - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(string name, out ResolveType resolvedType) - where ResolveType : class - { - try - { - resolvedType = Resolve(name); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = default(ResolveType); - return false; - } - } - - /// - /// Attemps to resolve a type using the given options and name - /// - /// Type to resolve - /// Name of registration - /// Resolution options - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(string name, ResolveOptions options, out ResolveType resolvedType) - where ResolveType : class - { - try - { - resolvedType = Resolve(name, options); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = default(ResolveType); - return false; - } - } - - /// - /// Attemps to resolve a type using the default options and supplied constructor parameters - /// - /// Type to resolve - /// User specified constructor parameters - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(NamedParameterOverloads parameters, out ResolveType resolvedType) - where ResolveType : class - { - try - { - resolvedType = Resolve(parameters); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = default(ResolveType); - return false; - } - } - - /// - /// Attemps to resolve a type using the default options and supplied name and constructor parameters - /// - /// Type to resolve - /// Name of registration - /// User specified constructor parameters - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(string name, NamedParameterOverloads parameters, out ResolveType resolvedType) - where ResolveType : class - { - try - { - resolvedType = Resolve(name, parameters); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = default(ResolveType); - return false; - } - } - - /// - /// Attemps to resolve a type using the supplied options and constructor parameters - /// - /// Type to resolve - /// Name of registration - /// User specified constructor parameters - /// Resolution options - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(NamedParameterOverloads parameters, ResolveOptions options, out ResolveType resolvedType) - where ResolveType : class - { - try - { - resolvedType = Resolve(parameters, options); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = default(ResolveType); - return false; - } - } - - /// - /// Attemps to resolve a type using the supplied name, options and constructor parameters - /// - /// Type to resolve - /// Name of registration - /// User specified constructor parameters - /// Resolution options - /// Resolved type or default if resolve fails - /// True if resolved sucessfully, false otherwise - public bool TryResolve(string name, NamedParameterOverloads parameters, ResolveOptions options, out ResolveType resolvedType) - where ResolveType : class - { - try - { - resolvedType = Resolve(name, parameters, options); - return true; - } - catch (TinyIoCResolutionException) - { - resolvedType = default(ResolveType); - return false; - } - } - - /// - /// Returns all registrations of a type - /// - /// Type to resolveAll - /// Whether to include un-named (default) registrations - /// IEnumerable - public IEnumerable ResolveAll(Type resolveType, bool includeUnnamed) - { - return ResolveAllInternal(resolveType, includeUnnamed); - } - - /// - /// Returns all registrations of a type, both named and unnamed - /// - /// Type to resolveAll - /// IEnumerable - public IEnumerable ResolveAll(Type resolveType) - { - return ResolveAll(resolveType, false); - } - - /// - /// Returns all registrations of a type - /// - /// Type to resolveAll - /// Whether to include un-named (default) registrations - /// IEnumerable - public IEnumerable ResolveAll(bool includeUnnamed) - where ResolveType : class - { - return ResolveAll(typeof(ResolveType), includeUnnamed).Cast(); - } - - /// - /// Returns all registrations of a type, both named and unnamed - /// - /// Type to resolveAll - /// Whether to include un-named (default) registrations - /// IEnumerable - public IEnumerable ResolveAll() - where ResolveType : class - { - return ResolveAll(true); - } - - /// - /// Attempts to resolve all public property dependencies on the given object. - /// - /// Object to "build up" - public void BuildUp(object input) - { - BuildUpInternal(input, ResolveOptions.Default); - } - - /// - /// Attempts to resolve all public property dependencies on the given object using the given resolve options. - /// - /// Object to "build up" - /// Resolve options to use - public void BuildUp(object input, ResolveOptions resolveOptions) - { - BuildUpInternal(input, resolveOptions); - } - #endregion - #endregion - - #region Object Factories - /// - /// Provides custom lifetime management for ASP.Net per-request lifetimes etc. - /// - public interface ITinyIoCObjectLifetimeProvider - { - /// - /// Gets the stored object if it exists, or null if not - /// - /// Object instance or null - object GetObject(); - - /// - /// Store the object - /// - /// Object to store - void SetObject(object value); - - /// - /// Release the object - /// - void ReleaseObject(); - } - - private abstract class ObjectFactoryBase - { - /// - /// Whether to assume this factory sucessfully constructs its objects - /// - /// Generally set to true for delegate style factories as CanResolve cannot delve - /// into the delegates they contain. - /// - public virtual bool AssumeConstruction => false; - - /// - /// The type the factory instantiates - /// - public abstract Type CreatesType { get; } - - /// - /// Constructor to use, if specified - /// - public ConstructorInfo Constructor { get; protected set; } - - /// - /// Create the type - /// - /// Type user requested to be resolved - /// Container that requested the creation - /// Any user parameters passed - /// - /// - public abstract object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options); - - public virtual ObjectFactoryBase SingletonVariant - { - get - { - throw new TinyIoCRegistrationException(GetType(), "singleton"); - } - } - - public virtual ObjectFactoryBase MultiInstanceVariant - { - get - { - throw new TinyIoCRegistrationException(GetType(), "multi-instance"); - } - } - - public virtual ObjectFactoryBase StrongReferenceVariant - { - get - { - throw new TinyIoCRegistrationException(GetType(), "strong reference"); - } - } - - public virtual ObjectFactoryBase WeakReferenceVariant - { - get - { - throw new TinyIoCRegistrationException(GetType(), "weak reference"); - } - } - - public virtual ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) - { - throw new TinyIoCRegistrationException(GetType(), errorString); - } - - public virtual void SetConstructor(ConstructorInfo constructor) - { - Constructor = constructor; - } - - public virtual ObjectFactoryBase GetFactoryForChildContainer(Type type, TinyIoCContainer parent, TinyIoCContainer child) - { - return this; - } - } - - /// - /// IObjectFactory that creates new instances of types for each resolution - /// - private class MultiInstanceFactory : ObjectFactoryBase - { - private readonly Type registerType; - private readonly Type registerImplementation; - public override Type CreatesType => registerImplementation; - - public MultiInstanceFactory(Type registerType, Type registerImplementation) - { - //#if NETFX_CORE - // if (registerImplementation.GetTypeInfo().IsAbstract() || registerImplementation.GetTypeInfo().IsInterface()) - // throw new TinyIoCRegistrationTypeException(registerImplementation, "MultiInstanceFactory"); - //#else - if (registerImplementation.IsAbstract() || registerImplementation.IsInterface()) - throw new TinyIoCRegistrationTypeException(registerImplementation, "MultiInstanceFactory"); - //#endif - if (!IsValidAssignment(registerType, registerImplementation)) - throw new TinyIoCRegistrationTypeException(registerImplementation, "MultiInstanceFactory"); - - this.registerType = registerType; - this.registerImplementation = registerImplementation; - } - - public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) - { - try - { - return container.ConstructType(requestedType, registerImplementation, Constructor, parameters, options); - } - catch (TinyIoCResolutionException ex) - { - throw new TinyIoCResolutionException(registerType, ex); - } - } - - public override ObjectFactoryBase SingletonVariant => new SingletonFactory(registerType, registerImplementation); - - public override ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) - { - return new CustomObjectLifetimeFactory(registerType, registerImplementation, lifetimeProvider, errorString); - } - - public override ObjectFactoryBase MultiInstanceVariant => this; - } - - /// - /// IObjectFactory that invokes a specified delegate to construct the object - /// - private class DelegateFactory : ObjectFactoryBase - { - private readonly Type registerType; - - private Func _factory; - - public override bool AssumeConstruction => true; - - public override Type CreatesType => registerType; - - public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) - { - try - { - return _factory.Invoke(container, parameters); - } - catch (Exception ex) - { - throw new TinyIoCResolutionException(registerType, ex); - } - } - - public DelegateFactory(Type registerType, Func factory) - { - if (factory == null) - throw new ArgumentNullException("factory"); - - _factory = factory; - - this.registerType = registerType; - } - - public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(registerType, _factory); - - public override ObjectFactoryBase StrongReferenceVariant => this; - - public override void SetConstructor(ConstructorInfo constructor) - { - throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for delegate factory registrations"); - } - } - - /// - /// IObjectFactory that invokes a specified delegate to construct the object - /// Holds the delegate using a weak reference - /// - private class WeakDelegateFactory : ObjectFactoryBase - { - private readonly Type registerType; - - private WeakReference _factory; - - public override bool AssumeConstruction => true; - - public override Type CreatesType => registerType; - - public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) - { - var factory = _factory.Target as Func; - - if (factory == null) - throw new TinyIoCWeakReferenceException(registerType); - - try - { - return factory.Invoke(container, parameters); - } - catch (Exception ex) - { - throw new TinyIoCResolutionException(registerType, ex); - } - } - - public WeakDelegateFactory(Type registerType, Func factory) - { - if (factory == null) - throw new ArgumentNullException("factory"); - - _factory = new WeakReference(factory); - - this.registerType = registerType; - } - - public override ObjectFactoryBase StrongReferenceVariant - { - get - { - var factory = _factory.Target as Func; - - if (factory == null) - throw new TinyIoCWeakReferenceException(registerType); - - return new DelegateFactory(registerType, factory); - } - } - - public override ObjectFactoryBase WeakReferenceVariant => this; - - public override void SetConstructor(ConstructorInfo constructor) - { - throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for delegate factory registrations"); - } - } - - /// - /// Stores an particular instance to return for a type - /// - private class InstanceFactory : ObjectFactoryBase, IDisposable - { - private readonly Type registerType; - private readonly Type registerImplementation; - private object _instance; - - public override bool AssumeConstruction => true; - - public InstanceFactory(Type registerType, Type registerImplementation, object instance) - { - if (!IsValidAssignment(registerType, registerImplementation)) - throw new TinyIoCRegistrationTypeException(registerImplementation, "InstanceFactory"); - - this.registerType = registerType; - this.registerImplementation = registerImplementation; - _instance = instance; - } - - public override Type CreatesType => registerImplementation; - - public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) - { - return _instance; - } - - public override ObjectFactoryBase MultiInstanceVariant => new MultiInstanceFactory(registerType, registerImplementation); - - public override ObjectFactoryBase WeakReferenceVariant => new WeakInstanceFactory(registerType, registerImplementation, _instance); - - public override ObjectFactoryBase StrongReferenceVariant => this; - - public override void SetConstructor(ConstructorInfo constructor) - { - throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for instance factory registrations"); - } - - public void Dispose() - { - var disposable = _instance as IDisposable; - - if (disposable != null) - disposable.Dispose(); - } - } - - /// - /// Stores an particular instance to return for a type - /// - /// Stores the instance with a weak reference - /// - private class WeakInstanceFactory : ObjectFactoryBase, IDisposable - { - private readonly Type registerType; - private readonly Type registerImplementation; - private readonly WeakReference _instance; - - public WeakInstanceFactory(Type registerType, Type registerImplementation, object instance) - { - if (!IsValidAssignment(registerType, registerImplementation)) - throw new TinyIoCRegistrationTypeException(registerImplementation, "WeakInstanceFactory"); - - this.registerType = registerType; - this.registerImplementation = registerImplementation; - _instance = new WeakReference(instance); - } - - public override Type CreatesType => registerImplementation; - - public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) - { - var instance = _instance.Target; - - if (instance == null) - throw new TinyIoCWeakReferenceException(registerType); - - return instance; - } - - public override ObjectFactoryBase MultiInstanceVariant => new MultiInstanceFactory(registerType, registerImplementation); - - public override ObjectFactoryBase WeakReferenceVariant => this; - - public override ObjectFactoryBase StrongReferenceVariant - { - get - { - var instance = _instance.Target; - - if (instance == null) - throw new TinyIoCWeakReferenceException(registerType); - - return new InstanceFactory(registerType, registerImplementation, instance); - } - } - - public override void SetConstructor(ConstructorInfo constructor) - { - throw new TinyIoCConstructorResolutionException("Constructor selection is not possible for instance factory registrations"); - } - - public void Dispose() - { - var disposable = _instance.Target as IDisposable; - - if (disposable != null) - disposable.Dispose(); - } - } - - /// - /// A factory that lazy instantiates a type and always returns the same instance - /// - private class SingletonFactory : ObjectFactoryBase, IDisposable - { - private readonly Type registerType; - private readonly Type registerImplementation; - private readonly object SingletonLock = new object(); - private object _Current; - - public SingletonFactory(Type registerType, Type registerImplementation) - { - //#if NETFX_CORE - // if (registerImplementation.GetTypeInfo().IsAbstract() || registerImplementation.GetTypeInfo().IsInterface()) - //#else - if (registerImplementation.IsAbstract() || registerImplementation.IsInterface()) - //#endif - throw new TinyIoCRegistrationTypeException(registerImplementation, "SingletonFactory"); - - if (!IsValidAssignment(registerType, registerImplementation)) - throw new TinyIoCRegistrationTypeException(registerImplementation, "SingletonFactory"); - - this.registerType = registerType; - this.registerImplementation = registerImplementation; - } - - public override Type CreatesType => registerImplementation; - - public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) - { - if (parameters.Count != 0) - throw new ArgumentException("Cannot specify parameters for singleton types"); - - lock (SingletonLock) - if (_Current == null) - _Current = container.ConstructType(requestedType, registerImplementation, Constructor, options); - - return _Current; - } - - public override ObjectFactoryBase SingletonVariant => this; - - public override ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) - { - return new CustomObjectLifetimeFactory(registerType, registerImplementation, lifetimeProvider, errorString); - } - - public override ObjectFactoryBase MultiInstanceVariant => new MultiInstanceFactory(registerType, registerImplementation); - - public override ObjectFactoryBase GetFactoryForChildContainer(Type type, TinyIoCContainer parent, TinyIoCContainer child) - { - // We make sure that the singleton is constructed before the child container takes the factory. - // Otherwise the results would vary depending on whether or not the parent container had resolved - // the type before the child container does. - GetObject(type, parent, NamedParameterOverloads.Default, ResolveOptions.Default); - return this; - } - - public void Dispose() - { - if (_Current == null) - return; - - var disposable = _Current as IDisposable; - - if (disposable != null) - disposable.Dispose(); - } - } - - /// - /// A factory that offloads lifetime to an external lifetime provider - /// - private class CustomObjectLifetimeFactory : ObjectFactoryBase, IDisposable - { - private readonly object SingletonLock = new object(); - private readonly Type registerType; - private readonly Type registerImplementation; - private readonly ITinyIoCObjectLifetimeProvider _LifetimeProvider; - - public CustomObjectLifetimeFactory(Type registerType, Type registerImplementation, ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorMessage) - { - if (lifetimeProvider == null) - throw new ArgumentNullException("lifetimeProvider", "lifetimeProvider is null."); - - if (!IsValidAssignment(registerType, registerImplementation)) - throw new TinyIoCRegistrationTypeException(registerImplementation, "SingletonFactory"); - - //#if NETFX_CORE - // if (registerImplementation.GetTypeInfo().IsAbstract() || registerImplementation.GetTypeInfo().IsInterface()) - //#else - if (registerImplementation.IsAbstract() || registerImplementation.IsInterface()) - //#endif - throw new TinyIoCRegistrationTypeException(registerImplementation, errorMessage); - - this.registerType = registerType; - this.registerImplementation = registerImplementation; - _LifetimeProvider = lifetimeProvider; - } - - public override Type CreatesType => registerImplementation; - - public override object GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options) - { - object current; - - lock (SingletonLock) - { - current = _LifetimeProvider.GetObject(); - if (current == null) - { - current = container.ConstructType(requestedType, registerImplementation, Constructor, options); - _LifetimeProvider.SetObject(current); - } - } - - return current; - } - - public override ObjectFactoryBase SingletonVariant - { - get - { - _LifetimeProvider.ReleaseObject(); - return new SingletonFactory(registerType, registerImplementation); - } - } - - public override ObjectFactoryBase MultiInstanceVariant - { - get - { - _LifetimeProvider.ReleaseObject(); - return new MultiInstanceFactory(registerType, registerImplementation); - } - } - - public override ObjectFactoryBase GetCustomObjectLifetimeVariant(ITinyIoCObjectLifetimeProvider lifetimeProvider, string errorString) - { - _LifetimeProvider.ReleaseObject(); - return new CustomObjectLifetimeFactory(registerType, registerImplementation, lifetimeProvider, errorString); - } - - public override ObjectFactoryBase GetFactoryForChildContainer(Type type, TinyIoCContainer parent, TinyIoCContainer child) - { - // We make sure that the singleton is constructed before the child container takes the factory. - // Otherwise the results would vary depending on whether or not the parent container had resolved - // the type before the child container does. - GetObject(type, parent, NamedParameterOverloads.Default, ResolveOptions.Default); - return this; - } - - public void Dispose() - { - _LifetimeProvider.ReleaseObject(); - } - } - #endregion - - #region Singleton Container - private static readonly TinyIoCContainer _Current = new TinyIoCContainer(); - - static TinyIoCContainer() - { - } - - /// - /// Lazy created Singleton instance of the container for simple scenarios - /// - public static TinyIoCContainer Current => _Current; - - #endregion - - #region Type Registrations - public sealed class TypeRegistration - { - private int _hashCode; - - public Type Type { get; private set; } - public string Name { get; private set; } - - public TypeRegistration(Type type) - : this(type, string.Empty) - { - } - - public TypeRegistration(Type type, string name) - { - Type = type; - Name = name; - - _hashCode = string.Concat(Type.FullName, "|", Name).GetHashCode(); - } - - public override bool Equals(object obj) - { - var typeRegistration = obj as TypeRegistration; - - if (typeRegistration == null) - return false; - - if (Type != typeRegistration.Type) - return false; - - if (string.Compare(Name, typeRegistration.Name, StringComparison.Ordinal) != 0) - return false; - - return true; - } - - public override int GetHashCode() - { - return _hashCode; - } - } - - private readonly SafeDictionary _RegisteredTypes; -#if USE_OBJECT_CONSTRUCTOR - private delegate object ObjectConstructor(params object[] parameters); - private static readonly SafeDictionary _ObjectConstructorCache = new SafeDictionary(); -#endif - #endregion - - #region Constructors - public TinyIoCContainer() - { - _RegisteredTypes = new SafeDictionary(); - - RegisterDefaultTypes(); - } - - private TinyIoCContainer _Parent; - private TinyIoCContainer(TinyIoCContainer parent) - : this() - { - _Parent = parent; - } - #endregion - - #region Internal Methods - private readonly object _AutoRegisterLock = new object(); - private void AutoRegisterInternal(IEnumerable assemblies, DuplicateImplementationActions duplicateAction, Func registrationPredicate) - { - lock (_AutoRegisterLock) - { - var types = assemblies.SelectMany(a => a.SafeGetTypes()).Where(t => !IsIgnoredType(t, registrationPredicate)).ToList(); - - var concreteTypes = from type in types - where type.IsClass() && (type.IsAbstract() == false) && (type != GetType() && (type.DeclaringType != GetType()) && (!type.IsGenericTypeDefinition())) - select type; - - foreach (var type in concreteTypes) - { - try - { - RegisterInternal(type, string.Empty, GetDefaultObjectFactory(type, type)); - } - catch (MethodAccessException) - { - // Ignore methods we can't access - added for Silverlight - } - } - - var abstractInterfaceTypes = from type in types - where ((type.IsInterface() || type.IsAbstract()) && (type.DeclaringType != GetType()) && (!type.IsGenericTypeDefinition())) - select type; - - foreach (var type in abstractInterfaceTypes) - { - var localType = type; - var implementations = from implementationType in concreteTypes - where localType.IsAssignableFrom(implementationType) - select implementationType; - - if (implementations.Count() > 1) - { - if (duplicateAction == DuplicateImplementationActions.Fail) - throw new TinyIoCAutoRegistrationException(type, implementations); - - if (duplicateAction == DuplicateImplementationActions.RegisterMultiple) - { - RegisterMultiple(type, implementations); - } - } - - var firstImplementation = implementations.FirstOrDefault(); - if (firstImplementation != null) - { - try - { - RegisterInternal(type, string.Empty, GetDefaultObjectFactory(type, firstImplementation)); - } - catch (MethodAccessException) - { - // Ignore methods we can't access - added for Silverlight - } - } - } - } - } - - private bool IsIgnoredAssembly(Assembly assembly) - { - // TODO - find a better way to remove "system" assemblies from the auto registration - var ignoreChecks = new List>() - { - asm => asm.FullName.StartsWith("Microsoft.", StringComparison.Ordinal), - asm => asm.FullName.StartsWith("System.", StringComparison.Ordinal), - asm => asm.FullName.StartsWith("System,", StringComparison.Ordinal), - asm => asm.FullName.StartsWith("CR_ExtUnitTest", StringComparison.Ordinal), - asm => asm.FullName.StartsWith("mscorlib,", StringComparison.Ordinal), - asm => asm.FullName.StartsWith("CR_VSTest", StringComparison.Ordinal), - asm => asm.FullName.StartsWith("DevExpress.CodeRush", StringComparison.Ordinal), - }; - - foreach (var check in ignoreChecks) - { - if (check(assembly)) - return true; - } - - return false; - } - - private bool IsIgnoredType(Type type, Func registrationPredicate) - { - // TODO - find a better way to remove "system" types from the auto registration - var ignoreChecks = new List>() - { - t => t.FullName.StartsWith("System.", StringComparison.Ordinal), - t => t.FullName.StartsWith("Microsoft.", StringComparison.Ordinal), - t => t.IsPrimitive(), -#if !UNBOUND_GENERICS_GETCONSTRUCTORS - t => t.IsGenericTypeDefinition(), -#endif - t => (t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0) && !(t.IsInterface() || t.IsAbstract()), - }; - - if (registrationPredicate != null) - { - ignoreChecks.Add(t => !registrationPredicate(t)); - } - - foreach (var check in ignoreChecks) - { - if (check(type)) - return true; - } - - return false; - } - - private void RegisterDefaultTypes() - { - Register(this); - -#if TINYMESSENGER - // Only register the TinyMessenger singleton if we are the root container - if (_Parent == null) - Register(); -#endif - } - - private ObjectFactoryBase GetCurrentFactory(TypeRegistration registration) - { - ObjectFactoryBase current = null; - - _RegisteredTypes.TryGetValue(registration, out current); - - return current; - } - - private RegisterOptions RegisterInternal(Type registerType, string name, ObjectFactoryBase factory) - { - var typeRegistration = new TypeRegistration(registerType, name); - - return AddUpdateRegistration(typeRegistration, factory); - } - - private RegisterOptions AddUpdateRegistration(TypeRegistration typeRegistration, ObjectFactoryBase factory) - { - _RegisteredTypes[typeRegistration] = factory; - - return new RegisterOptions(this, typeRegistration); - } - - private void RemoveRegistration(TypeRegistration typeRegistration) - { - _RegisteredTypes.Remove(typeRegistration); - } - - private ObjectFactoryBase GetDefaultObjectFactory(Type registerType, Type registerImplementation) - { - //#if NETFX_CORE - // if (registerType.GetTypeInfo().IsInterface() || registerType.GetTypeInfo().IsAbstract()) - //#else - if (registerType.IsInterface() || registerType.IsAbstract()) - //#endif - return new SingletonFactory(registerType, registerImplementation); - - return new MultiInstanceFactory(registerType, registerImplementation); - } - - private bool CanResolveInternal(TypeRegistration registration, NamedParameterOverloads parameters, ResolveOptions options) - { - if (parameters == null) - throw new ArgumentNullException("parameters"); - - Type checkType = registration.Type; - string name = registration.Name; - - ObjectFactoryBase factory; - if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType, name), out factory)) - { - if (factory.AssumeConstruction) - return true; - - if (factory.Constructor == null) - return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; - else - return CanConstruct(factory.Constructor, parameters, options); - } - - // Fail if requesting named resolution and settings set to fail if unresolved - // Or bubble up if we have a parent - if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) - return (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; - - // Attemped unnamed fallback container resolution if relevant and requested - if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) - { - if (_RegisteredTypes.TryGetValue(new TypeRegistration(checkType), out factory)) - { - if (factory.AssumeConstruction) - return true; - - return (GetBestConstructor(factory.CreatesType, parameters, options) != null) ? true : false; - } - } - - // Check if type is an automatic lazy factory request - if (IsAutomaticLazyFactoryRequest(checkType)) - return true; - - // Check if type is an IEnumerable - if (IsIEnumerableRequest(registration.Type)) - return true; - - // Attempt unregistered construction if possible and requested - // If we cant', bubble if we have a parent - if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (checkType.IsGenericType() && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) - return (GetBestConstructor(checkType, parameters, options) != null) ? true : (_Parent != null) ? _Parent.CanResolveInternal(registration, parameters, options) : false; - - // Bubble resolution up the container tree if we have a parent - if (_Parent != null) - return _Parent.CanResolveInternal(registration, parameters, options); - - return false; - } - - private bool IsIEnumerableRequest(Type type) - { - if (!type.IsGenericType()) - return false; - - Type genericType = type.GetGenericTypeDefinition(); - - if (genericType == typeof(IEnumerable<>)) - return true; - - return false; - } - - private bool IsAutomaticLazyFactoryRequest(Type type) - { - if (!type.IsGenericType()) - return false; - - Type genericType = type.GetGenericTypeDefinition(); - - // Just a func - if (genericType == typeof(Func<>)) - return true; - - // 2 parameter func with string as first parameter (name) - //#if NETFX_CORE - // if ((genericType == typeof(Func<,>) && type.GetTypeInfo().GenericTypeArguments[0] == typeof(string))) - //#else - if ((genericType == typeof(Func<,>) && type.GetGenericArguments()[0] == typeof(string))) - //#endif - return true; - - // 3 parameter func with string as first parameter (name) and IDictionary as second (parameters) - //#if NETFX_CORE - // if ((genericType == typeof(Func<,,>) && type.GetTypeInfo().GenericTypeArguments[0] == typeof(string) && type.GetTypeInfo().GenericTypeArguments[1] == typeof(IDictionary))) - //#else - if ((genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) && type.GetGenericArguments()[1] == typeof(IDictionary))) - //#endif - return true; - - return false; - } - - private ObjectFactoryBase GetParentObjectFactory(TypeRegistration registration) - { - if (_Parent == null) - return null; - - ObjectFactoryBase factory; - if (_Parent._RegisteredTypes.TryGetValue(registration, out factory)) - { - return factory.GetFactoryForChildContainer(registration.Type, _Parent, this); - } - - return _Parent.GetParentObjectFactory(registration); - } - - private object ResolveInternal(TypeRegistration registration, NamedParameterOverloads parameters, ResolveOptions options) - { - ObjectFactoryBase factory; - - // Attempt container resolution - if (_RegisteredTypes.TryGetValue(registration, out factory)) - { - try - { - return factory.GetObject(registration.Type, this, parameters, options); - } - catch (TinyIoCResolutionException) - { - throw; - } - catch (Exception ex) - { - throw new TinyIoCResolutionException(registration.Type, ex); - } - } - -#if RESOLVE_OPEN_GENERICS - // Attempt container resolution of open generic - if (registration.Type.IsGenericType()) - { - var openTypeRegistration = new TypeRegistration(registration.Type.GetGenericTypeDefinition(), - registration.Name); - - if (_RegisteredTypes.TryGetValue(openTypeRegistration, out factory)) - { - try - { - return factory.GetObject(registration.Type, this, parameters, options); - } - catch (TinyIoCResolutionException) - { - throw; - } - catch (Exception ex) - { - throw new TinyIoCResolutionException(registration.Type, ex); - } - } - } -#endif - - // Attempt to get a factory from parent if we can - var bubbledObjectFactory = GetParentObjectFactory(registration); - if (bubbledObjectFactory != null) - { - try - { - return bubbledObjectFactory.GetObject(registration.Type, this, parameters, options); - } - catch (TinyIoCResolutionException) - { - throw; - } - catch (Exception ex) - { - throw new TinyIoCResolutionException(registration.Type, ex); - } - } - - // Fail if requesting named resolution and settings set to fail if unresolved - if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.Fail) - throw new TinyIoCResolutionException(registration.Type); - - // Attemped unnamed fallback container resolution if relevant and requested - if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == NamedResolutionFailureActions.AttemptUnnamedResolution) - { - if (_RegisteredTypes.TryGetValue(new TypeRegistration(registration.Type, string.Empty), out factory)) - { - try - { - return factory.GetObject(registration.Type, this, parameters, options); - } - catch (TinyIoCResolutionException) - { - throw; - } - catch (Exception ex) - { - throw new TinyIoCResolutionException(registration.Type, ex); - } - } - } - -#if EXPRESSIONS - // Attempt to construct an automatic lazy factory if possible - if (IsAutomaticLazyFactoryRequest(registration.Type)) - return GetLazyAutomaticFactoryRequest(registration.Type); -#endif - if (IsIEnumerableRequest(registration.Type)) - return GetIEnumerableRequest(registration.Type); - - // Attempt unregistered construction if possible and requested - if ((options.UnregisteredResolutionAction == UnregisteredResolutionActions.AttemptResolve) || (registration.Type.IsGenericType() && options.UnregisteredResolutionAction == UnregisteredResolutionActions.GenericsOnly)) - { - if (!registration.Type.IsAbstract() && !registration.Type.IsInterface()) - return ConstructType(null, registration.Type, parameters, options); - } - - // Unable to resolve - throw - throw new TinyIoCResolutionException(registration.Type); - } - -#if EXPRESSIONS - private object GetLazyAutomaticFactoryRequest(Type type) - { - if (!type.IsGenericType()) - return null; - - Type genericType = type.GetGenericTypeDefinition(); - //#if NETFX_CORE - // Type[] genericArguments = type.GetTypeInfo().GenericTypeArguments.ToArray(); - //#else - Type[] genericArguments = type.GetGenericArguments(); - //#endif - - // Just a func - if (genericType == typeof(Func<>)) - { - Type returnType = genericArguments[0]; - - //#if NETFX_CORE - // MethodInfo resolveMethod = typeof(TinyIoCContainer).GetTypeInfo().GetDeclaredMethods("Resolve").First(mi => !mi.GetParameters().Any()); - //#else - MethodInfo resolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new Type[] { }); - //#endif - resolveMethod = resolveMethod.MakeGenericMethod(returnType); - - var resolveCall = Expression.Call(Expression.Constant(this), resolveMethod); - - var resolveLambda = Expression.Lambda(resolveCall).Compile(); - - return resolveLambda; - } - - // 2 parameter func with string as first parameter (name) - if ((genericType == typeof(Func<,>)) && (genericArguments[0] == typeof(string))) - { - Type returnType = genericArguments[1]; - - //#if NETFX_CORE - // MethodInfo resolveMethod = typeof(TinyIoCContainer).GetTypeInfo().GetDeclaredMethods("Resolve").First(mi => mi.GetParameters().Length == 1 && mi.GetParameters()[0].GetType() == typeof(String)); - //#else - MethodInfo resolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new Type[] { typeof(string) }); - //#endif - resolveMethod = resolveMethod.MakeGenericMethod(returnType); - - ParameterExpression[] resolveParameters = new ParameterExpression[] { Expression.Parameter(typeof(string), "name") }; - var resolveCall = Expression.Call(Expression.Constant(this), resolveMethod, resolveParameters); - - var resolveLambda = Expression.Lambda(resolveCall, resolveParameters).Compile(); - - return resolveLambda; - } - - // 3 parameter func with string as first parameter (name) and IDictionary as second (parameters) - //#if NETFX_CORE - // if ((genericType == typeof(Func<,,>) && type.GenericTypeArguments[0] == typeof(string) && type.GenericTypeArguments[1] == typeof(IDictionary))) - //#else - if ((genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) && type.GetGenericArguments()[1] == typeof(IDictionary))) - //#endif - { - Type returnType = genericArguments[2]; - - var name = Expression.Parameter(typeof(string), "name"); - var parameters = Expression.Parameter(typeof(IDictionary), "parameters"); - - //#if NETFX_CORE - // MethodInfo resolveMethod = typeof(TinyIoCContainer).GetTypeInfo().GetDeclaredMethods("Resolve").First(mi => mi.GetParameters().Length == 2 && mi.GetParameters()[0].GetType() == typeof(String) && mi.GetParameters()[1].GetType() == typeof(NamedParameterOverloads)); - //#else - MethodInfo resolveMethod = typeof(TinyIoCContainer).GetMethod("Resolve", new Type[] { typeof(string), typeof(NamedParameterOverloads) }); - //#endif - resolveMethod = resolveMethod.MakeGenericMethod(returnType); - - var resolveCall = Expression.Call(Expression.Constant(this), resolveMethod, name, Expression.Call(typeof(NamedParameterOverloads), "FromIDictionary", null, parameters)); - - var resolveLambda = Expression.Lambda(resolveCall, name, parameters).Compile(); - - return resolveLambda; - } - - throw new TinyIoCResolutionException(type); - } -#endif - private object GetIEnumerableRequest(Type type) - { - //#if NETFX_CORE - // var genericResolveAllMethod = this.GetType().GetGenericMethod("ResolveAll", type.GenericTypeArguments, new[] { typeof(bool) }); - //#else - var genericResolveAllMethod = GetType().GetGenericMethod(BindingFlags.Public | BindingFlags.Instance, "ResolveAll", type.GetGenericArguments(), new[] { typeof(bool) }); - //#endif - return genericResolveAllMethod.Invoke(this, new object[] { false }); - } - - private bool CanConstruct(ConstructorInfo ctor, NamedParameterOverloads parameters, ResolveOptions options) - { - if (parameters == null) - throw new ArgumentNullException("parameters"); - - foreach (var parameter in ctor.GetParameters()) - { - if (string.IsNullOrEmpty(parameter.Name)) - return false; - - var isParameterOverload = parameters.ContainsKey(parameter.Name); - - //#if NETFX_CORE - // if (parameter.ParameterType.GetTypeInfo().IsPrimitive && !isParameterOverload) - //#else - if (parameter.ParameterType.IsPrimitive() && !isParameterOverload) - //#endif - return false; - - if (!isParameterOverload && !CanResolveInternal(new TypeRegistration(parameter.ParameterType), NamedParameterOverloads.Default, options)) - return false; - } - - return true; - } - - private ConstructorInfo GetBestConstructor(Type type, NamedParameterOverloads parameters, ResolveOptions options) - { - if (parameters == null) - throw new ArgumentNullException("parameters"); - - //#if NETFX_CORE - // if (type.GetTypeInfo().IsValueType) - //#else - if (type.IsValueType()) - //#endif - return null; - - // Get constructors in reverse order based on the number of parameters - // i.e. be as "greedy" as possible so we satify the most amount of dependencies possible - var ctors = GetTypeConstructors(type); - - foreach (var ctor in ctors) - { - if (CanConstruct(ctor, parameters, options)) - return ctor; - } - - return null; - } - - private IEnumerable GetTypeConstructors(Type type) - { - //#if NETFX_CORE - // return type.GetTypeInfo().DeclaredConstructors.OrderByDescending(ctor => ctor.GetParameters().Count()); - //#else - return type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Count()); - //#endif - } - - private object ConstructType(Type requestedType, Type implementationType, ResolveOptions options) - { - return ConstructType(requestedType, implementationType, null, NamedParameterOverloads.Default, options); - } - - private object ConstructType(Type requestedType, Type implementationType, ConstructorInfo constructor, ResolveOptions options) - { - return ConstructType(requestedType, implementationType, constructor, NamedParameterOverloads.Default, options); - } - - private object ConstructType(Type requestedType, Type implementationType, NamedParameterOverloads parameters, ResolveOptions options) - { - return ConstructType(requestedType, implementationType, null, parameters, options); - } - - private object ConstructType(Type requestedType, Type implementationType, ConstructorInfo constructor, NamedParameterOverloads parameters, ResolveOptions options) - { - var typeToConstruct = implementationType; - -#if RESOLVE_OPEN_GENERICS - if (implementationType.IsGenericTypeDefinition()) - { - if (requestedType == null || !requestedType.IsGenericType() || !requestedType.GetGenericArguments().Any()) - throw new TinyIoCResolutionException(typeToConstruct); - - typeToConstruct = typeToConstruct.MakeGenericType(requestedType.GetGenericArguments()); - } -#endif - if (constructor == null) - { - // Try and get the best constructor that we can construct - // if we can't construct any then get the constructor - // with the least number of parameters so we can throw a meaningful - // resolve exception - constructor = GetBestConstructor(typeToConstruct, parameters, options) ?? GetTypeConstructors(typeToConstruct).LastOrDefault(); - } - - if (constructor == null) - throw new TinyIoCResolutionException(typeToConstruct); - - var ctorParams = constructor.GetParameters(); - object[] args = new object[ctorParams.Count()]; - - for (int parameterIndex = 0; parameterIndex < ctorParams.Count(); parameterIndex++) - { - var currentParam = ctorParams[parameterIndex]; - - try - { - if (ctorParams[parameterIndex].ParameterType == typeof(Logger)) - { - args[parameterIndex] = LogManager.GetLogger(implementationType.Name); - } - else - { - args[parameterIndex] = parameters.ContainsKey(currentParam.Name) ? - parameters[currentParam.Name] : - ResolveInternal( - new TypeRegistration(currentParam.ParameterType), - NamedParameterOverloads.Default, - options); - } - } - catch (TinyIoCResolutionException ex) - { - // If a constructor parameter can't be resolved - // it will throw, so wrap it and throw that this can't - // be resolved. - throw new TinyIoCResolutionException(typeToConstruct, ex); - } - catch (Exception ex) - { - throw new TinyIoCResolutionException(typeToConstruct, ex); - } - } - - try - { -#if USE_OBJECT_CONSTRUCTOR - var constructionDelegate = CreateObjectConstructionDelegateWithCache(constructor); - return constructionDelegate.Invoke(args); -#else - return constructor.Invoke(args); -#endif - } - catch (Exception ex) - { - throw new TinyIoCResolutionException(typeToConstruct, ex); - } - } - -#if USE_OBJECT_CONSTRUCTOR - private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor) - { - ObjectConstructor objectConstructor; - if (_ObjectConstructorCache.TryGetValue(constructor, out objectConstructor)) - return objectConstructor; - - // We could lock the cache here, but there's no real side - // effect to two threads creating the same ObjectConstructor - // at the same time, compared to the cost of a lock for - // every creation. - var constructorParams = constructor.GetParameters(); - var lambdaParams = Expression.Parameter(typeof(object[]), "parameters"); - var newParams = new Expression[constructorParams.Length]; - - for (int i = 0; i < constructorParams.Length; i++) - { - var paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i)); - - newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType); - } - - var newExpression = Expression.New(constructor, newParams); - - var constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams); - - objectConstructor = (ObjectConstructor)constructionLambda.Compile(); - - _ObjectConstructorCache[constructor] = objectConstructor; - return objectConstructor; - } -#endif - - private void BuildUpInternal(object input, ResolveOptions resolveOptions) - { - //#if NETFX_CORE - // var properties = from property in input.GetType().GetTypeInfo().DeclaredProperties - // where (property.GetMethod != null) && (property.SetMethod != null) && !property.PropertyType.GetTypeInfo().IsValueType - // select property; - //#else - var properties = from property in input.GetType().GetProperties() - where (property.GetGetMethod() != null) && (property.GetSetMethod() != null) && !property.PropertyType.IsValueType() - select property; - //#endif - foreach (var property in properties) - { - if (property.GetValue(input, null) == null) - { - try - { - property.SetValue(input, ResolveInternal(new TypeRegistration(property.PropertyType), NamedParameterOverloads.Default, resolveOptions), null); - } - catch (TinyIoCResolutionException) - { - // Catch any resolution errors and ignore them - } - } - } - } - - private IEnumerable GetParentRegistrationsForType(Type resolveType) - { - if (_Parent == null) - return new TypeRegistration[] { }; - - var registrations = _Parent._RegisteredTypes.Keys.Where(tr => tr.Type == resolveType); - - return registrations.Concat(_Parent.GetParentRegistrationsForType(resolveType)); - } - - private IEnumerable ResolveAllInternal(Type resolveType, bool includeUnnamed) - { - var registrations = _RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(GetParentRegistrationsForType(resolveType)); - - if (!includeUnnamed) - registrations = registrations.Where(tr => tr.Name != string.Empty); - - return registrations.Select(registration => ResolveInternal(registration, NamedParameterOverloads.Default, ResolveOptions.Default)); - } - - private static bool IsValidAssignment(Type registerType, Type registerImplementation) - { - //#if NETFX_CORE - // var registerTypeDef = registerType.GetTypeInfo(); - // var registerImplementationDef = registerImplementation.GetTypeInfo(); - - // if (!registerTypeDef.IsGenericTypeDefinition) - // { - // if (!registerTypeDef.IsAssignableFrom(registerImplementationDef)) - // return false; - // } - // else - // { - // if (registerTypeDef.IsInterface()) - // { - // if (!registerImplementationDef.ImplementedInterfaces.Any(t => t.GetTypeInfo().Name == registerTypeDef.Name)) - // return false; - // } - // else if (registerTypeDef.IsAbstract() && registerImplementationDef.BaseType() != registerType) - // { - // return false; - // } - // } - //#else - if (!registerType.IsGenericTypeDefinition()) - { - if (!registerType.IsAssignableFrom(registerImplementation)) - return false; - } - else - { - if (registerType.IsInterface()) - { - if (!registerImplementation.FindInterfaces((t, o) => t.Name == registerType.Name, null).Any()) - return false; - } - else if (registerType.IsAbstract() && registerImplementation.BaseType() != registerType) - { - return false; - } - } - - //#endif - return true; - } - - #endregion - - #region IDisposable Members - private bool disposed = false; - public void Dispose() - { - if (!disposed) - { - disposed = true; - - _RegisteredTypes.Dispose(); - - GC.SuppressFinalize(this); - } - } - - #endregion - } -} - -// reverse shim for WinRT SR changes... -#if !NETFX_CORE -namespace System.Reflection -{ - public static class ReverseTypeExtender - { - public static bool IsClass(this Type type) - { - return type.IsClass; - } - - public static bool IsAbstract(this Type type) - { - return type.IsAbstract; - } - - public static bool IsInterface(this Type type) - { - return type.IsInterface; - } - - public static bool IsPrimitive(this Type type) - { - return type.IsPrimitive; - } - - public static bool IsValueType(this Type type) - { - return type.IsValueType; - } - - public static bool IsGenericType(this Type type) - { - return type.IsGenericType; - } - - public static bool IsGenericParameter(this Type type) - { - return type.IsGenericParameter; - } - - public static bool IsGenericTypeDefinition(this Type type) - { - return type.IsGenericTypeDefinition; - } - - public static Type BaseType(this Type type) - { - return type.BaseType; - } - - public static Assembly Assembly(this Type type) - { - return type.Assembly; - } - } -} -#endif diff --git a/src/NzbDrone.Console/ConsoleAlerts.cs b/src/NzbDrone.Console/ConsoleAlerts.cs deleted file mode 100644 index 04893b18a..000000000 --- a/src/NzbDrone.Console/ConsoleAlerts.cs +++ /dev/null @@ -1,15 +0,0 @@ -using NzbDrone.Host; - -namespace NzbDrone.Console -{ - public class ConsoleAlerts : IUserAlert - { - public void Alert(string message) - { - System.Console.WriteLine(); - System.Console.WriteLine(message); - System.Console.WriteLine("Press enter to continue"); - System.Console.ReadLine(); - } - } -} diff --git a/src/NzbDrone.Console/ConsoleApp.cs b/src/NzbDrone.Console/ConsoleApp.cs index 7e616ffb6..cf1c5297f 100644 --- a/src/NzbDrone.Console/ConsoleApp.cs +++ b/src/NzbDrone.Console/ConsoleApp.cs @@ -2,6 +2,8 @@ using System; using System.IO; using System.Net.Sockets; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Exceptions; @@ -15,7 +17,7 @@ namespace NzbDrone.Console { private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(ConsoleApp)); - private enum ExitCodes : int + private enum ExitCodes { Normal = 0, UnknownFailure = 1, @@ -40,7 +42,7 @@ namespace NzbDrone.Console throw; } - Bootstrap.Start(startupArgs, new ConsoleAlerts()); + Bootstrap.Start(args); } catch (LidarrStartupException ex) { diff --git a/src/NzbDrone.Core.Test/Framework/MigrationTest.cs b/src/NzbDrone.Core.Test/Framework/MigrationTest.cs index 7a59c837b..a103c555c 100644 --- a/src/NzbDrone.Core.Test/Framework/MigrationTest.cs +++ b/src/NzbDrone.Core.Test/Framework/MigrationTest.cs @@ -1,6 +1,7 @@ using System; using FluentMigrator; using Microsoft.Extensions.Logging; +using NLog.Extensions.Logging; using NUnit.Framework; using NzbDrone.Core.Datastore.Migration.Framework; @@ -39,7 +40,7 @@ namespace NzbDrone.Core.Test.Framework protected override void SetupLogging() { - Mocker.SetConstant(Mocker.Resolve()); + Mocker.SetConstant(Mocker.Resolve()); } [SetUp] diff --git a/src/NzbDrone.Core/Datastore/DbFactory.cs b/src/NzbDrone.Core/Datastore/DbFactory.cs index c70f29ead..6ae61fe39 100644 --- a/src/NzbDrone.Core/Datastore/DbFactory.cs +++ b/src/NzbDrone.Core/Datastore/DbFactory.cs @@ -1,7 +1,6 @@ using System; using System.Data.SQLite; using NLog; -using NzbDrone.Common.Composition; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Exceptions; @@ -39,17 +38,6 @@ namespace NzbDrone.Core.Datastore Environment.SetEnvironmentVariable("No_PreLoadSQLite", "true"); } - public static void RegisterDatabase(IContainer container) - { - var mainDb = new MainDatabase(container.Resolve().Create()); - - container.Register(mainDb); - - var logDb = new LogDatabase(container.Resolve().Create(MigrationType.Log)); - - container.Register(logDb); - } - public DbFactory(IMigrationController migrationController, IConnectionStringFactory connectionStringFactory, IDiskProvider diskProvider, diff --git a/src/NzbDrone.Core/Datastore/Extensions/CompositionExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/CompositionExtensions.cs new file mode 100644 index 000000000..c5e31f92c --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Extensions/CompositionExtensions.cs @@ -0,0 +1,24 @@ +using DryIoc; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Extensions +{ + public static class CompositionExtensions + { + public static IContainer AddDatabase(this IContainer container) + { + container.RegisterDelegate(f => new MainDatabase(f.Create()), Reuse.Singleton); + container.RegisterDelegate(f => new LogDatabase(f.Create(MigrationType.Log)), Reuse.Singleton); + + return container; + } + + public static IContainer AddDummyDatabase(this IContainer container) + { + container.RegisterInstance(new MainDatabase(null)); + container.RegisterInstance(new LogDatabase(null)); + + return container; + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs index e49b7b493..35c5c6b59 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Reflection; using FluentMigrator.Runner; @@ -7,6 +7,7 @@ using FluentMigrator.Runner.Processors; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NLog; +using NLog.Extensions.Logging; namespace NzbDrone.Core.Datastore.Migration.Framework { @@ -34,7 +35,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework _logger.Info("*** Migrating {0} ***", connectionString); var serviceProvider = new ServiceCollection() - .AddLogging(lb => lb.AddProvider(_migrationLoggerProvider)) + .AddLogging(b => b.AddNLog()) .AddFluentMigratorCore() .ConfigureRunner( builder => builder diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationLogger.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationLogger.cs deleted file mode 100644 index 47ff934e1..000000000 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationLogger.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using FluentMigrator.Runner; -using FluentMigrator.Runner.Logging; -using NLog; - -namespace NzbDrone.Core.Datastore.Migration.Framework -{ - public class MigrationLogger : FluentMigratorLogger - { - private readonly Logger _logger; - - public MigrationLogger(Logger logger, - FluentMigratorLoggerOptions options) - : base(options) - { - _logger = logger; - } - - protected override void WriteHeading(string message) - { - _logger.Info("*** {0} ***", message); - } - - protected override void WriteSay(string message) - { - _logger.Debug(message); - } - - protected override void WriteEmphasize(string message) - { - _logger.Warn(message); - } - - protected override void WriteSql(string sql) - { - _logger.Debug(sql); - } - - protected override void WriteEmptySql() - { - _logger.Debug(@"No SQL statement executed."); - } - - protected override void WriteElapsedTime(TimeSpan timeSpan) - { - _logger.Debug("Took: {0}", timeSpan); - } - - protected override void WriteError(string message) - { - _logger.Error(message); - } - - protected override void WriteError(Exception exception) - { - _logger.Error(exception); - } - } -} diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationLoggerProvider.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationLoggerProvider.cs deleted file mode 100644 index 66d368de4..000000000 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationLoggerProvider.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using FluentMigrator.Runner; -using Microsoft.Extensions.Logging; -using NLog; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace NzbDrone.Core.Datastore.Migration.Framework -{ - public class MigrationLoggerProvider : ILoggerProvider - { - private readonly Logger _logger; - - public MigrationLoggerProvider(Logger logger) - { - _logger = logger; - } - - public ILogger CreateLogger(string categoryName) - { - return new MigrationLogger(_logger, new FluentMigratorLoggerOptions() { ShowElapsedTime = true, ShowSql = true }); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - // Nothing to clean up - } - } -} diff --git a/src/NzbDrone.Core/Download/DownloadClientFactory.cs b/src/NzbDrone.Core/Download/DownloadClientFactory.cs index c8a8c302e..3a4ab734a 100644 --- a/src/NzbDrone.Core/Download/DownloadClientFactory.cs +++ b/src/NzbDrone.Core/Download/DownloadClientFactory.cs @@ -1,8 +1,8 @@ +using System; using System.Collections.Generic; using System.Linq; using FluentValidation.Results; using NLog; -using NzbDrone.Common.Composition; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.ThingiProvider; @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Download public DownloadClientFactory(IDownloadClientStatusService downloadClientStatusService, IDownloadClientRepository providerRepository, IEnumerable providers, - IContainer container, + IServiceProvider container, IEventAggregator eventAggregator, Logger logger) : base(providerRepository, providers, container, eventAggregator, logger) diff --git a/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs b/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs index 1d74c29f7..f9ec7a6fc 100644 --- a/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs +++ b/src/NzbDrone.Core/Extras/ExistingExtraFileService.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Extras public ExistingExtraFileService(IDiskProvider diskProvider, IDiskScanService diskScanService, - List existingExtraFileImporters, + IEnumerable existingExtraFileImporters, Logger logger) { _diskProvider = diskProvider; diff --git a/src/NzbDrone.Core/Extras/ExtraService.cs b/src/NzbDrone.Core/Extras/ExtraService.cs index d28af99b9..284f84e76 100644 --- a/src/NzbDrone.Core/Extras/ExtraService.cs +++ b/src/NzbDrone.Core/Extras/ExtraService.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Extras ITrackService trackService, IDiskProvider diskProvider, IConfigService configService, - List extraFileManagers, + IEnumerable extraFileManagers, Logger logger) { _mediaFileService = mediaFileService; diff --git a/src/NzbDrone.Core/Extras/Metadata/MetadataFactory.cs b/src/NzbDrone.Core/Extras/Metadata/MetadataFactory.cs index b52d8a18c..fad65a348 100644 --- a/src/NzbDrone.Core/Extras/Metadata/MetadataFactory.cs +++ b/src/NzbDrone.Core/Extras/Metadata/MetadataFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NLog; @@ -17,7 +17,7 @@ namespace NzbDrone.Core.Extras.Metadata { private readonly IMetadataRepository _providerRepository; - public MetadataFactory(IMetadataRepository providerRepository, IEnumerable providers, IContainer container, IEventAggregator eventAggregator, Logger logger) + public MetadataFactory(IMetadataRepository providerRepository, IEnumerable providers, IServiceProvider container, IEventAggregator eventAggregator, Logger logger) : base(providerRepository, providers, container, eventAggregator, logger) { _providerRepository = providerRepository; diff --git a/src/NzbDrone.Core/ImportLists/ImportListFactory.cs b/src/NzbDrone.Core/ImportLists/ImportListFactory.cs index 79165d275..aed9ad44d 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListFactory.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using FluentValidation.Results; @@ -21,7 +22,7 @@ namespace NzbDrone.Core.ImportLists public ImportListFactory(IImportListStatusService importListStatusService, IImportListRepository providerRepository, IEnumerable providers, - IContainer container, + IServiceProvider container, IEventAggregator eventAggregator, Logger logger) : base(providerRepository, providers, container, eventAggregator, logger) diff --git a/src/NzbDrone.Core/Indexers/IndexerFactory.cs b/src/NzbDrone.Core/Indexers/IndexerFactory.cs index 902de54b9..08b260609 100644 --- a/src/NzbDrone.Core/Indexers/IndexerFactory.cs +++ b/src/NzbDrone.Core/Indexers/IndexerFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using FluentValidation.Results; @@ -23,7 +24,7 @@ namespace NzbDrone.Core.Indexers public IndexerFactory(IIndexerStatusService indexerStatusService, IIndexerRepository providerRepository, IEnumerable providers, - IContainer container, + IServiceProvider container, IEventAggregator eventAggregator, Logger logger) : base(providerRepository, providers, container, eventAggregator, logger) diff --git a/src/NzbDrone.Core/Lidarr.Core.csproj b/src/NzbDrone.Core/Lidarr.Core.csproj index ea6d888ae..01e71f851 100644 --- a/src/NzbDrone.Core/Lidarr.Core.csproj +++ b/src/NzbDrone.Core/Lidarr.Core.csproj @@ -6,12 +6,16 @@ + + + + diff --git a/src/NzbDrone.Core/Messaging/Commands/CommandQueueManager.cs b/src/NzbDrone.Core/Messaging/Commands/CommandQueueManager.cs index d06b5051c..cf11f4320 100644 --- a/src/NzbDrone.Core/Messaging/Commands/CommandQueueManager.cs +++ b/src/NzbDrone.Core/Messaging/Commands/CommandQueueManager.cs @@ -5,6 +5,7 @@ using System.Net; using System.Threading; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Composition; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Serializer; using NzbDrone.Core.Exceptions; @@ -36,17 +37,18 @@ namespace NzbDrone.Core.Messaging.Commands public class CommandQueueManager : IManageCommandQueue, IHandle { private readonly ICommandRepository _repo; - private readonly IServiceFactory _serviceFactory; + private readonly KnownTypes _knownTypes; private readonly Logger _logger; private readonly CommandQueue _commandQueue; public CommandQueueManager(ICommandRepository repo, IServiceFactory serviceFactory, + KnownTypes knownTypes, Logger logger) { _repo = repo; - _serviceFactory = serviceFactory; + _knownTypes = knownTypes; _logger = logger; _commandQueue = new CommandQueue(); @@ -233,9 +235,8 @@ namespace NzbDrone.Core.Messaging.Commands private dynamic GetCommand(string commandName) { commandName = commandName.Split('.').Last(); - - var commandType = _serviceFactory.GetImplementations(typeof(Command)) - .Single(c => c.Name.Equals(commandName, StringComparison.InvariantCultureIgnoreCase)); + var commands = _knownTypes.GetImplementations(typeof(Command)); + var commandType = commands.Single(c => c.Name.Equals(commandName, StringComparison.InvariantCultureIgnoreCase)); return Json.Deserialize("{}", commandType); } diff --git a/src/NzbDrone.Core/Notifications/NotificationFactory.cs b/src/NzbDrone.Core/Notifications/NotificationFactory.cs index 3d4fbb74b..da063cf3a 100644 --- a/src/NzbDrone.Core/Notifications/NotificationFactory.cs +++ b/src/NzbDrone.Core/Notifications/NotificationFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using NLog; @@ -21,7 +22,7 @@ namespace NzbDrone.Core.Notifications public class NotificationFactory : ProviderFactory, INotificationFactory { - public NotificationFactory(INotificationRepository providerRepository, IEnumerable providers, IContainer container, IEventAggregator eventAggregator, Logger logger) + public NotificationFactory(INotificationRepository providerRepository, IEnumerable providers, IServiceProvider container, IEventAggregator eventAggregator, Logger logger) : base(providerRepository, providers, container, eventAggregator, logger) { } diff --git a/src/NzbDrone.Core/Tags/TagService.cs b/src/NzbDrone.Core/Tags/TagService.cs index 9c5d07eb6..7ef08df3c 100644 --- a/src/NzbDrone.Core/Tags/TagService.cs +++ b/src/NzbDrone.Core/Tags/TagService.cs @@ -37,7 +37,7 @@ namespace NzbDrone.Core.Tags public TagService(ITagRepository repo, IEventAggregator eventAggregator, IDelayProfileService delayProfileService, - ImportListFactory importListFactory, + IImportListFactory importListFactory, INotificationFactory notificationFactory, IReleaseProfileService releaseProfileService, IArtistService artistService, diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs index 9cbf46615..fac049f45 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using FluentValidation.Results; +using Microsoft.Extensions.DependencyInjection; using NLog; using NzbDrone.Common.Composition; using NzbDrone.Core.Lifecycle; @@ -15,7 +16,7 @@ namespace NzbDrone.Core.ThingiProvider where TProvider : IProvider { private readonly IProviderRepository _providerRepository; - private readonly IContainer _container; + private readonly IServiceProvider _container; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; @@ -23,7 +24,7 @@ namespace NzbDrone.Core.ThingiProvider protected ProviderFactory(IProviderRepository providerRepository, IEnumerable providers, - IContainer container, + IServiceProvider container, IEventAggregator eventAggregator, Logger logger) { @@ -123,7 +124,7 @@ namespace NzbDrone.Core.ThingiProvider public TProvider GetInstance(TProviderDefinition definition) { var type = GetImplementation(definition); - var instance = (TProvider)_container.Resolve(type); + var instance = (TProvider)_container.GetRequiredService(type); instance.Definition = definition; SetProviderCharacteristics(instance, definition); return instance; diff --git a/src/NzbDrone.Host.Test/ContainerFixture.cs b/src/NzbDrone.Host.Test/ContainerFixture.cs index af4383be3..4c263daf1 100644 --- a/src/NzbDrone.Host.Test/ContainerFixture.cs +++ b/src/NzbDrone.Host.Test/ContainerFixture.cs @@ -1,12 +1,16 @@ using System.Collections.Generic; using System.Linq; +using DryIoc; +using DryIoc.Microsoft.DependencyInjection; using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; using NzbDrone.Common; -using NzbDrone.Common.Composition; +using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Core.Datastore; +using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Indexers; @@ -15,44 +19,50 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Host; using NzbDrone.SignalR; using NzbDrone.Test.Common; +using IServiceProvider = System.IServiceProvider; namespace NzbDrone.App.Test { [TestFixture] public class ContainerFixture : TestBase { - private IContainer _container; + private IServiceProvider _container; [SetUp] public void SetUp() { var args = new StartupContext("first", "second"); - _container = MainAppContainerBuilder.BuildContainer(args); - - _container.Register(new MainDatabase(null)); - // set up a dummy broadcaster to allow tests to resolve var mockBroadcaster = new Mock(); - _container.Register(mockBroadcaster.Object); + + var container = new Container(rules => rules.WithNzbDroneRules()) + .AutoAddServices(Bootstrap.ASSEMBLIES) + .AddNzbDroneLogger() + .AddDummyDatabase() + .AddStartupContext(args); + + container.RegisterInstance(mockBroadcaster.Object); + + _container = container.GetServiceProvider(); } [Test] public void should_be_able_to_resolve_indexers() { - _container.Resolve>().Should().NotBeEmpty(); + _container.GetRequiredService>().Should().NotBeEmpty(); } [Test] public void should_be_able_to_resolve_downloadclients() { - _container.Resolve>().Should().NotBeEmpty(); + _container.GetRequiredService>().Should().NotBeEmpty(); } [Test] public void container_should_inject_itself() { - var factory = _container.Resolve(); + var factory = _container.GetRequiredService(); factory.Build().Should().NotBeNull(); } @@ -62,7 +72,7 @@ namespace NzbDrone.App.Test { var genericExecutor = typeof(IExecute<>).MakeGenericType(typeof(RssSyncCommand)); - var executor = _container.Resolve(genericExecutor); + var executor = _container.GetRequiredService(genericExecutor); executor.Should().NotBeNull(); executor.Should().BeAssignableTo>(); @@ -71,8 +81,8 @@ namespace NzbDrone.App.Test [Test] public void should_return_same_instance_via_resolve_and_resolveall() { - var first = (DownloadMonitoringService)_container.Resolve>(); - var second = _container.ResolveAll>().OfType().Single(); + var first = (DownloadMonitoringService)_container.GetRequiredService>(); + var second = _container.GetServices>().OfType().Single(); first.Should().BeSameAs(second); } @@ -80,8 +90,8 @@ namespace NzbDrone.App.Test [Test] public void should_return_same_instance_of_singletons_by_same_interface() { - var first = _container.ResolveAll>().OfType().Single(); - var second = _container.ResolveAll>().OfType().Single(); + var first = _container.GetServices>().OfType().Single(); + var second = _container.GetServices>().OfType().Single(); first.Should().BeSameAs(second); } @@ -89,8 +99,8 @@ namespace NzbDrone.App.Test [Test] public void should_return_same_instance_of_singletons_by_different_interfaces() { - var first = _container.ResolveAll>().OfType().Single(); - var second = (DownloadMonitoringService)_container.Resolve>(); + var first = _container.GetServices>().OfType().Single(); + var second = (DownloadMonitoringService)_container.GetRequiredService>(); first.Should().BeSameAs(second); } diff --git a/src/NzbDrone.Host.Test/RouterTest.cs b/src/NzbDrone.Host.Test/RouterTest.cs index 4d3f44c9c..430b76867 100644 --- a/src/NzbDrone.Host.Test/RouterTest.cs +++ b/src/NzbDrone.Host.Test/RouterTest.cs @@ -1,4 +1,3 @@ -using System.ServiceProcess; using Moq; using NUnit.Framework; using NzbDrone.Common; @@ -10,7 +9,7 @@ using NzbDrone.Test.Common; namespace NzbDrone.App.Test { [TestFixture] - public class RouterTest : TestBase + public class RouterTest : TestBase { [SetUp] public void Setup() @@ -48,33 +47,6 @@ namespace NzbDrone.App.Test serviceProviderMock.Verify(c => c.Uninstall(ServiceProvider.SERVICE_NAME), Times.Once()); } - [Test] - public void Route_should_call_console_service_when_application_mode_is_console() - { - Mocker.GetMock().SetupGet(c => c.IsUserInteractive).Returns(true); - - Subject.Route(ApplicationModes.Interactive); - - Mocker.GetMock().Verify(c => c.Start(), Times.Once()); - } - - [Test] - public void Route_should_call_service_start_when_run_in_service_mode() - { - var envMock = Mocker.GetMock(); - var serviceProvider = Mocker.GetMock(); - - envMock.SetupGet(c => c.IsUserInteractive).Returns(false); - - serviceProvider.Setup(c => c.Run(It.IsAny())); - serviceProvider.Setup(c => c.ServiceExist(It.IsAny())).Returns(true); - serviceProvider.Setup(c => c.GetStatus(It.IsAny())).Returns(ServiceControllerStatus.StartPending); - - Subject.Route(ApplicationModes.Service); - - serviceProvider.Verify(c => c.Run(It.IsAny()), Times.Once()); - } - [Test] public void show_error_on_install_if_service_already_exist() { diff --git a/src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs b/src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs similarity index 90% rename from src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs rename to src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs index 291defcfb..a4f440672 100644 --- a/src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs +++ b/src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs @@ -2,6 +2,11 @@ using NzbDrone.Common.EnvironmentInfo; namespace NzbDrone.Host.AccessControl { + public interface IRemoteAccessAdapter + { + void MakeAccessible(bool passive); + } + public class RemoteAccessAdapter : IRemoteAccessAdapter { private readonly IRuntimeInfo _runtimeInfo; diff --git a/src/NzbDrone.Host/AppLifetime.cs b/src/NzbDrone.Host/AppLifetime.cs new file mode 100644 index 000000000..d0d0955ce --- /dev/null +++ b/src/NzbDrone.Host/AppLifetime.cs @@ -0,0 +1,119 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using NLog; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Processes; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Host +{ + public class AppLifetime : IHostedService, IHandle + { + private readonly IHostApplicationLifetime _appLifetime; + private readonly IConfigFileProvider _configFileProvider; + private readonly IRuntimeInfo _runtimeInfo; + private readonly IStartupContext _startupContext; + private readonly IBrowserService _browserService; + private readonly IProcessProvider _processProvider; + private readonly IEventAggregator _eventAggregator; + private readonly IUtilityModeRouter _utilityModeRouter; + private readonly Logger _logger; + + public AppLifetime(IHostApplicationLifetime appLifetime, + IConfigFileProvider configFileProvider, + IRuntimeInfo runtimeInfo, + IStartupContext startupContext, + IBrowserService browserService, + IProcessProvider processProvider, + IEventAggregator eventAggregator, + IUtilityModeRouter utilityModeRouter, + Logger logger) + { + _appLifetime = appLifetime; + _configFileProvider = configFileProvider; + _runtimeInfo = runtimeInfo; + _startupContext = startupContext; + _browserService = browserService; + _processProvider = processProvider; + _eventAggregator = eventAggregator; + _utilityModeRouter = utilityModeRouter; + _logger = logger; + + appLifetime.ApplicationStarted.Register(OnAppStarted); + appLifetime.ApplicationStopped.Register(OnAppStopped); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private void OnAppStarted() + { + _runtimeInfo.IsExiting = false; + + if (!_startupContext.Flags.Contains(StartupContext.NO_BROWSER) + && _configFileProvider.LaunchBrowser) + { + _browserService.LaunchWebUI(); + } + + _eventAggregator.PublishEvent(new ApplicationStartedEvent()); + } + + private void OnAppStopped() + { + if (_runtimeInfo.RestartPending) + { + var restartArgs = GetRestartArgs(); + + _logger.Info("Attempting restart with arguments: {0}", restartArgs); + _processProvider.SpawnNewProcess(_runtimeInfo.ExecutingApplication, restartArgs); + } + } + + private void Shutdown() + { + _logger.Info("Attempting to stop application."); + _logger.Info("Application has finished stop routine."); + _runtimeInfo.IsExiting = true; + _appLifetime.StopApplication(); + } + + private string GetRestartArgs() + { + var args = _startupContext.PreservedArguments; + + args += " /restart"; + + if (!args.Contains("/nobrowser")) + { + args += " /nobrowser"; + } + + return args; + } + + public void Handle(ApplicationShutdownRequested message) + { + if (!_runtimeInfo.IsWindowsService) + { + if (message.Restarting) + { + _runtimeInfo.RestartPending = true; + } + + LogManager.Configuration = null; + Shutdown(); + } + } + } +} diff --git a/src/NzbDrone.Host/ApplicationServer.cs b/src/NzbDrone.Host/ApplicationServer.cs deleted file mode 100644 index 1149f07e5..000000000 --- a/src/NzbDrone.Host/ApplicationServer.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.ServiceProcess; -using NLog; -using NzbDrone.Common.Composition; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Lifecycle; -using NzbDrone.Core.Messaging.Events; - -namespace NzbDrone.Host -{ - public interface INzbDroneServiceFactory - { - ServiceBase Build(); - } - - public interface INzbDroneConsoleFactory - { - void Start(); - void Shutdown(); - } - - public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory - { - private readonly INzbDroneConsoleFactory _consoleFactory; - - public NzbDroneServiceFactory(INzbDroneConsoleFactory consoleFactory) - { - _consoleFactory = consoleFactory; - } - - protected override void OnStart(string[] args) - { - _consoleFactory.Start(); - } - - protected override void OnStop() - { - _consoleFactory.Shutdown(); - } - - public ServiceBase Build() - { - return this; - } - } - - public class DummyNzbDroneServiceFactory : INzbDroneServiceFactory - { - public ServiceBase Build() - { - return null; - } - } - - public class NzbDroneConsoleFactory : INzbDroneConsoleFactory, IHandle - { - private readonly IConfigFileProvider _configFileProvider; - private readonly IRuntimeInfo _runtimeInfo; - private readonly IHostController _hostController; - private readonly IStartupContext _startupContext; - private readonly IBrowserService _browserService; - private readonly IContainer _container; - private readonly Logger _logger; - - public NzbDroneConsoleFactory(IConfigFileProvider configFileProvider, - IHostController hostController, - IRuntimeInfo runtimeInfo, - IStartupContext startupContext, - IBrowserService browserService, - IContainer container, - Logger logger) - { - _configFileProvider = configFileProvider; - _hostController = hostController; - _runtimeInfo = runtimeInfo; - _startupContext = startupContext; - _browserService = browserService; - _container = container; - _logger = logger; - } - - public void Start() - { - if (OsInfo.IsNotWindows) - { - Console.CancelKeyPress += (sender, eventArgs) => LogManager.Configuration = null; - } - - _runtimeInfo.IsExiting = false; - DbFactory.RegisterDatabase(_container); - - _container.Resolve().PublishEvent(new ApplicationStartingEvent()); - - if (_runtimeInfo.IsExiting) - { - return; - } - - _hostController.StartServer(); - - if (!_startupContext.Flags.Contains(StartupContext.NO_BROWSER) - && _configFileProvider.LaunchBrowser) - { - _browserService.LaunchWebUI(); - } - - _container.Resolve().PublishEvent(new ApplicationStartedEvent()); - } - - public void Shutdown() - { - _logger.Info("Attempting to stop application."); - _hostController.StopServer(); - _logger.Info("Application has finished stop routine."); - _runtimeInfo.IsExiting = true; - } - - public void Handle(ApplicationShutdownRequested message) - { - if (!_runtimeInfo.IsWindowsService) - { - if (message.Restarting) - { - _runtimeInfo.RestartPending = true; - } - - LogManager.Configuration = null; - Shutdown(); - } - } - } -} diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs index c4407313e..17d7f5276 100644 --- a/src/NzbDrone.Host/Bootstrap.cs +++ b/src/NzbDrone.Host/Bootstrap.cs @@ -1,54 +1,90 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Reflection; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using System.Text; -using System.Threading; +using DryIoc; +using DryIoc.Microsoft.DependencyInjection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.WindowsServices; using NLog; -using NzbDrone.Common.Composition; +using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Exceptions; +using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation; -using NzbDrone.Common.Processes; +using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Configuration; -using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Datastore.Extensions; namespace NzbDrone.Host { public static class Bootstrap { private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(Bootstrap)); - private static IContainer _container; - public static void Start(StartupContext startupContext, IUserAlert userAlert, Action startCallback = null) + public static readonly List ASSEMBLIES = new List + { + "Lidarr.Host", + "Lidarr.Core", + "Lidarr.SignalR", + "Lidarr.Api.V1", + "Lidarr.Http" + }; + + public static void Start(string[] args, Action trayCallback = null) { try { Logger.Info("Starting Lidarr - {0} - Version {1}", Assembly.GetCallingAssembly().Location, Assembly.GetExecutingAssembly().GetName().Version); - if (!PlatformValidation.IsValidate(userAlert)) - { - throw new TerminateApplicationException("Missing system requirements"); - } + var startupContext = new StartupContext(args); LongPathSupport.Enable(); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - _container = MainAppContainerBuilder.BuildContainer(startupContext); - _container.Resolve().Initialize(); - _container.Resolve().Register(); - _container.Resolve().Write(); - var appMode = GetApplicationMode(startupContext); - Start(appMode, startupContext); - - if (startCallback != null) - { - startCallback(_container); - } - else + switch (appMode) { - SpinToExit(appMode); + case ApplicationModes.Service: + { + Logger.Debug("Service selected"); + + CreateConsoleHostBuilder(args, startupContext).UseWindowsService().Build().Run(); + break; + } + + case ApplicationModes.Interactive: + { + Logger.Debug(trayCallback != null ? "Tray selected" : "Console selected"); + var builder = CreateConsoleHostBuilder(args, startupContext); + + if (trayCallback != null) + { + trayCallback(builder); + } + + builder.Build().Run(); + break; + } + + // Utility mode + default: + { + new Container(rules => rules.WithNzbDroneRules()) + .AutoAddServices(ASSEMBLIES) + .AddNzbDroneLogger() + .AddStartupContext(startupContext) + .Resolve() + .Route(appMode); + break; + } } } catch (InvalidConfigFileException ex) @@ -66,61 +102,57 @@ namespace NzbDrone.Host } } - private static void Start(ApplicationModes applicationModes, StartupContext startupContext) + public static IHostBuilder CreateConsoleHostBuilder(string[] args, StartupContext context) { - _container.Resolve().Reconfigure(); - - if (!IsInUtilityMode(applicationModes)) - { - if (startupContext.Flags.Contains(StartupContext.RESTART)) - { - Thread.Sleep(2000); - } + var config = GetConfiguration(context); - EnsureSingleInstance(applicationModes == ApplicationModes.Service, startupContext); - } + var bindAddress = config.GetValue(nameof(ConfigFileProvider.BindAddress), "*"); + var port = config.GetValue(nameof(ConfigFileProvider.Port), 8686); + var sslPort = config.GetValue(nameof(ConfigFileProvider.SslPort), 6868); + var enableSsl = config.GetValue(nameof(ConfigFileProvider.EnableSsl), false); + var sslCertPath = config.GetValue(nameof(ConfigFileProvider.SslCertPath)); + var sslCertPassword = config.GetValue(nameof(ConfigFileProvider.SslCertPassword)); - _container.Resolve().Route(applicationModes); - } + var urls = new List { BuildUrl("http", bindAddress, port) }; - private static void SpinToExit(ApplicationModes applicationModes) - { - if (IsInUtilityMode(applicationModes)) - { - return; - } - - _container.Resolve().Spin(); - } - - private static void EnsureSingleInstance(bool isService, IStartupContext startupContext) - { - if (startupContext.Flags.Contains(StartupContext.NO_SINGLE_INSTANCE_CHECK)) + if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace()) { - return; + urls.Add(BuildUrl("https", bindAddress, sslPort)); } - var instancePolicy = _container.Resolve(); - - if (startupContext.Flags.Contains(StartupContext.TERMINATE)) - { - instancePolicy.KillAllOtherInstance(); - } - else if (startupContext.Args.ContainsKey(StartupContext.APPDATA)) - { - instancePolicy.WarnIfAlreadyRunning(); - } - else if (isService) - { - instancePolicy.KillAllOtherInstance(); - } - else - { - instancePolicy.PreventStartIfAlreadyRunning(); - } + return new HostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules()))) + .ConfigureContainer(c => + { + c.AutoAddServices(Bootstrap.ASSEMBLIES) + .AddNzbDroneLogger() + .AddDatabase() + .AddStartupContext(context); + }) + .ConfigureWebHost(builder => + { + builder.UseUrls(urls.ToArray()); + builder.UseKestrel(options => + { + if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace()) + { + options.ConfigureHttpsDefaults(configureOptions => + { + configureOptions.ServerCertificate = ValidateSslCertificate(sslCertPath, sslCertPassword); + }); + } + }); + builder.ConfigureKestrel(serverOptions => + { + serverOptions.AllowSynchronousIO = true; + serverOptions.Limits.MaxRequestBodySize = null; + }); + builder.UseStartup(); + }); } - private static ApplicationModes GetApplicationMode(IStartupContext startupContext) + public static ApplicationModes GetApplicationMode(IStartupContext startupContext) { if (startupContext.Help) { @@ -142,7 +174,7 @@ namespace NzbDrone.Host return ApplicationModes.UninstallService; } - if (_container.Resolve().IsWindowsService) + if (OsInfo.IsWindows && WindowsServiceHelpers.IsWindowsService()) { return ApplicationModes.Service; } @@ -150,23 +182,39 @@ namespace NzbDrone.Host return ApplicationModes.Interactive; } - private static bool IsInUtilityMode(ApplicationModes applicationMode) + private static IConfiguration GetConfiguration(StartupContext context) + { + var appFolder = new AppFolderInfo(context); + return new ConfigurationBuilder() + .AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: false) + .Build(); + } + + private static string BuildUrl(string scheme, string bindAddress, int port) { - switch (applicationMode) + return $"{scheme}://{bindAddress}:{port}"; + } + + private static X509Certificate2 ValidateSslCertificate(string cert, string password) + { + X509Certificate2 certificate; + + try { - case ApplicationModes.InstallService: - case ApplicationModes.UninstallService: - case ApplicationModes.RegisterUrl: - case ApplicationModes.Help: - { - return true; - } + certificate = new X509Certificate2(cert, password, X509KeyStorageFlags.DefaultKeySet); + } + catch (CryptographicException ex) + { + if (ex.HResult == 0x2 || ex.HResult == 0x2006D080) + { + throw new LidarrStartupException(ex, + $"The SSL certificate file {cert} does not exist"); + } - default: - { - return false; - } + throw new LidarrStartupException(ex); } + + return certificate; } } } diff --git a/src/NzbDrone.Host/IHostController.cs b/src/NzbDrone.Host/IHostController.cs deleted file mode 100644 index 858b785ad..000000000 --- a/src/NzbDrone.Host/IHostController.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace NzbDrone.Host -{ - public interface IHostController - { - void StartServer(); - void StopServer(); - } -} diff --git a/src/NzbDrone.Host/IRemoteAccessAdapter.cs b/src/NzbDrone.Host/IRemoteAccessAdapter.cs deleted file mode 100644 index 7021411a9..000000000 --- a/src/NzbDrone.Host/IRemoteAccessAdapter.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NzbDrone.Host.AccessControl -{ - public interface IRemoteAccessAdapter - { - void MakeAccessible(bool passive); - } -} diff --git a/src/NzbDrone.Host/IUserAlert.cs b/src/NzbDrone.Host/IUserAlert.cs deleted file mode 100644 index 3be8f7e63..000000000 --- a/src/NzbDrone.Host/IUserAlert.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NzbDrone.Host -{ - public interface IUserAlert - { - void Alert(string message); - } -} diff --git a/src/NzbDrone.Host/Lidarr.Host.csproj b/src/NzbDrone.Host/Lidarr.Host.csproj index 97ec2cb28..06c332c61 100644 --- a/src/NzbDrone.Host/Lidarr.Host.csproj +++ b/src/NzbDrone.Host/Lidarr.Host.csproj @@ -4,9 +4,10 @@ Library - - + + + diff --git a/src/NzbDrone.Host/MainAppContainerBuilder.cs b/src/NzbDrone.Host/MainAppContainerBuilder.cs deleted file mode 100644 index f2a788f3e..000000000 --- a/src/NzbDrone.Host/MainAppContainerBuilder.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Common.Composition; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.SignalR; - -namespace NzbDrone.Host -{ - public class MainAppContainerBuilder : ContainerBuilderBase - { - public static IContainer BuildContainer(StartupContext args) - { - var assemblies = new List - { - "Lidarr.Host", - "Lidarr.Core", - "Lidarr.SignalR", - "Lidarr.Api.V1", - "Lidarr.Http" - }; - - return new MainAppContainerBuilder(args, assemblies).Container; - } - - private MainAppContainerBuilder(StartupContext args, List assemblies) - : base(args, assemblies) - { - AutoRegisterImplementations(); - - if (OsInfo.IsWindows) - { - Container.Register(); - } - else - { - Container.Register(); - } - } - } -} diff --git a/src/NzbDrone.Host/PlatformValidation.cs b/src/NzbDrone.Host/PlatformValidation.cs deleted file mode 100644 index be6b1833d..000000000 --- a/src/NzbDrone.Host/PlatformValidation.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Diagnostics; -using System.Reflection; -using NLog; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Instrumentation; - -namespace NzbDrone.Host -{ - public static class PlatformValidation - { - private const string DOWNLOAD_LINK = "http://www.microsoft.com/en-us/download/details.aspx?id=42643"; - - private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(PlatformValidation)); - - public static bool IsValidate(IUserAlert userAlert) - { - if (OsInfo.IsNotWindows) - { - return true; - } - - if (!IsAssemblyAvailable("System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")) - { - userAlert.Alert("It looks like you don't have the correct version of .NET Framework installed. You will now be directed the download page."); - - try - { - Process.Start(DOWNLOAD_LINK); - } - catch (Exception) - { - userAlert.Alert("Oops. Couldn't start your browser. Please visit http://www.microsoft.com/net to download the latest version of .NET Framework"); - } - - return false; - } - - return true; - } - - private static bool IsAssemblyAvailable(string assemblyString) - { - try - { - Assembly.Load(assemblyString); - return true; - } - catch (Exception e) - { - Logger.Warn(e, "Couldn't load {0}", assemblyString); - return false; - } - } - } -} diff --git a/src/NzbDrone.Host/SpinService.cs b/src/NzbDrone.Host/SpinService.cs deleted file mode 100644 index 16bde2e15..000000000 --- a/src/NzbDrone.Host/SpinService.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Threading; -using NLog; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Processes; - -namespace NzbDrone.Host -{ - public interface IWaitForExit - { - void Spin(); - } - - public class SpinService : IWaitForExit - { - private readonly IRuntimeInfo _runtimeInfo; - private readonly IProcessProvider _processProvider; - private readonly IStartupContext _startupContext; - private readonly Logger _logger; - - public SpinService(IRuntimeInfo runtimeInfo, IProcessProvider processProvider, IStartupContext startupContext, Logger logger) - { - _runtimeInfo = runtimeInfo; - _processProvider = processProvider; - _startupContext = startupContext; - _logger = logger; - } - - public void Spin() - { - while (!_runtimeInfo.IsExiting) - { - Thread.Sleep(1000); - } - - _logger.Debug("Wait loop was terminated."); - - if (_runtimeInfo.RestartPending) - { - var restartArgs = GetRestartArgs(); - - _logger.Info("Attempting restart with arguments: {0}", restartArgs); - _processProvider.SpawnNewProcess(_runtimeInfo.ExecutingApplication, restartArgs); - } - } - - private string GetRestartArgs() - { - var args = _startupContext.PreservedArguments; - - args += " /restart"; - - if (!args.Contains("/nobrowser")) - { - args += " /nobrowser"; - } - - return args; - } - } -} diff --git a/src/NzbDrone.Host/Startup.cs b/src/NzbDrone.Host/Startup.cs new file mode 100644 index 000000000..7037d3ce1 --- /dev/null +++ b/src/NzbDrone.Host/Startup.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using Lidarr.Api.V1.System; +using Lidarr.Http; +using Lidarr.Http.Authentication; +using Lidarr.Http.ErrorManagement; +using Lidarr.Http.Frontend; +using Lidarr.Http.Middleware; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NLog.Extensions.Logging; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Instrumentation; +using NzbDrone.Common.Processes; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Instrumentation; +using NzbDrone.Host.AccessControl; +using NzbDrone.Http.Authentication; +using NzbDrone.SignalR; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; + +namespace NzbDrone.Host +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddLogging(b => + { + b.ClearProviders(); + b.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); + b.AddFilter("Microsoft.AspNetCore", Microsoft.Extensions.Logging.LogLevel.Warning); + b.AddFilter("Lidarr.Http.Authentication", LogLevel.Information); + b.AddNLog(); + }); + + services.AddRouting(options => options.LowercaseUrls = true); + + services.AddResponseCompression(); + + services.AddCors(options => + { + options.AddPolicy(VersionedApiControllerAttribute.API_CORS_POLICY, + builder => + builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + + options.AddPolicy("AllowGet", + builder => + builder.AllowAnyOrigin() + .WithMethods("GET", "OPTIONS") + .AllowAnyHeader()); + }); + + services + .AddControllers(options => + { + options.ReturnHttpNotAcceptable = true; + }) + .AddApplicationPart(typeof(SystemController).Assembly) + .AddApplicationPart(typeof(StaticResourceController).Assembly) + .AddJsonOptions(options => + { + STJson.ApplySerializerSettings(options.JsonSerializerOptions); + }) + .AddControllersAsServices(); + + services + .AddSignalR() + .AddJsonProtocol(options => + { + options.PayloadSerializerOptions = STJson.GetSerializerSettings(); + }); + + services.AddSingleton(); + services.AddAuthorization(options => + { + options.AddPolicy("SignalR", policy => + { + policy.AuthenticationSchemes.Add("SignalR"); + policy.RequireAuthenticatedUser(); + }); + + // Require auth on everything except those marked [AllowAnonymous] + options.FallbackPolicy = new AuthorizationPolicyBuilder("API") + .RequireAuthenticatedUser() + .Build(); + }); + + services.AddAppAuthentication(); + } + + public void Configure(IApplicationBuilder app, + IStartupContext startupContext, + Lazy mainDatabaseFactory, + Lazy logDatabaseFactory, + DatabaseTarget dbTarget, + ISingleInstancePolicy singleInstancePolicy, + InitializeLogger initializeLogger, + ReconfigureLogging reconfigureLogging, + IAppFolderFactory appFolderFactory, + IProvidePidFile pidFileProvider, + IConfigFileProvider configFileProvider, + IRuntimeInfo runtimeInfo, + IFirewallAdapter firewallAdapter, + LidarrErrorPipeline errorHandler) + { + initializeLogger.Initialize(); + appFolderFactory.Register(); + pidFileProvider.Write(); + + reconfigureLogging.Reconfigure(); + + EnsureSingleInstance(false, startupContext, singleInstancePolicy); + + // instantiate the databases to initialize/migrate them + _ = mainDatabaseFactory.Value; + _ = logDatabaseFactory.Value; + + dbTarget.Register(); + + if (OsInfo.IsNotWindows) + { + Console.CancelKeyPress += (sender, eventArgs) => NLog.LogManager.Configuration = null; + } + + if (OsInfo.IsWindows && runtimeInfo.IsAdmin) + { + firewallAdapter.MakeAccessible(); + } + + app.UseMiddleware(); + app.UsePathBase(new PathString(configFileProvider.UrlBase)); + app.UseExceptionHandler(new ExceptionHandlerOptions + { + AllowStatusCode404Response = true, + ExceptionHandler = errorHandler.HandleException + }); + + app.UseRouting(); + app.UseCors(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseResponseCompression(); + app.Properties["host.AppName"] = BuildInfo.AppName; + + app.UseMiddleware(); + app.UseMiddleware(configFileProvider.UrlBase); + app.UseMiddleware(); + app.UseMiddleware(); + app.UseMiddleware(new List { "/api/v1/command" }); + + app.UseWebSockets(); + + app.UseEndpoints(x => + { + x.MapHub("/signalr/messages").RequireAuthorization("SignalR"); + x.MapControllers(); + }); + } + + private void EnsureSingleInstance(bool isService, IStartupContext startupContext, ISingleInstancePolicy instancePolicy) + { + if (startupContext.Flags.Contains(StartupContext.NO_SINGLE_INSTANCE_CHECK)) + { + return; + } + + if (startupContext.Flags.Contains(StartupContext.TERMINATE)) + { + instancePolicy.KillAllOtherInstance(); + } + else if (startupContext.Args.ContainsKey(StartupContext.APPDATA)) + { + instancePolicy.WarnIfAlreadyRunning(); + } + else if (isService) + { + instancePolicy.KillAllOtherInstance(); + } + else + { + instancePolicy.PreventStartIfAlreadyRunning(); + } + } + } +} diff --git a/src/NzbDrone.Host/Router.cs b/src/NzbDrone.Host/UtilityModeRouter.cs similarity index 76% rename from src/NzbDrone.Host/Router.cs rename to src/NzbDrone.Host/UtilityModeRouter.cs index 833e0b7ba..b260dcade 100644 --- a/src/NzbDrone.Host/Router.cs +++ b/src/NzbDrone.Host/UtilityModeRouter.cs @@ -7,10 +7,13 @@ using IServiceProvider = NzbDrone.Common.IServiceProvider; namespace NzbDrone.Host { - public class Router + public interface IUtilityModeRouter + { + void Route(ApplicationModes applicationModes); + } + + public class UtilityModeRouter : IUtilityModeRouter { - private readonly INzbDroneConsoleFactory _nzbDroneConsoleFactory; - private readonly INzbDroneServiceFactory _nzbDroneServiceFactory; private readonly IServiceProvider _serviceProvider; private readonly IConsoleService _consoleService; private readonly IRuntimeInfo _runtimeInfo; @@ -18,17 +21,13 @@ namespace NzbDrone.Host private readonly IRemoteAccessAdapter _remoteAccessAdapter; private readonly Logger _logger; - public Router(INzbDroneConsoleFactory nzbDroneConsoleFactory, - INzbDroneServiceFactory nzbDroneServiceFactory, - IServiceProvider serviceProvider, + public UtilityModeRouter(IServiceProvider serviceProvider, IConsoleService consoleService, IRuntimeInfo runtimeInfo, IProcessProvider processProvider, IRemoteAccessAdapter remoteAccessAdapter, Logger logger) { - _nzbDroneConsoleFactory = nzbDroneConsoleFactory; - _nzbDroneServiceFactory = nzbDroneServiceFactory; _serviceProvider = serviceProvider; _consoleService = consoleService; _runtimeInfo = runtimeInfo; @@ -43,20 +42,6 @@ namespace NzbDrone.Host switch (applicationModes) { - case ApplicationModes.Service: - { - _logger.Debug("Service selected"); - _serviceProvider.Run(_nzbDroneServiceFactory.Build()); - break; - } - - case ApplicationModes.Interactive: - { - _logger.Debug(_runtimeInfo.IsWindowsTray ? "Tray selected" : "Console selected"); - _nzbDroneConsoleFactory.Start(); - break; - } - case ApplicationModes.InstallService: { _logger.Debug("Install Service selected"); diff --git a/src/NzbDrone.Host/WebHost/ControllerActivator.cs b/src/NzbDrone.Host/WebHost/ControllerActivator.cs deleted file mode 100644 index ad90106fa..000000000 --- a/src/NzbDrone.Host/WebHost/ControllerActivator.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Controllers; -using NzbDrone.Common.Composition; - -namespace NzbDrone.Host -{ - public class ControllerActivator : IControllerActivator - { - private readonly IContainer _container; - - public ControllerActivator(IContainer container) - { - _container = container; - } - - public object Create(ControllerContext context) - { - return _container.Resolve(context.ActionDescriptor.ControllerTypeInfo.AsType()); - } - - public void Release(ControllerContext context, object controller) - { - // Nothing to do - } - } -} diff --git a/src/NzbDrone.Host/WebHost/WebHostController.cs b/src/NzbDrone.Host/WebHost/WebHostController.cs deleted file mode 100644 index 30f307fc0..000000000 --- a/src/NzbDrone.Host/WebHost/WebHostController.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Lidarr.Api.V1.System; -using Lidarr.Http; -using Lidarr.Http.Authentication; -using Lidarr.Http.ErrorManagement; -using Lidarr.Http.Frontend; -using Lidarr.Http.Middleware; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Internal; -using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using NLog; -using NLog.Extensions.Logging; -using NzbDrone.Common.Composition; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Exceptions; -using NzbDrone.Common.Extensions; -using NzbDrone.Common.Serializer; -using NzbDrone.Core.Configuration; -using NzbDrone.Host.AccessControl; -using NzbDrone.SignalR; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; - -namespace NzbDrone.Host -{ - public class WebHostController : IHostController - { - private readonly IContainer _container; - private readonly IRuntimeInfo _runtimeInfo; - private readonly IConfigFileProvider _configFileProvider; - private readonly IFirewallAdapter _firewallAdapter; - private readonly LidarrErrorPipeline _errorHandler; - private readonly Logger _logger; - private IWebHost _host; - - public WebHostController(IContainer container, - IRuntimeInfo runtimeInfo, - IConfigFileProvider configFileProvider, - IFirewallAdapter firewallAdapter, - LidarrErrorPipeline errorHandler, - Logger logger) - { - _container = container; - _runtimeInfo = runtimeInfo; - _configFileProvider = configFileProvider; - _firewallAdapter = firewallAdapter; - _errorHandler = errorHandler; - _logger = logger; - } - - public void StartServer() - { - if (OsInfo.IsWindows && _runtimeInfo.IsAdmin) - { - _firewallAdapter.MakeAccessible(); - } - - var bindAddress = _configFileProvider.BindAddress; - var enableSsl = _configFileProvider.EnableSsl; - var sslCertPath = _configFileProvider.SslCertPath; - - var urls = new List(); - - urls.Add(BuildUrl("http", bindAddress, _configFileProvider.Port)); - - if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace()) - { - urls.Add(BuildUrl("https", bindAddress, _configFileProvider.SslPort)); - } - - _host = new WebHostBuilder() - .UseUrls(urls.ToArray()) - .UseKestrel(options => - { - if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace()) - { - options.ConfigureHttpsDefaults(configureOptions => - { - X509Certificate2 certificate; - - try - { - certificate = new X509Certificate2(sslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet); - } - catch (CryptographicException ex) - { - if (ex.HResult == 0x2 || ex.HResult == 0x2006D080) - { - throw new LidarrStartupException(ex, $"The SSL certificate file {sslCertPath} does not exist"); - } - - throw new LidarrStartupException(ex); - } - - configureOptions.ServerCertificate = certificate; - }); - } - }) - .ConfigureKestrel(serverOptions => - { - serverOptions.AllowSynchronousIO = true; - serverOptions.Limits.MaxRequestBodySize = null; - }) - .ConfigureLogging(logging => - { - logging.AddProvider(new NLogLoggerProvider()); - logging.SetMinimumLevel(LogLevel.Warning); - }) - .ConfigureServices(services => - { - // So that we can resolve containers with our TinyIoC services - services.AddSingleton(_container); - services.AddSingleton(); - - // Bits used in our custom middleware - services.AddSingleton(_container.Resolve()); - services.AddSingleton(_container.Resolve()); - - // Used in authentication - services.AddSingleton(_container.Resolve()); - - services.AddRouting(options => options.LowercaseUrls = true); - - services.AddResponseCompression(); - - services.AddCors(options => - { - options.AddPolicy(VersionedApiControllerAttribute.API_CORS_POLICY, - builder => - builder.AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader()); - - options.AddPolicy("AllowGet", - builder => - builder.AllowAnyOrigin() - .WithMethods("GET", "OPTIONS") - .AllowAnyHeader()); - }); - - services - .AddControllers(options => - { - options.ReturnHttpNotAcceptable = true; - }) - .AddApplicationPart(typeof(SystemController).Assembly) - .AddApplicationPart(typeof(StaticResourceController).Assembly) - .AddJsonOptions(options => - { - STJson.ApplySerializerSettings(options.JsonSerializerOptions); - }); - - services - .AddSignalR() - .AddJsonProtocol(options => - { - options.PayloadSerializerOptions = STJson.GetSerializerSettings(); - }); - - services.AddAuthorization(options => - { - options.AddPolicy("UI", policy => - { - policy.AuthenticationSchemes.Add(_configFileProvider.AuthenticationMethod.ToString()); - policy.RequireAuthenticatedUser(); - }); - - options.AddPolicy("SignalR", policy => - { - policy.AuthenticationSchemes.Add("SignalR"); - policy.RequireAuthenticatedUser(); - }); - - // Require auth on everything except those marked [AllowAnonymous] - options.DefaultPolicy = new AuthorizationPolicyBuilder("API") - .RequireAuthenticatedUser() - .Build(); - }); - - services.AddAppAuthentication(_configFileProvider); - }) - .Configure(app => - { - app.UseMiddleware(); - app.UsePathBase(new PathString(_configFileProvider.UrlBase)); - app.UseExceptionHandler(new ExceptionHandlerOptions - { - AllowStatusCode404Response = true, - ExceptionHandler = _errorHandler.HandleException - }); - - app.UseRouting(); - app.UseCors(); - app.UseAuthentication(); - app.UseAuthorization(); - app.UseResponseCompression(); - app.Properties["host.AppName"] = BuildInfo.AppName; - - app.UseMiddleware(); - app.UseMiddleware(_configFileProvider.UrlBase); - app.UseMiddleware(); - app.UseMiddleware(); - - app.Use((context, next) => - { - if (context.Request.Path.StartsWithSegments("/api/v1/command", StringComparison.CurrentCultureIgnoreCase)) - { - context.Request.EnableBuffering(); - } - - return next(); - }); - - app.UseWebSockets(); - - app.UseEndpoints(x => - { - x.MapHub("/signalr/messages").RequireAuthorization("SignalR"); - x.MapControllers(); - }); - - // This is a side effect of haing multiple IoC containers, TinyIoC and whatever - // Kestrel/SignalR is using. Ideally we'd have one IoC container, but that's non-trivial with TinyIoC - // TODO: Use a single IoC container if supported for TinyIoC or if we switch to another system (ie Autofac). - _container.Register(app.ApplicationServices); - _container.Register(app.ApplicationServices.GetService>()); - _container.Register(app.ApplicationServices.GetService()); - _container.Register(app.ApplicationServices.GetService()); - _container.Register(app.ApplicationServices.GetService()); - }) - .UseContentRoot(Directory.GetCurrentDirectory()) - .Build(); - - _logger.Info("Listening on the following URLs:"); - - foreach (var url in urls) - { - _logger.Info(" {0}", url); - } - - _host.Start(); - } - - public async void StopServer() - { - _logger.Info("Attempting to stop OWIN host"); - - await _host.StopAsync(TimeSpan.FromSeconds(5)); - _host.Dispose(); - _host = null; - - _logger.Info("Host has stopped"); - } - - private string BuildUrl(string scheme, string bindAddress, int port) - { - return $"{scheme}://{bindAddress}:{port}"; - } - } -} diff --git a/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs b/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs index efcccbc83..374a9f984 100644 --- a/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs +++ b/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs @@ -164,7 +164,7 @@ namespace NzbDrone.Test.Common.AutoMoq RegisterPlatformLibrary(container); AddTheAutoMockingContainerExtensionToTheContainer(container); - ContainerBuilderBase.RegisterSQLiteResolver(); + AssemblyLoader.RegisterSQLiteResolver(); } private static void AddTheAutoMockingContainerExtensionToTheContainer(IUnityContainer container) diff --git a/src/NzbDrone.Update/Lidarr.Update.csproj b/src/NzbDrone.Update/Lidarr.Update.csproj index ee11e69ce..34fb6b649 100644 --- a/src/NzbDrone.Update/Lidarr.Update.csproj +++ b/src/NzbDrone.Update/Lidarr.Update.csproj @@ -4,6 +4,8 @@ net5.0 + + diff --git a/src/NzbDrone.Update/UpdateApp.cs b/src/NzbDrone.Update/UpdateApp.cs index e975c9fad..9fd4880fc 100644 --- a/src/NzbDrone.Update/UpdateApp.cs +++ b/src/NzbDrone.Update/UpdateApp.cs @@ -1,11 +1,14 @@ -using System; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using DryIoc; using NLog; -using NzbDrone.Common.Composition; +using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation; +using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Processes; using NzbDrone.Update.UpdateEngine; @@ -18,8 +21,6 @@ namespace NzbDrone.Update private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(UpdateApp)); - private static IContainer _container; - public UpdateApp(IInstallUpdateService installUpdateService, IProcessProvider processProvider) { _installUpdateService = installUpdateService; @@ -35,9 +36,13 @@ namespace NzbDrone.Update Logger.Info("Starting Lidarr Update Client"); - _container = UpdateContainerBuilder.Build(startupContext); - _container.Resolve().Initialize(); - _container.Resolve().Start(args); + var container = new Container(rules => rules.WithNzbDroneRules()) + .AutoAddServices(new List { "Lidarr.Update" }) + .AddNzbDroneLogger() + .AddStartupContext(startupContext); + + container.Resolve().Initialize(); + container.Resolve().Start(args); Logger.Info("Update completed successfully"); } diff --git a/src/NzbDrone.Update/UpdateContainerBuilder.cs b/src/NzbDrone.Update/UpdateContainerBuilder.cs deleted file mode 100644 index 78b487e64..000000000 --- a/src/NzbDrone.Update/UpdateContainerBuilder.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Common.Composition; -using NzbDrone.Common.EnvironmentInfo; - -namespace NzbDrone.Update -{ - public class UpdateContainerBuilder : ContainerBuilderBase - { - private UpdateContainerBuilder(IStartupContext startupContext, List assemblies) - : base(startupContext, assemblies) - { - } - - public static IContainer Build(IStartupContext startupContext) - { - var assemblies = new List - { - "Lidarr.Update" - }; - - return new UpdateContainerBuilder(startupContext, assemblies).Container; - } - } -} diff --git a/src/NzbDrone/Lidarr.csproj b/src/NzbDrone/Lidarr.csproj index 35f57a2c7..9fba95023 100644 --- a/src/NzbDrone/Lidarr.csproj +++ b/src/NzbDrone/Lidarr.csproj @@ -13,6 +13,7 @@ + diff --git a/src/NzbDrone/MessageBoxUserAlert.cs b/src/NzbDrone/MessageBoxUserAlert.cs deleted file mode 100644 index b95eec3de..000000000 --- a/src/NzbDrone/MessageBoxUserAlert.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Windows.Forms; -using NzbDrone.Host; - -namespace NzbDrone -{ - public class MessageBoxUserAlert : IUserAlert - { - public void Alert(string message) - { - MessageBox.Show(text: message, buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Warning, caption: "NzbDrone"); - } - } -} diff --git a/src/NzbDrone/SysTray/SysTrayApp.cs b/src/NzbDrone/SysTray/SysTrayApp.cs index 296394483..9db8aa9bd 100644 --- a/src/NzbDrone/SysTray/SysTrayApp.cs +++ b/src/NzbDrone/SysTray/SysTrayApp.cs @@ -1,6 +1,9 @@ -using System; +using System; using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; +using Microsoft.Extensions.Hosting; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Processes; @@ -8,12 +11,7 @@ using NzbDrone.Host; namespace NzbDrone.SysTray { - public interface ISystemTrayApp - { - void Start(); - } - - public class SystemTrayApp : Form, ISystemTrayApp + public class SystemTrayApp : Form, IHostedService { private readonly IBrowserService _browserService; private readonly IRuntimeInfo _runtimeInfo; @@ -34,8 +32,12 @@ namespace NzbDrone.SysTray Application.ThreadException += OnThreadException; Application.ApplicationExit += OnApplicationExit; + Application.SetHighDpiMode(HighDpiMode.PerMonitor); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + _trayMenu.Items.Add(new ToolStripMenuItem("Launch Browser", null, LaunchBrowser)); - _trayMenu.Items.Add(new ToolStripMenuItem("-")); + _trayMenu.Items.Add(new ToolStripSeparator()); _trayMenu.Items.Add(new ToolStripMenuItem("Exit", null, OnExit)); _trayIcon.Text = string.Format("Lidarr - {0}", BuildInfo.Version); @@ -48,6 +50,20 @@ namespace NzbDrone.SysTray Application.Run(this); } + public Task StartAsync(CancellationToken cancellationToken) + { + var thread = new Thread(Start); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + protected override void OnClosing(CancelEventArgs e) { DisposeTrayIcon(); diff --git a/src/NzbDrone/WindowsApp.cs b/src/NzbDrone/WindowsApp.cs index 076c27a68..361e26d44 100644 --- a/src/NzbDrone/WindowsApp.cs +++ b/src/NzbDrone/WindowsApp.cs @@ -1,5 +1,7 @@ -using System; +using System; using System.Windows.Forms; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Instrumentation; @@ -20,11 +22,9 @@ namespace NzbDrone NzbDroneLogger.Register(startupArgs, false, true); - Bootstrap.Start(startupArgs, new MessageBoxUserAlert(), container => + Bootstrap.Start(args, e => { - container.Register(); - var trayApp = container.Resolve(); - trayApp.Start(); + e.ConfigureServices((_, s) => s.AddSingleton()); }); } catch (Exception e)