using Lampac.Engine; using Lampac.Engine.Middlewares; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.StaticFiles; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Http; using Newtonsoft.Json; using Shared; using Shared.Engine; using Shared.Models.Module; using Shared.Models.Module.Entrys; using Shared.Models.SQL; using Shared.PlaywrightCore; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Lampac { public class Startup { #region Startup static IApplicationBuilder _app = null; public static bool IsShutdown { get; private set; } public IConfiguration Configuration { get; } public static IServiceCollection serviceCollection { get; private set; } public static IMemoryCache memoryCache { get; private set; } public Startup(IConfiguration configuration) { Configuration = configuration; } #endregion #region ConfigureServices public void ConfigureServices(IServiceCollection services) { var init = AppInit.conf; var mods = init.BaseModule; serviceCollection = services; #region IHttpClientFactory services.AddHttpClient("proxy").ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, SslOptions = { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }, PooledConnectionLifetime = TimeSpan.FromMinutes(30), UseCookies = false }); services.AddHttpClient("proxyRedirect").ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.None, SslOptions = { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }, PooledConnectionLifetime = TimeSpan.FromMinutes(30), UseCookies = false }); services.AddHttpClient("proxyimg").ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.None, SslOptions = { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }, PooledConnectionLifetime = TimeSpan.FromMinutes(30), UseCookies = false }); services.AddHttpClient("base").ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip | DecompressionMethods.Deflate, SslOptions = { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }, PooledConnectionLifetime = TimeSpan.FromMinutes(30), UseCookies = false }); services.AddHttpClient("baseNoRedirect").ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip | DecompressionMethods.Deflate, SslOptions = { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }, PooledConnectionLifetime = TimeSpan.FromMinutes(30), UseCookies = false }); services.AddHttpClient("http2", client => { client.DefaultRequestVersion = HttpVersion.Version20; client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; }) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip | DecompressionMethods.Deflate, SslOptions = { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }, PooledConnectionLifetime = TimeSpan.FromMinutes(30), EnableMultipleHttp2Connections = true, UseCookies = false }); services.AddHttpClient("http2proxyimg", client => { client.DefaultRequestVersion = HttpVersion.Version20; client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; }) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.None, SslOptions = { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }, PooledConnectionLifetime = TimeSpan.FromMinutes(30), EnableMultipleHttp2Connections = true, UseCookies = false }); services.AddHttpClient("http2NoRedirect", client => { client.DefaultRequestVersion = HttpVersion.Version20; client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; }) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip | DecompressionMethods.Deflate, SslOptions = { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }, PooledConnectionLifetime = TimeSpan.FromMinutes(30), EnableMultipleHttp2Connections = true, UseCookies = false }); services.AddHttpClient("http3", client => { client.DefaultRequestVersion = HttpVersion.Version30; client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; }) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip | DecompressionMethods.Deflate, SslOptions = { RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true }, PooledConnectionLifetime = TimeSpan.FromMinutes(30), EnableMultipleHttp2Connections = true, UseCookies = false }); services.RemoveAll(); #endregion services.Configure(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); if (init.listen.compression) { services.AddResponseCompression(options => { options.MimeTypes = AppInit.CompressionMimeTypes; }); } services.AddMemoryCache(o => { o.TrackStatistics = AppInit.conf.openstat.enable; }); if (mods.ws) { services.AddSignalR(o => { o.EnableDetailedErrors = true; o.MaximumParallelInvocationsPerClient = 2; o.MaximumReceiveMessageSize = 1024 * 1024 * 10; // 10MB o.StreamBufferCapacity = 1024 * 1024; // 1MB }); } services.AddSingleton(DynamicActionDescriptorChangeProvider.Instance); services.AddSingleton(DynamicActionDescriptorChangeProvider.Instance); services.AddDbContextFactory(HybridCacheContext.ConfiguringDbBuilder); services.AddDbContextFactory(ProxyLinkContext.ConfiguringDbBuilder); if (mods.Sql.syncUser) services.AddDbContextFactory(SyncUserContext.ConfiguringDbBuilder); if (mods.Sql.sisi) services.AddDbContextFactory(SisiContext.ConfiguringDbBuilder); if (mods.Sql.externalids) services.AddDbContextFactory(ExternalidsContext.ConfiguringDbBuilder); IMvcBuilder mvcBuilder = services.AddControllersWithViews(); mvcBuilder.AddJsonOptions(options => { //options.JsonSerializerOptions.IgnoreNullValues = true; options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault; }); #region module references string referencesPath = Path.Combine(Environment.CurrentDirectory, "module", "references"); if (Directory.Exists(referencesPath)) { var current = AppDomain.CurrentDomain.GetAssemblies(); foreach (string dllFile in Directory.GetFiles(referencesPath, "*.dll", SearchOption.AllDirectories)) { try { string loadedName = Path.GetFileNameWithoutExtension(dllFile); if (current.Any(a => string.Equals(a.GetName().Name, loadedName, StringComparison.OrdinalIgnoreCase))) continue; Assembly loadedAssembly = Assembly.LoadFrom(dllFile); mvcBuilder.AddApplicationPart(loadedAssembly); Program.assemblieReferences.Add(MetadataReference.CreateFromFile(loadedAssembly.Location)); Console.WriteLine($"load reference: {Path.GetFileName(dllFile)}"); } catch (Exception ex) { Console.WriteLine($"Failed to load reference {dllFile}: {ex.Message}"); } } } #endregion ModuleRepository.Configuration(mvcBuilder); Shared.Startup.Configure(Program.appReload, new NativeWebSocket(), new soks()); BaseModControllers(mvcBuilder); #region compilation modules if (AppInit.modules != null) { // mod.dll foreach (var mod in AppInit.modules) { try { Console.WriteLine("load module: " + mod.dll); mvcBuilder.AddApplicationPart(mod.assembly); } catch (Exception ex) { Console.WriteLine(ex.Message + "\n"); } } } // dll source if (File.Exists("module/manifest.json")) { var jss = new JsonSerializerSettings { Error = (se, ev) => { ev.ErrorContext.Handled = true; Console.WriteLine("module/manifest.json - " + ev.ErrorContext.Error + "\n\n"); } }; var modules = JsonConvert.DeserializeObject>(File.ReadAllText("module/manifest.json"), jss); if (modules == null) return; #region CompilationMod void CompilationMod(RootModule mod) { if (!mod.enable || AppInit.modules.FirstOrDefault(i => i.dll == mod.dll) != null) return; if (mod.dll.EndsWith(".dll")) { try { mod.assembly = Assembly.LoadFrom(mod.dll); AppInit.modules.Add(mod); mvcBuilder.AddApplicationPart(mod.assembly); Console.WriteLine($"load module: {Path.GetFileName(mod.dll)}"); } catch (Exception ex) { Console.WriteLine($"Failed to load reference {mod.dll}: {ex.Message}"); } return; } string path = Directory.Exists(mod.dll) ? mod.dll : $"{Environment.CurrentDirectory}/module/{mod.dll}"; if (Directory.Exists(path)) { var syntaxTree = new List(); foreach (string file in Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories)) { string _file = file.Replace("\\", "/").Replace(path.Replace("\\", "/"), "").Replace(Environment.CurrentDirectory.Replace("\\", "/"), ""); if (Regex.IsMatch(_file, "(\\.vs|bin|obj|Properties)/", RegexOptions.IgnoreCase)) continue; syntaxTree.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(file))); } if (mod.references != null) { foreach (string refns in mod.references) { string dlrns = Path.Combine(Environment.CurrentDirectory, "module", "references", refns); if (!File.Exists(dlrns)) dlrns = Path.Combine(Environment.CurrentDirectory, "module", mod.dll, refns); if (File.Exists(dlrns) && Program.assemblieReferences.FirstOrDefault(a => Path.GetFileName(a.FilePath) == refns) == null) { var assembly = Assembly.LoadFrom(dlrns); Program.assemblieReferences.Add(MetadataReference.CreateFromFile(assembly.Location)); } } } CSharpCompilation compilation = CSharpCompilation.Create(Path.GetFileName(mod.dll), syntaxTree, references: Program.assemblieReferences, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (var ms = new MemoryStream()) { var result = compilation.Emit(ms); if (!result.Success) { Console.WriteLine($"\ncompilation error: {mod.dll}"); foreach (var diagnostic in result.Diagnostics) { if (diagnostic.Severity == DiagnosticSeverity.Error) Console.WriteLine(diagnostic); } Console.WriteLine(); } else { ms.Seek(0, SeekOrigin.Begin); mod.assembly = Assembly.Load(ms.ToArray()); Console.WriteLine("compilation module: " + mod.dll); mod.index = mod.index != 0 ? mod.index : (100 + AppInit.modules.Count); AppInit.modules.Add(mod); mvcBuilder.AddApplicationPart(mod.assembly); WatchersDynamicModule(null, mvcBuilder, mod, path); } } } } #endregion foreach (var mod in modules) CompilationMod(mod); foreach (string folderMod in Directory.GetDirectories("module/")) { string manifest = $"{Environment.CurrentDirectory}/{folderMod}/manifest.json"; if (!File.Exists(manifest)) continue; var mod = JsonConvert.DeserializeObject(File.ReadAllText(manifest), jss); if (mod != null) { if (mod.dll == null) mod.dll = folderMod.Split("/")[1]; else if (mod.dll.EndsWith(".dll")) mod.dll = Path.Combine(folderMod, mod.dll); CompilationMod(mod); } } if (Program.assemblieReferences != null) CSharpEval.appReferences = Program.assemblieReferences; } if (AppInit.modules != null) AppInit.modules = AppInit.modules.OrderBy(i => i.index).ToList(); Console.WriteLine(); #endregion } #endregion public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IMemoryCache memory, IHttpClientFactory httpClientFactory, IHostApplicationLifetime applicationLifetime) { _app = app; memoryCache = memory; var init = AppInit.conf; var mods = init.BaseModule; var midd = mods.Middlewares; #region IDbContextFactory HybridCacheContext.Factory = app.ApplicationServices.GetService>(); ProxyLinkContext.Factory = app.ApplicationServices.GetService>(); if (mods.Sql.externalids) ExternalidsContext.Factory = app.ApplicationServices.GetService>(); if (mods.Sql.sisi) SisiContext.Factory = app.ApplicationServices.GetService>(); if (mods.Sql.syncUser) SyncUserContext.Factory = app.ApplicationServices.GetService>(); #endregion Shared.Startup.Configure(app, memory); HybridCache.Configure(memory); HybridFileCache.Configure(memory); ProxyManager.Configure(memory); Http.httpClientFactory = httpClientFactory; if (mods.nws) { NativeWebSocket.memoryCache = memoryCache; Http.nws = new NativeWebSocket(); } if (mods.ws) Http.ws = new soks(); #region modules loaded if (AppInit.modules != null) { foreach (var mod in AppInit.modules) { try { if (mod.dll == "DLNA.dll") mod.initspace = "DLNA.ModInit"; if (mod.dll == "SISI.dll") mod.initspace = "SISI.ModInit"; if (mod.dll == "Tracks.dll" || mod.dll == "TorrServer.dll") mod.version = 2; LoadedModule(app, mod); } catch (Exception ex) { Console.WriteLine($"Module {mod.NamespacePath(mod.initspace)}: {ex.Message}\n\n"); } } } #endregion app.UseBaseMod(); if (!init.multiaccess || init.useDeveloperExceptionPage) app.UseDeveloperExceptionPage(); applicationLifetime.ApplicationStopping.Register(OnShutdown); applicationLifetime.ApplicationStarted.Register(() => { if (!string.IsNullOrEmpty(init.listen.sock)) _ = Bash.Run($"while [ ! -S /var/run/{init.listen.sock}.sock ]; do sleep 1; done && chmod 666 /var/run/{init.listen.sock}.sock").ConfigureAwait(false); }); #region UseForwardedHeaders var forwarded = new ForwardedHeadersOptions { ForwardLimit = null, ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }; if (init.KnownProxies != null && init.KnownProxies.Count > 0) { foreach (var k in init.KnownProxies) forwarded.KnownNetworks.Add(new Microsoft.AspNetCore.HttpOverrides.IPNetwork(IPAddress.Parse(k.ip), k.prefixLength)); } app.UseForwardedHeaders(forwarded); #endregion app.UseModHeaders(); app.UseRequestInfo(); if (mods.nws) { app.Map("/nws", nwsApp => { nwsApp.UseWAF(); nwsApp.UseWebSockets(); nwsApp.Run(NativeWebSocket.HandleWebSocketAsync); }); } if (mods.ws) { app.Map("/ws", wsApp => { wsApp.UseWAF(); wsApp.UseRouting(); wsApp.UseEndpoints(endpoints => { endpoints.MapHub(""); }); }); } if (midd.staticache) app.UseStaticache(); app.UseRouting(); if (init.listen.compression) app.UseResponseCompression(); if (midd.statistics) app.UseRequestStatistics(); app.UseAnonymousRequest(); app.UseAlwaysRjson(); if (midd.module) app.UseModule(first: true); app.UseOverrideResponse(first: true); #region UseStaticFiles if (midd.staticFiles) { var contentTypeProvider = new FileExtensionContentTypeProvider(); if (midd.staticFilesMappings != null) { foreach (var mapping in midd.staticFilesMappings) contentTypeProvider.Mappings[mapping.Key] = mapping.Value; } app.UseStaticFiles(new StaticFileOptions { ServeUnknownFileTypes = midd.unknownStaticFiles, DefaultContentType = "application/octet-stream", ContentTypeProvider = contentTypeProvider }); } #endregion app.UseWAF(); app.UseAccsdb(); if (midd.proxy) { app.MapWhen(context => context.Request.Path.Value.StartsWith("/proxy/") || context.Request.Path.Value.StartsWith("/proxy-dash/"), proxyApp => { proxyApp.UseProxyAPI(); }); } if (midd.proxyimg) { app.MapWhen(context => context.Request.Path.Value.StartsWith("/proxyimg"), proxyApp => { proxyApp.UseProxyIMG(); }); } if (midd.proxycub) { app.MapWhen(context => context.Request.Path.Value.StartsWith("/cub/"), proxyApp => { proxyApp.UseProxyCub(); }); } if (midd.proxytmdb) { app.MapWhen(context => context.Request.Path.Value.StartsWith("/tmdb/"), proxyApp => { proxyApp.UseProxyTmdb(); }); } if (midd.module) app.UseModule(first: false); app.UseOverrideResponse(first: false); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } #region OnShutdown void OnShutdown() { if (Program._reload) return; IsShutdown = true; Shared.Startup.IsShutdown = true; Chromium.FullDispose(); Firefox.FullDispose(); NativeWebSocket.FullDispose(); soks.FullDispose(); DisposeModule(null); } #endregion #region BaseModControllers public static bool WebLogEnableController { get; private set; } public void BaseModControllers(IMvcBuilder mvcBuilder) { if (AppInit.conf?.BaseModule?.EnableControllers == null || AppInit.conf.BaseModule.EnableControllers.Length == 0) return; WebLogEnableController = AppInit.conf.BaseModule.EnableControllers.Contains("WebLog"); var syntaxTree = new List(); string patchcontrol = Path.Combine("basemod", "Controllers"); if (!Directory.Exists(patchcontrol)) patchcontrol = "../../../../../BaseModule/Controllers"; foreach (string file in Directory.GetFiles(patchcontrol, "*.cs", SearchOption.AllDirectories)) { string name = Path.GetFileName(file).Replace("Controller.cs", ""); if (name.Equals("Cmd", StringComparison.OrdinalIgnoreCase) && AppInit.conf.cmd.Count == 0) continue; if (name.Equals("SyncApi", StringComparison.OrdinalIgnoreCase) && !AppInit.conf.sync.enable) continue; if (AppInit.conf.BaseModule.EnableControllers.Contains(name, StringComparer.OrdinalIgnoreCase)) syntaxTree.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(file))); } CSharpCompilation compilation = CSharpCompilation.Create("basemod", syntaxTree, references: Program.assemblieReferences, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (var ms = new MemoryStream()) { var result = compilation.Emit(ms); if (result.Success) { ms.Seek(0, SeekOrigin.Begin); var assembly = Assembly.Load(ms.ToArray()); mvcBuilder.AddApplicationPart(assembly); } else { Console.WriteLine($"\ncompilation error: basemod"); foreach (var diagnostic in result.Diagnostics) { if (diagnostic.Severity == DiagnosticSeverity.Error) Console.WriteLine(diagnostic); } Console.WriteLine(); } } } #endregion #region WatchRebuildModule static readonly Dictionary moduleWatchers = new(); static readonly object moduleWatcherLock = new object(); void WatchersDynamicModule(IApplicationBuilder app, IMvcBuilder mvcBuilder, RootModule mod, string path) { if (!mod.dynamic) return; path = Path.GetFullPath(path); lock (moduleWatcherLock) { if (moduleWatchers.ContainsKey(path)) return; var watcher = new FileSystemWatcher(path) { IncludeSubdirectories = true, NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size }; watcher.Filters.Add("*.cs"); watcher.Filters.Add("manifest.json"); CancellationTokenSource debounceCts = null; object debounceLock = new object(); void Recompile(object sender, FileSystemEventArgs e) { string _file = e.FullPath.Replace("\\", "/").Replace(path.Replace("\\", "/"), "").Replace(Environment.CurrentDirectory.Replace("\\", "/"), ""); if (Regex.IsMatch(_file, "(\\.vs|bin|obj|Properties)/", RegexOptions.IgnoreCase)) return; CancellationTokenSource cts; lock (debounceLock) { debounceCts?.Cancel(); debounceCts = new CancellationTokenSource(); cts = debounceCts; } _ = Task.Run(async () => { await Task.Delay(TimeSpan.FromSeconds(2), cts.Token); if (cts.IsCancellationRequested) return; watcher.EnableRaisingEvents = false; try { var parts = mvcBuilder.PartManager.ApplicationParts .OfType() .Where(p => p.Assembly == mod.assembly) .ToList(); #region update manifest.json string manifestPath = Path.Combine(path, "manifest.json"); RootModule manifestMod = null; if (File.Exists(manifestPath)) { try { manifestMod = JsonConvert.DeserializeObject(File.ReadAllText(manifestPath)); var excludedProperties = new HashSet(StringComparer.OrdinalIgnoreCase) { nameof(RootModule.dynamic), nameof(RootModule.index), nameof(RootModule.dll), nameof(RootModule.assembly), nameof(RootModule.initspace) }; foreach (var property in typeof(RootModule).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (!property.CanRead || !property.CanWrite || excludedProperties.Contains(property.Name)) continue; property.SetValue(mod, property.GetValue(manifestMod)); } } catch (Exception manifestEx) { Console.WriteLine($"Failed to update manifest for {mod.dll}: {manifestEx.Message}"); } } #endregion var assembly = CSharpEval.Compilation(mod); if (assembly != null) { DisposeModule(mod); foreach (var part in parts) mvcBuilder.PartManager.ApplicationParts.Remove(part); if (manifestMod != null) mod.initspace = manifestMod.initspace; mod.assembly = assembly; LoadedModule(app, mod); mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(mod.assembly)); DynamicActionDescriptorChangeProvider.Instance.NotifyChanges(); MiddlewaresModuleEntry.EnsureCache(forced: true); OnlineModuleEntry.EnsureCache(forced: true); SisiModuleEntry.EnsureCache(forced: true); Console.WriteLine("rebuild module: " + mod.dll); } } catch (Exception ex) { Console.WriteLine($"Failed to rebuild module {mod.dll}: {ex.Message}"); } finally { watcher.EnableRaisingEvents = true; } }); } watcher.Changed += Recompile; watcher.Created += Recompile; watcher.Deleted += Recompile; watcher.Renamed += Recompile; watcher.EnableRaisingEvents = true; moduleWatchers[path] = watcher; } } #endregion #region LoadedModule void LoadedModule(IApplicationBuilder app, RootModule mod) { if (mod == null) return; if (mod.initspace != null && mod.assembly.GetType(mod.NamespacePath(mod.initspace)) is Type t && t.GetMethod("loaded") is MethodInfo m) { if (mod.version >= 2) { m.Invoke(null, [ new InitspaceModel() { path = $"module/{mod.dll}", soks = new soks(), nws = new NativeWebSocket(), memoryCache = memoryCache, configuration = Configuration, services = serviceCollection, app = app ?? _app } ]); } else m.Invoke(null, []); } } #endregion #region DisposeModule void DisposeModule(RootModule module) { if (AppInit.modules == null) return; if (module != null) { try { if (module.initspace != null && module.assembly.GetType(module.NamespacePath(module.initspace)) is Type t && t.GetMethod("Dispose") is MethodInfo m) m.Invoke(null, []); } catch { } } else { foreach (var mod in AppInit.modules) { try { if (mod.initspace != null && mod.assembly.GetType(mod.NamespacePath(mod.initspace)) is Type t && t.GetMethod("Dispose") is MethodInfo m) m.Invoke(null, []); } catch { } } } } #endregion } }