public static ApiResponseServerMethodBase Exception(Exception ex) { SessionLog.Exception(ex); // TODO: Record ServerMethod detail var id = ExceptionLogger.LogException(ex, (Controllers.ExecController.ExecOptions)null); var ret = new ApiResponseServerMethodBase(); ret.Error = $"Error ref: {id}"; ret.Type = ApiResponseType.Exception; return(ret); }
public static void Start() { try { var endpoints = SettingsInstance.Instance.ProjectList.SelectMany(p => p.Applications).SelectMany(app => app.Endpoints).ToList(); WorkSpawner.TEMPLATE_RoutineContainer = File.ReadAllText("./resources/RoutineContainerTemplate.txt"); WorkSpawner.TEMPLATE_Routine = File.ReadAllText("./resources/RoutineTemplate.txt"); WorkSpawner.TEMPLATE_TypescriptDefinitions = File.ReadAllText("./resources/TypeScriptDefinitionsContainer.d.ts"); WorkSpawner._workerList = new List <Worker>(); //dbSources = new DatabaseSource[] { dbSources.First() }.ToList(); //TEMP // TODO: handle items (project/sources) that were deleted //async.each(dbSources, (source) => { endpoints.ForEach(endpoint => { //TEST!! // if (endpoint.Name != "DEV" || endpoint.Application.Name != "PWAs") return; try { CreateNewWorker(endpoint); } catch (Exception e) { Log.Error(e, $"Failed to create new work for {endpoint.Pedigree}"); ExceptionLogger.LogException(e); } }); Hubs.WorkerMonitor.Instance.NotifyObservers(); } catch (Exception e) { SessionLog.Exception(e); Log.Error(e, "Work spawner error on start"); } } // Start
public void AddPlugin(PluginInfo plugin) { try { this._plugins.Add(plugin); if (plugin.Type == PluginType.BackgroundThread) { BackgroundThreadPluginManager.Instance.Register(plugin); } else if (plugin.Type == PluginType.ServerMethod) { ServerMethodManager.Register(InstanceId, plugin); } } catch (Exception ex) { SessionLog.Exception(ex); } }
public static void RemoveEndpoint(Endpoint endpoint) { try { lock (_workerList) { var worker = GetWorkerByEndpoint(endpoint); if (worker != null) { worker.Stop(); _workerList.Remove(worker); Hubs.WorkerMonitor.Instance.NotifyObservers(); } } } catch (Exception ex) { SessionLog.Exception(ex); } }
public override void Init() { try { _throttleTracker = new Dictionary <string, List <DateTime> >(); _database = new LiteDB.LiteDatabase("data/exceptions.db"); var exceptionCollection = _database.GetCollection <ExceptionWrapper>("Exceptions"); exceptionCollection.EnsureIndex("sId", unique: true); exceptionCollection.EnsureIndex("EndpointKey", unique: false); base.Init(); } catch (Exception ex) { SessionLog.Exception(ex); Log.Error(ex, "Failed to initiate Exceptions DB"); } }
private static PluginSetParameterValue GetParameterValueFromPlugins(CachedRoutine routine, string parameterName, List <ExecutionPlugin> plugins) { foreach (var plugin in plugins) { try { var val = plugin.GetParameterValue(routine.Schema, routine.Routine, parameterName); if (val != PluginSetParameterValue.DontSet) { return(val); } } catch (Exception ex) { SessionLog.Error("Plugin {0} GetParameterValue failed", plugin.Name); SessionLog.Exception(ex); } } return(PluginSetParameterValue.DontSet); }
private (List <ResultSetFieldMetadata>, string) ExtractResultSetMetadata(string resultSetXml) { try { var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(SqlResultSetFieldCollection)); resultSetXml = $"<FieldCollection>{resultSetXml}</FieldCollection>"; using (var sr = new StringReader(resultSetXml)) { var val = (xmlSerializer.Deserialize(sr) as Settings.ObjectModel.SqlResultSetFieldCollection); // look for error if (val.Fields.Count > 0 && !string.IsNullOrWhiteSpace(val.Fields[0].ErrorMsg)) { var f = val.Fields[0]; return(null, $"({f.ErrorDescription}) {f.ErrorMsg}"); } else { return(val.Fields.Select(f => new ResultSetFieldMetadata() { ColumnName = f.Name, DataType = RoutineParameterV2.GetCSharpDataTypeFromSqlDbType(f.Type), DbDataType = f.Type, ColumnSize = f.Size, NumericalPrecision = f.Precision, NumericalScale = f.Scale }).ToList(), null); } } } catch (Exception ex) { SessionLog.Exception(ex); return(null, "Failed to parse ResultSetXml:" + ex.Message); } }
private void ParseAndLoadPluginAssembly(AssemblyLoadContext asmCtx, Assembly assembly, string inlineEntryId = null) { if (ParsePluginAssembly(assembly, out var pluginInfoList, out var errorList, checkForConflict: true)) { foreach (var pluginInfo in pluginInfoList) { SessionLog.Info($"{(inlineEntryId != null ? "(Inline) " : "")}Plugin '{pluginInfo.Name}' ({pluginInfo.Guid}) found in assembly: {assembly.FullName}"); var existing = PluginAssemblies.FirstOrDefault(a => a.Assembly == assembly); if (existing == null) { var newPA = new PluginAssembly(asmCtx, assembly, inlineEntryId); newPA.AddPlugin(pluginInfo); _pluginAssemblies.Add(newPA); } else { existing.AddPlugin(pluginInfo); } } } }
private void CompileCodeIntoAssemblyContext(InlineModuleManifestEntry inlineEntry, string code) { var ctxName = $"Inline Plugin Context {++_asmCtxCounter}"; var pluginPath = Path.GetFullPath("plugins"); var asmCtx = new PluginAssemblyLoadContext(pluginPath, ctxName, true /*enable unloading*/); ASM_CTXES.Add(asmCtx); SessionLog.Info($"Created {ctxName} for {inlineEntry.Name}".PadRight(35)); var assemblyBytes = CSharpCompilerHelper.CompileIntoAssembly(inlineEntry.Name, code, out var problems); if ((problems != null && problems.Count == 0)) { Assembly assembly = null; try { using (var ms = new MemoryStream(assemblyBytes)) { assembly = asmCtx.LoadFromStream(ms); ParseAndLoadPluginAssembly(asmCtx, assembly, inlineEntry.Id); } } catch (Exception ee) { SessionLog.Error($"Failed to load inline plugin assembly '{assembly?.FullName}' {inlineEntry.Name}/{inlineEntry.Id}. See exception that follows."); SessionLog.Exception(ee); } } else { SessionLog.Error($"Inline plugin {inlineEntry.Name} ({inlineEntry.Id}) failed to compile with the following error(s): {string.Join(", ", problems)}"); } }
public void Run() { try { Thread.CurrentThread.Name = "WorkerThread " + this.Endpoint.Pedigree; this.Status = "Started"; this.IsRunning = true; this.IsOutputFilesDirty = false; //DateTime lastSavedDate = DateTime.Now; var cache = this.Endpoint.CachedRoutines; if (cache != null && cache.Count > 0) { this.MaxRowDate = cache.Max(c => c.RowVer); } int connectionOpenErrorCnt = 0; if (this.Endpoint?.MetadataConnection?.ConnectionStringDecrypted == null) { this.IsRunning = false; this.Status = $"Endpoint '{this.Endpoint?.Pedigree ?? "(null)"}' does not have a valid metadata connection configured."; this.log.Error(this.Status); SessionLog.Error(this.Status); return; } var exceptionThrottler = new SortedList <DateTime, Exception>(); while (this.IsRunning) { // look for new instructions first lock (_instructionQueue) { while (_instructionQueue.Count > 0) { var ins = _instructionQueue.Dequeue(); if (ins.Type == WorkerInstructionType.RegenAllFiles) { this.ForceGenerateAllOutputFiles(this.Endpoint); } else if (ins.Type == WorkerInstructionType.RegenSpecificFile) { this.ForceGenerateOutputFile(this.Endpoint, ins.JsFile); } } } isIterationDirty = false; string connectionStringRefForLog = null; try { if (!string.IsNullOrEmpty(Endpoint.PullMetadataFromEndpointId)) { var ep = Settings.SettingsInstance.Instance.FindEndpointById(Endpoint.PullMetadataFromEndpointId); if (ep != null) { this.Status = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm")} - Metadata pulled from {ep.Pedigree}"; } else { this.Status = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm")} - ERROR. Metadata configured to pull from endpoint with Id {Endpoint.PullMetadataFromEndpointId} but source endpoint not found."; } this.IsRunning = false; continue; } else if (Endpoint.DisableMetadataCapturing) { this.Status = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm")} - Metadata capturing disabled"; this.IsRunning = false; continue; } else if (!Endpoint.IsOrmInstalled) { // try again in 3 seconds this.Status = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm")} - Waiting for ORM to be installed"; Thread.Sleep(3000); continue; } var csb = new SqlConnectionStringBuilder(this.Endpoint.MetadataConnection.ConnectionStringDecrypted); connectionStringRefForLog = $"Data Source={csb.DataSource}; UserId={csb.UserID}; Catalog={csb.InitialCatalog}"; var appName = $"jsdal-server worker {this.Endpoint.Pedigree} {System.Environment.MachineName}"; csb.ApplicationName = appName.Left(128); var connectionStringDecrypted = csb.ToString(); using (var con = new SqlConnection(connectionStringDecrypted)) { try { con.Open(); connectionOpenErrorCnt = 0; } catch (Exception oex) { this.Status = "Failed to open connection to database: " + oex.Message; this.log.Exception(oex, connectionStringRefForLog); connectionOpenErrorCnt++; int waitMS = Math.Min(3000 + (connectionOpenErrorCnt * 3000), 300000 /*Max 5mins between tries*/); this.Status = $"Attempt: #{connectionOpenErrorCnt + 1} (waiting for {waitMS / 1000} secs). " + this.Status; Hubs.WorkerMonitor.Instance.NotifyObservers(); Thread.Sleep(waitMS); continue; } ProcessAsync(con, connectionStringDecrypted).Wait(); } // using connection if (isIterationDirty) { Hubs.WorkerMonitor.Instance.NotifyObservers(); } Thread.Sleep(SettingsInstance.Instance.Settings.DbSource_CheckForChangesInMilliseconds); } catch (Exception ex) { this.log.Exception(ex); exceptionThrottler.Add(DateTime.Now, ex); var thresholdDate = DateTime.Now.AddSeconds(-60); var beforeThreshold = exceptionThrottler.Where(kv => kv.Key < thresholdDate).ToList(); // remove items outside of threshold checking window for (int i = 0; i < beforeThreshold.Count; i++) { exceptionThrottler.Remove(beforeThreshold[i].Key); } // TODO: make threshold count configurable if (exceptionThrottler.Count() >= 6) { exceptionThrottler.Clear(); this.Status = $"{ DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} - Too many exceptions, shutting down thread for now. Last exception: {ex.Message}"; Hubs.WorkerMonitor.Instance.NotifyObservers(); break; // break out of main while loop } this.Status = $"{ DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} - {ex.Message}"; Hubs.WorkerMonitor.Instance.NotifyObservers(); var sleepTimeMS = 2000 + (exceptionThrottler.Count() * 400); // cap at 8 secs sleepTimeMS = sleepTimeMS > 8000 ? 8000 : sleepTimeMS; Thread.Sleep(sleepTimeMS); } } // while IsRunning } catch (ThreadAbortException) { // ignore TAEs } catch (Exception ex) { this.log.Exception(ex); } finally { this.IsRunning = false; this.winThread = null; Hubs.WorkerMonitor.Instance.NotifyObservers(); } } // Run
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IHostApplicationLifetime applicationLifetime) { //app.UseDeveloperExceptionPage(); // force instantiation on singletons { var pmInst = app.ApplicationServices.GetService <PluginLoader>(); CommonNotificationThread.Instance = app.ApplicationServices.GetService <CommonNotificationThread>(); BackgroundThreadPluginManager.Instance = app.ApplicationServices.GetService <BackgroundThreadPluginManager>(); WorkerMonitor.Instance = app.ApplicationServices.GetService <WorkerMonitor>(); RealtimeMonitor.Instance = app.ApplicationServices.GetService <RealtimeMonitor>(); BackgroundTaskMonitor.Instance = app.ApplicationServices.GetService <BackgroundTaskMonitor>(); ConnectionStringSecurity.Instance = app.ApplicationServices.GetService <ConnectionStringSecurity>(); DotNetCoreCounterListener.Instance = app.ApplicationServices.GetService <DotNetCoreCounterListener>(); DotNetCoreCounterListener.Instance.Start(); {// More app startup stuff...but have a dependency on the singleton objects above. Can we move this somewhere else? Log.Information("Initialising project object model"); // we can only initialise the Project structure once ConnectionStringSecurity exists Settings.SettingsInstance.Instance.ProjectList.ForEach(p => p.AfterDeserializationInit()); Log.Information("Initialising plugin loader"); PluginLoader.Instance = pmInst; PluginLoader.Instance.InitAsync().GetAwaiter().GetResult(); ServerMethodManager.RebuildCacheForAllApps(); Log.Information("Starting work spawner."); WorkSpawner.Start(); } } applicationLifetime.ApplicationStopped.Register(() => { Log.Information("Application stopped"); }); applicationLifetime.ApplicationStopping.Register(() => { Log.Information("Application is shutting down"); }); // app.Use(async (httpContext, next) => // { // //if (httpContext.Request.Path.Value.Contains("api/") && httpContext.Request.Method == "OPTIONS") // if (httpContext.Request.Method.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) // { // httpContext.Response.Headers.Add("Access-Control-Max-Age", "600"); // // return; // } // await next(); // }); var webSocketOptions = new WebSocketOptions() { KeepAliveInterval = TimeSpan.FromSeconds(120), ReceiveBufferSize = 4 * 1024, }; app.UseWebSockets(webSocketOptions); app.UseCors("CorsPolicy"); // SPA (angular) route fallback app.Use(async(context, next) => { await next(); if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value) && !(context.Request.Path.Value?.ToLower().StartsWith("/api/") ?? false)) { context.Request.Path = "/index.html"; context.Response.StatusCode = 200; await next(); } }); /***** * var mirrorSharpOptions = new MirrorSharpOptions() * { * SelfDebugEnabled = true, * IncludeExceptionDetails = true, * //SetOptionsFromClient = SetOptionsFromClientExtension() * // CSharp = { * // MetadataReferences = ImmutableList.Create<MetadataReference>(all), * // CompilationOptions = compilationOptions * // }, * ExceptionLogger = new MirrorSharpExceptionLogger() * }.SetupCSharp(cs => * { * //cs.MetadataReferences = cs.MetadataReferences.Clear(); * //cs.AddMetadataReferencesFromFiles(all); * cs.MetadataReferences = ImmutableList.Create<MetadataReference>(all); * cs.CompilationOptions = compilationOptions; * * }); * * * app.UseMirrorSharp(mirrorSharpOptions); */ app.UseDefaultFiles(); app.UseStaticFiles(); // TODO: This outputs full request detail into log. Perhaps consider outputting this to a different detailed log //app.UseSerilogRequestLogging(); app.UseSerilogRequestLogging(options => { options.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); // Customize the message template //HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms options.MessageTemplate = "Req/Res: {ReqLen,7} {ResLen,7} {StatusCode} {Elapsed,7:0} ms {RequestMethod,4} {RequestPath}"; //options.GetLevel = (httpContext, elapsed, ex) => Serilog.Events.LogEventLevel.Warning; // Attach additional properties to the request completion event options.EnrichDiagnosticContext = (diagnosticContext, httpContext) => { diagnosticContext.Set("ResLen", httpContext.Response.ContentLength ?? 0); diagnosticContext.Set("ReqLen", httpContext.Request.ContentLength ?? 0); }; }); app.UseRouting(); MetadataReference[] allMetadataReferences = null; try { allMetadataReferences = CSharpCompilerHelper.GetCommonMetadataReferences(); var jsDALBasePluginPath = Path.GetFullPath("./plugins/jsdal-plugin.dll"); if (File.Exists("./plugins/jsdal-plugin.dll")) { Array.Resize(ref allMetadataReferences, allMetadataReferences.Length + 1); allMetadataReferences[allMetadataReferences.Length - 1] = Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(jsDALBasePluginPath); } else { Log.Error($"Failed to find base plugin assembly at {jsDALBasePluginPath}"); SessionLog.Error($"Failed to find base plugin assembly at {jsDALBasePluginPath}"); } } catch (Exception mex) { Log.Error(mex, "Failed to compile collection of metadata references"); } app.UseEndpoints(endpoints => { endpoints.MapHub <Hubs.HomeDashboardHub>("/main-stats"); endpoints.MapHub <Hubs.WorkerDashboardHub>("/worker-hub"); endpoints.MapHub <Hubs.Performance.RealtimeHub>("/performance-realtime-hub"); endpoints.MapHub <Hubs.HeartBeat.HeartBeatHub>("/heartbeat"); endpoints.MapHub <Hubs.BackgroundTaskHub>("/bgtasks-hub"); endpoints.MapHub <Hubs.BackgroundPluginHub>("/bgplugin-hub"); endpoints.MapHub <Hubs.ExecHub>("/exec-hub"); if (allMetadataReferences?.Length > 0) { var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, // generalDiagnosticOption: ReportDiagnostic.Suppress specificDiagnosticOptions: new Dictionary <string, ReportDiagnostic> { { "CS1701", ReportDiagnostic.Suppress }, // Binding redirects { "CS1702", ReportDiagnostic.Suppress }, { "CS1705", ReportDiagnostic.Suppress } } ); var mirrorSharpOptions = new MirrorSharpOptions() { SelfDebugEnabled = true, IncludeExceptionDetails = true //SetOptionsFromClient = SetOptionsFromClientExtension() // CSharp = { // MetadataReferences = ImmutableList.Create<MetadataReference>(all), // CompilationOptions = compilationOptions // }, // ExceptionLogger = new MirrorSharpExceptionLogger() }.SetupCSharp(cs => { //cs.MetadataReferences = cs.MetadataReferences.Clear(); //cs.AddMetadataReferencesFromFiles(all); cs.MetadataReferences = ImmutableList.Create <MetadataReference>(allMetadataReferences); cs.CompilationOptions = compilationOptions; }); endpoints.MapMirrorSharp("/mirrorsharp", mirrorSharpOptions); } else { Log.Warning("Mirrorsharp not started because of errors builiding up metadata references"); } }); app.UseAuthentication(); app.UseWebSockets(); app.UseCookiePolicy(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); // unhandled exceptions app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { try { var exceptionHandlerPathFeature = context.Features.Get <IExceptionHandlerPathFeature>(); var id = ExceptionLogger.LogException(exceptionHandlerPathFeature.Error, exceptionHandlerPathFeature.Path, "jsdal-server", null); context.Response.StatusCode = 500; context.Response.ContentType = "text/plain"; await context.Response.WriteAsync($"Server error. Ref: {id}"); await context.Response.WriteAsync(new string(' ', 512)); // IE padding } catch (Exception ex) { Log.Error(ex, $"Failed to log unhandled exception because of:\r\n {ex.ToString()}"); } }); }); } app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
public static void Main(string[] args) { IsShuttingDown = false; var loggerConfig = new LoggerConfiguration() .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.File("log/detail-.txt", //?restrictedToMinimumLevel: LogEventLevel.Warning, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 7, shared: true, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {Message:lj}{NewLine}{Exception}" ) // .WriteTo.File("log/info-.txt", // rollingInterval: RollingInterval.Day, // shared: true, // outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3} {Message:lj}{NewLine}{Exception}", // restrictedToMinimumLevel: LogEventLevel.Information // ) ; try { CTS = new CancellationTokenSource(); var isService = args.Length == 1 && args[0].Equals("--service", StringComparison.OrdinalIgnoreCase); var justRun = args.Length == 1 && args[0].Equals("--run", StringComparison.OrdinalIgnoreCase); if (isService) { var pathToExe = Process.GetCurrentProcess().MainModule.FileName; var pathToContentRootService = Path.GetDirectoryName(pathToExe); Directory.SetCurrentDirectory(pathToContentRootService); } else if (!Debugger.IsAttached && !justRun) { TerminalUI.Init(); return; } else { loggerConfig.WriteTo.Console(); } Log.Logger = loggerConfig.CreateLogger(); var pathToContentRoot = Directory.GetCurrentDirectory(); if (Debugger.IsAttached) { // var pathToExe = Process.GetCurrentProcess().MainModule.FileName; // pathToContentRoot = Path.GetDirectoryName(pathToExe); } else { // now relying on serilog // OverrideStdout(); } AppDomain.CurrentDomain.UnhandledException += (sender, e) => { var isTerminating = e.IsTerminating; Log.Fatal("Unhandled exception", e.ExceptionObject); ExceptionLogger.LogException(e.ExceptionObject as Exception); }; AppConfigSettings appConfigSettings = null; try { var appSettingsJson = File.ReadAllText("./appsettings.json"); appConfigSettings = System.Text.Json.JsonSerializer.Deserialize <AppConfigSettings>(appSettingsJson); } catch (Exception ex) { Log.Error(ex, "Failed to read appsettings.json"); } _startDate = DateTime.Now; var _ = UptimeLogger.LogServerUptimeAsync(); Log.Information($"Application started with process id {System.Diagnostics.Process.GetCurrentProcess().Id}."); // I forget what the different msg types look like Log.Warning("This is what a warning looks like"); Log.Error("This is what an error looks like"); Log.Fatal("This is what a fatal error looks like"); Log.Information("Loading users"); UserManagement.LoadUsersFromFile(); Log.Information("Initialising exception logger"); ExceptionLogger.Instance.Init(); Log.Information("Loading settings"); SettingsInstance.LoadSettingsFromFile(); Log.Information("Initialising real-time tracker"); RealtimeTrackerThread.Instance.Init(); if (appConfigSettings?.AppSettings?.Startup?.HealthMonitor ?? false) { Log.Information("Initialising jsDAL health monitor"); jsDALHealthMonitorThread.Instance.Init(); } else { Log.Information("jsDAL health monitor not configured to run at startup"); } if (appConfigSettings?.AppSettings?.Startup?.DataCollector ?? false) { Log.Information("Initialising data collector"); DataCollectorThread.Instance.Init(); } else { Log.Information("Data collector not configured to run at startup"); } Log.Information("Initialising inline module manifest"); InlineModuleManifest.Instance.Init(); Log.Information("Configuring global culture"); var globalCulture = new System.Globalization.CultureInfo("en-US"); // set global culture to en-US - will help with things like parsing numbers from Javascript(e.g. 10.123) as double/decimal even if server uses a comma as decimal separator for example CultureInfo.DefaultThreadCurrentCulture = globalCulture; CultureInfo.DefaultThreadCurrentUICulture = globalCulture; Log.Information("Building web host"); var builder = BuildWebHost(pathToContentRoot, args); var host = builder.Build(); if (isService) { host.RunAsCustomService(); } else { host.RunAsync(); Console.WriteLine("\r\nPress CTRL+X to shutdown server"); while (true) { var keyInfo = Console.ReadKey(true); if (keyInfo.Key == ConsoleKey.X && (keyInfo.Modifiers & ConsoleModifiers.Control) == ConsoleModifiers.Control) { //host.StopAsync(); ShutdownAllBackgroundThreads(); break; } } } } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); SessionLog.Exception(ex); ShutdownAllBackgroundThreads(); } finally { Log.CloseAndFlush(); } }
public static IWebHostBuilder BuildWebHost(string pathToContentRoot, string[] args) { var webServerSettings = SettingsInstance.Instance.Settings.WebServer; // var certPath = "cert.pfx"; // var certPassPath = Path.GetFullPath("cert.pass"); // if (!File.Exists(certPassPath)) // { // throw new Exception($"Unable to find cert path file: {certPassPath}"); // } // var certPass = System.IO.File.ReadAllText(certPassPath); // var configurationBuilder = new ConfigurationBuilder(); // configurationBuilder.AddJsonFile("./appsettings.json", false, true); // var appConfig = configurationBuilder.Build(); return(WebHost .CreateDefaultBuilder(args) .UseSerilog() .UseContentRoot(pathToContentRoot) .UseWebRoot(Path.Combine(pathToContentRoot, "wwwroot")) .UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "true") // .ConfigureAppConfiguration((builderContext, config) => // { // IHostingEnvironment env = builderContext.HostingEnvironment; // config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); // //.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); // }) .UseHttpSys(options => { options.Authentication.Schemes = AuthenticationSchemes.None; options.Authentication.AllowAnonymous = true; options.MaxConnections = null; options.MaxRequestBodySize = 30000000; //~30MB int interfaceCnt = 0; if ((webServerSettings.EnableSSL ?? false) && !string.IsNullOrWhiteSpace(webServerSettings.HttpsServerHostname) && webServerSettings.HttpsServerPort.HasValue && !string.IsNullOrWhiteSpace(webServerSettings.HttpsCertHash)) { try { var httpsUrl = $"https://{webServerSettings.HttpsServerHostname}:{webServerSettings.HttpsServerPort.Value}"; if (NetshWrapper.ValidateSSLCertBinding(webServerSettings.HttpsServerHostname, webServerSettings.HttpsServerPort.Value)) { if (NetshWrapper.ValidateUrlAcl(true, webServerSettings.HttpsServerHostname, webServerSettings.HttpsServerPort.Value)) { //!eventLog.Info($"Listening to {httpsUrl}"); options.UrlPrefixes.Add(httpsUrl); interfaceCnt++; } else { if (NetshWrapper.AddUrlToACL(true, webServerSettings.HttpsServerHostname, webServerSettings.HttpsServerPort.Value)) { //!eventLog.Info($"Listening to {httpsUrl}"); options.UrlPrefixes.Add(httpsUrl); interfaceCnt++; } else { SessionLog.Error($"The url '{httpsUrl}' was not found in ACL list so a listener for this URL cannot be started."); Log.Error($"The url '{httpsUrl}' was not found in ACL list so a listener for this URL cannot be started."); } } } else { SessionLog.Error($"There is no SSL cert binding for '{httpsUrl}' so a listener for this URL cannot be started."); Log.Error($"There is no SSL cert binding for '{httpsUrl}' so a listener for this URL cannot be started."); } } catch (Exception ex) { SessionLog.Exception(ex); Log.Error(ex, "HTTPS init failed"); } } if (webServerSettings.EnableBasicHttp ?? false) { try { var httpUrl = $"http://{webServerSettings.HttpServerHostname}:{webServerSettings.HttpServerPort}"; if (NetshWrapper.ValidateUrlAcl(false, webServerSettings.HttpServerHostname, webServerSettings.HttpServerPort.Value)) { //!eventLog.Info($"Listening to {httpUrl}"); options.UrlPrefixes.Add(httpUrl); interfaceCnt++; } else { if (NetshWrapper.AddUrlToACL(false, webServerSettings.HttpServerHostname, webServerSettings.HttpServerPort.Value)) { //!eventLog.Info($"Listening to {httpUrl}"); options.UrlPrefixes.Add(httpUrl); interfaceCnt++; } else { SessionLog.Error($"The url '{httpUrl}' was not found in ACL list so a listener for this URL cannot be started."); Log.Error($"The url '{httpUrl}' was not found in ACL list so a listener for this URL cannot be started."); } } } catch (Exception ex) { SessionLog.Exception(ex); Log.Error(ex, "Basic Http init failed"); } } if (interfaceCnt == 0) { Log.Warning("No valid interface (http or https) found so defaulting to localhost:9086"); options.UrlPrefixes.Add("http://localhost:9086"); } }) // .UseKestrel(options => // { // options.AddServerHeader = false; // // TODO: Allow more config here, especially the limits // //!options.Limits.MaxConcurrentConnections // if (webServerSettings.EnableSSL ?? false) // { // if (File.Exists(certPath)) // { // options.Listen(System.Net.IPAddress.Any, webServerSettings.HttpsServerPort ?? 44312, listenOptions => // { // try // { // listenOptions.UseHttps(certPath, certPass); // } // catch (System.Exception ex) // { // SessionLog.Exception(ex); // Cons ole.WriteLine(ex.ToString()); // throw; // } // }); // } // else // { // Cons ole.WriteLine("Cannot start HTTPS listener: The cert file '{0}' does not exists.", certPath); // } // } // if (webServerSettings.EnableBasicHttp ?? false) // { // options.Listen(System.Net.IPAddress.Any, webServerSettings.HttpServerPort ?? 9086); // http // } // }) .UseStartup <Startup>() .UseSetting(WebHostDefaults.DetailedErrorsKey, "true") ); // .UseUrls("http://localhost:9086", "https://*:4430") }
public static void GenerateJsFileV2(string source, Endpoint endpoint, JsFile jsFile, Dictionary <string, ChangeDescriptor> fullChangeSet = null, bool rulesChanged = false) { var generateMetric = new Performance.ExecutionBase("GenerateJsFileV2"); var noChanges = false; try { // TODO: Figure out out casing on this property string jsNamespace = null;//endpoint.JsNamespace; if (string.IsNullOrWhiteSpace(jsNamespace)) { jsNamespace = endpoint.MetadataConnection.InitialCatalog; } var jsSafeNamespace = MakeNameJsSafe(jsNamespace); var routineContainerTemplate = WorkSpawner.TEMPLATE_RoutineContainer; var routineTemplate = WorkSpawner.TEMPLATE_Routine; var typescriptDefinitionsContainer = WorkSpawner.TEMPLATE_TypescriptDefinitions; endpoint.ApplyRules(jsFile); var includedRoutines = (from row in endpoint.CachedRoutines where !row.IsDeleted && (row.RuleInstructions[jsFile]?.Included ?? false == true) orderby row.FullName select row).ToList(); List <KeyValuePair <string, ChangeDescriptor> > changesInFile = null; if (fullChangeSet != null) { changesInFile = fullChangeSet.Where(change => includedRoutines.Count(inc => inc.FullName.Equals(change.Key, StringComparison.OrdinalIgnoreCase)) > 0).ToList(); // TODO: Consider if this is a good idea? // If we can reasonably say that there are no changes to routines that this JsFile cares about, why regenerate this file and why give it a new Version if (changesInFile.Count == 0) { noChanges = true; return; } } var jsSchemaLookupForJsFunctions = new Dictionary <string, List <string> /*Routine defs*/>(); var tsSchemaLookup = new Dictionary <string, List <string> /*Routine defs*/>(); var typeScriptParameterAndResultTypesSB = new StringBuilder(); var serverMethodPlugins = PluginLoader.Instance.PluginAssemblies .SelectMany(pa => pa.Plugins) .Where(p => p.Type == PluginType.ServerMethod && endpoint.Application.IsPluginIncluded(p.Guid.ToString())); var uniqueSchemas = new List <string>(); var mainLoopMetric = generateMetric.BeginChildStage("Main loop"); includedRoutines.ForEach(r => { try { if (r.TypescriptMethodStub == null) { r.PrecalculateJsGenerationValues(endpoint); } var jsSchemaName = JsFileGenerator.MakeNameJsSafe(r.Schema); var jsFunctionName = JsFileGenerator.MakeNameJsSafe(r.Routine); if (!jsSchemaLookupForJsFunctions.ContainsKey(jsSchemaName)) { jsSchemaLookupForJsFunctions.Add(jsSchemaName, new List <string>()); } if (!tsSchemaLookup.ContainsKey(jsSchemaName)) { tsSchemaLookup.Add(jsSchemaName, new List <string>()); } if (!uniqueSchemas.Contains(r.Schema)) { uniqueSchemas.Add(r.Schema); } var schemaIx = uniqueSchemas.IndexOf(r.Schema); // .js { var jsFunctionDefLine = routineTemplate.Replace("<<FUNC_NAME>>", jsFunctionName).Replace("<<SCHEMA_IX>>", schemaIx.ToString()).Replace("<<ROUTINE>>", r.Routine); if (r.Type.Equals(string.Intern("PROCEDURE"), StringComparison.OrdinalIgnoreCase)) { jsFunctionDefLine = jsFunctionDefLine.Replace("<<CLASS>>", "S"); } else { jsFunctionDefLine = jsFunctionDefLine.Replace("<<CLASS>>", "U"); } jsSchemaLookupForJsFunctions[jsSchemaName].Add(jsFunctionDefLine); } // .tsd { typeScriptParameterAndResultTypesSB.AppendLine(r.TypescriptParameterTypeDefinition); typeScriptParameterAndResultTypesSB.AppendLine(r.TypescriptOutputParameterTypeDefinition); typeScriptParameterAndResultTypesSB.AppendLine(r.TypescriptResultSetDefinitions); tsSchemaLookup[jsSchemaName].Add(r.TypescriptMethodStub); } } catch (Exception ex) { SessionLog.Exception(ex); // TODO: quit whole process } }); mainLoopMetric.End(); var finalSBMetric = generateMetric.BeginChildStage("Final SB"); var schemaAndRoutineDefs = string.Join("\r\n", jsSchemaLookupForJsFunctions.Select(s => "\tx." + s.Key + " = {\r\n\t\t" + string.Join(",\r\n\t\t", s.Value.ToArray()) + "\r\n\t}\r\n").ToArray()); var tsSchemaAndRoutineDefs = string.Join("\r\n", tsSchemaLookup.Select(s => "\t\tclass " + s.Key + " {\r\n" + string.Join(";\r\n", s.Value.ToArray()) + "\r\n\t\t}\r\n").ToArray()); var finalSB = new StringBuilder(routineContainerTemplate); jsFile.IncrementVersion(); // record changes against new version if (changesInFile != null && changesInFile.Count > 0) { JsFileChangesTracker.Instance.AddUpdate(endpoint, jsFile, changesInFile.Select(kv => kv.Value).ToList()); } if (rulesChanged) { JsFileChangesTracker.Instance.AddUpdate(endpoint, jsFile, new List <ChangeDescriptor> { ChangeDescriptor.Create("System", "One or more rules changed.") }); } finalSB.Replace("<<DATE>>", DateTime.Now.ToString("dd MMM yyyy, HH:mm")) .Replace("<<FILE_VERSION>>", jsFile.Version.ToString()) .Replace("<<SERVER_NAME>>", Environment.MachineName) .Replace("<<ENDPOINT>>", endpoint.Pedigree) .Replace("<<UNIQUE_SCHEMAS>>", string.Join(',', uniqueSchemas.Select(k => $"'{k}'"))) .Replace("<<Catalog>>", jsSafeNamespace) .Replace("<<ROUTINES>>", schemaAndRoutineDefs) ; var finalTypeScriptSB = new StringBuilder(); finalTypeScriptSB = finalTypeScriptSB.Append(typescriptDefinitionsContainer); // Custom/User types if (endpoint.CustomTypeLookupWithTypeScriptDef.Count > 0) { var customTSD = from kv in endpoint.CustomTypeLookupWithTypeScriptDef select $"\t\ttype {kv.Key} = {kv.Value};"; typeScriptParameterAndResultTypesSB.Insert(0, string.Join("\r\n", customTSD)); } var resultAndParameterTypes = typeScriptParameterAndResultTypesSB.ToString(); finalTypeScriptSB.Replace("<<DATE>>", DateTime.Now.ToString("dd MMM yyyy, HH:mm")) .Replace("<<FILE_VERSION>>", jsFile.Version.ToString()) .Replace("<<SERVER_NAME>>", Environment.MachineName) .Replace("<<Catalog>>", jsSafeNamespace) .Replace("<<ResultAndParameterTypes>>", resultAndParameterTypes) .Replace("<<MethodsStubs>>", tsSchemaAndRoutineDefs) ; finalSBMetric.End(); var toStringMetric = generateMetric.BeginChildStage("ToString"); var typescriptDefinitionsOutput = finalTypeScriptSB.ToString(); var finalOutput = finalSB.ToString(); toStringMetric.End(); var filePath = endpoint.OutputFilePath(jsFile); var minfiedFilePath = endpoint.MinifiedOutputFilePath(jsFile); var tsTypingsFilePath = endpoint.OutputTypeScriptTypingsFilePath(jsFile); var minifyMetric = generateMetric.BeginChildStage("Minify"); var minifiedSource = Uglify.Js(finalOutput /*, { }*/).Code; minifyMetric.End(); if (!Directory.Exists(endpoint.OutputDir)) { Directory.CreateDirectory(endpoint.OutputDir); } var fileOutputMetric = generateMetric.BeginChildStage("Write"); var jsFinalBytes = System.Text.Encoding.UTF8.GetBytes(finalOutput); var jsFinalMinifiedBytes = System.Text.Encoding.UTF8.GetBytes(minifiedSource); jsFile.ETag = Controllers.PublicController.ComputeETag(jsFinalBytes); jsFile.ETagMinified = Controllers.PublicController.ComputeETag(jsFinalMinifiedBytes); File.WriteAllText(filePath, finalOutput); File.WriteAllText(minfiedFilePath, minifiedSource); File.WriteAllText(tsTypingsFilePath, typescriptDefinitionsOutput); fileOutputMetric.End(); } finally { generateMetric.End(); SessionLog.InfoToFileOnly($"{endpoint.Pedigree.PadRight(25, ' ')} - {generateMetric.DurationInMS.ToString().PadLeft(4)} ms {jsFile.Filename.PadRight(20)} (source={source};rulesChanged={rulesChanged};changes={!noChanges}); {generateMetric.ChildDurationsSingleLine()}"); } }
// generates and caches the ServerMethod .js and .tsd for a specific ServerMethod Plugin Registration private void GenerateAndCacheJsInterface() { try { var definitionsJS = JavaScriptDefinitions = new Dictionary <string, List <Definition> >(); var definitionsTSD = TypescriptDefinitions = new Dictionary <string, List <Definition> >(); JavaScriptDefinitionsHash = TypescriptDefinitionsHash = null; ConverterLookup = new List <string>(); var namespaceLookup = new List <string>(); // add default namespace definitionsJS.Add("ServerMethods", new List <Definition>()); definitionsTSD.Add("ServerMethods", new List <Definition>()); foreach (var method in this.Registration.Methods) { var namespaceKey = "ServerMethods"; var namespaceKeyTSD = "ServerMethods"; var jsNamespaceVar = Strings.@null; // null for main ServerMethod namespace if (!string.IsNullOrEmpty(method.Namespace)) { namespaceKey = method.Namespace; namespaceKeyTSD = method.Namespace; } var isCustomNamespace = !namespaceKey.Equals("ServerMethods", StringComparison.Ordinal); if (isCustomNamespace) { if (!namespaceLookup.Contains(namespaceKey)) { namespaceLookup.Add(namespaceKey); } jsNamespaceVar = $"_ns[{namespaceLookup.IndexOf(namespaceKey)}]"; } if (!definitionsJS.ContainsKey(namespaceKey)) { definitionsJS.Add(namespaceKey, new List <Definition>()); } if (!definitionsTSD.ContainsKey(namespaceKeyTSD)) { definitionsTSD.Add(namespaceKeyTSD, new List <Definition>()); } var methodParameters = method.AssemblyMethodInfo.GetParameters(); var inputConvertersLookup = new Dictionary <string, ConverterDefinition>(); var outputConvertersLookup = new Dictionary <string, ConverterDefinition>(); var resultsConvertersLookup = new Dictionary <string, ConverterDefinition>(); foreach (var inputParam in methodParameters) { GlobalConverterLookup.AnalyseForRequiredOutputConverters(inputParam.Name, inputParam.ParameterType, null, ref inputConvertersLookup); } foreach (var outputParam in methodParameters.Where(p => p.IsOut || p.ParameterType.IsByRef)) { GlobalConverterLookup.AnalyseForRequiredOutputConverters(outputParam.Name, outputParam.ParameterType, null, ref outputConvertersLookup); } GlobalConverterLookup.AnalyseForRequiredOutputConverters("$result$", method.AssemblyMethodInfo.ReturnType, null, ref resultsConvertersLookup); string inputConverter = null; string outputConverter = null; string resultConverter = null; var allRequiredConverters = inputConvertersLookup.Select(c => c.Value.ToJson()).Distinct() .Concat(outputConvertersLookup.Select(c => c.Value.ToJson()).Distinct()) .Concat(resultsConvertersLookup.Select(c => c.Value.ToJson()).Distinct()) .ToList(); if (allRequiredConverters.Count > 0) { foreach (var converterJson in allRequiredConverters) { if (ConverterLookup.IndexOf(converterJson) == -1) { ConverterLookup.Add(converterJson); } } } inputConverter = string.Join(",", (from kv in inputConvertersLookup select $"\"{kv.Key}\": $c[{kv.Value.ToJson()}]")); // ignore ConverterOptions for now as we don't actually have any use for it at the moment outputConverter = string.Join(",", (from kv in outputConvertersLookup select $"\"{kv.Key}\": $c[{kv.Value.ToJson()}]")); // ignore ConverterOptions for now as we don't actually have any use for it at the moment resultConverter = string.Join(",", (from kv in resultsConvertersLookup select $"\"{kv.Key}\": $c[{kv.Value.ToJson()}]")); // ignore ConverterOptions for now as we don't actually have any use for it at the moment inputConverter = string.IsNullOrWhiteSpace(inputConverter) ? null : inputConverter; outputConverter = string.IsNullOrWhiteSpace(outputConverter) ? null : outputConverter; resultConverter = string.IsNullOrWhiteSpace(resultConverter) ? null : resultConverter; var hasConverters = inputConverter != null || outputConverter != null || resultConverter != null; // js var methodLineJS = ServerMethodManager.TEMPLATE_ServerMethodFunctionTemplate .Replace("<<FUNC_NAME>>", method.Name) .Replace("<<NAMESPACE>>", jsNamespaceVar); ; if (!hasConverters) { methodLineJS = methodLineJS.Replace("<<CONV_SEP>>", ""); methodLineJS = methodLineJS.Replace("<<CONVERTERS>>", ""); } if (methodParameters.Count() > 0) { methodLineJS = methodLineJS.Replace("<<ARG_SEP>>", ", "); methodLineJS = methodLineJS.Replace("<<HAS_PARMS>>", "o"); } else { methodLineJS = methodLineJS.Replace("<<ARG_SEP>>", ""); methodLineJS = methodLineJS.Replace("<<HAS_PARMS>>", ""); } definitionsJS[namespaceKey].Add(new Definition() { MethodName = method.Name, Line = methodLineJS, InputConverter = inputConverter, OutputConverter = outputConverter, ResultsConverter = resultConverter }); // TSD string methodLineTSD = null; if (isCustomNamespace) { methodLineTSD = SERVER_TSD_METHOD_NONSTATIC_TEMPLATE.Replace("<<FUNC_NAME>>", method.Name); } else { methodLineTSD = SERVER_TSD_METHOD_TEMPLATE.Replace("<<FUNC_NAME>>", method.Name); } var inputParmListLookup = from p in methodParameters select new { Name = p.Name, p.IsOut, p.ParameterType.IsByRef, p.ParameterType.IsArray, p.ParameterType.IsValueType, HasDefault = p.IsOptional, IsNullable = Nullable.GetUnderlyingType(p.ParameterType) != null, TypescriptDataType = GlobalTypescriptTypeLookup.GetTypescriptTypeFromCSharp(p.ParameterType) }; var inputParmsFormatted = from p in inputParmListLookup select $"{p.Name}{((p.HasDefault) ? "?" : "")}: {p.TypescriptDataType}"; // TODO: Revise. Not clear if IsNullable should also be output with a '?'. In TypeScript this means optional and not 'nullable'. So in C# even if a parameter is nullable it is still required to specified it. ? should be reserved for OPTIONAL parameters //select $"{p.Name}{((p.HasDefault || p.IsNullable ) ? "?" : "")}: {p.TypescriptDataType}"; string typeNameBase = $"{(isCustomNamespace ? namespaceKeyTSD + "_" : "")}{ method.Name }"; string typeNameInputParms = $"{typeNameBase}_In"; string typeNameOutputParms = $"{typeNameBase}_Out"; string typeNameResult = $"{typeNameBase}_Res"; string typeDefInputParms = null; string typeDefOutputParms = null; string typeDefResult = null; // if there are INPUT parameters if (inputParmsFormatted.Count() > 0) { methodLineTSD = methodLineTSD.Replace("<<PARM_LIST>>", "parameters?: __." + typeNameInputParms); typeDefInputParms = $"type {typeNameInputParms} = {"{" + string.Join(", ", inputParmsFormatted) + "}"};"; typeNameInputParms = "__." + typeNameInputParms; } else { methodLineTSD = methodLineTSD.Replace("<<PARM_LIST>>", ""); typeNameInputParms = "void"; } // if there are OUTPUT parameters if (inputParmListLookup.Count(p => p.IsOut) > 0) { typeDefOutputParms = $"type {typeNameOutputParms} = {{ " + string.Join(", ", (from p in inputParmListLookup where p.IsOut select $"{p.Name}: {p.TypescriptDataType}")) + " };"; typeNameOutputParms = "__." + typeNameOutputParms; } else { typeNameOutputParms = "void"; } if (method.AssemblyMethodInfo.ReturnType == typeof(void)) // no result { //IServerMethodVoid<OuputParameters, InputParameters> methodLineTSD = methodLineTSD.Replace("<<RET_TYPE>>", $"IServerMethodVoid<{typeNameOutputParms}, {typeNameInputParms}>"); } else { //IServerMethod<OuputParameters, ResultType, InputParameters> var retType = GlobalTypescriptTypeLookup.GetTypescriptTypeFromCSharp(method.AssemblyMethodInfo.ReturnType); // if a built-in js/TS type if (new string[] { "number", "string", "date", "boolean", "any", "number[]", "string[]", "date[]", "boolean[]", "any[]" }.Contains(retType.ToLower())) { typeDefResult = null; methodLineTSD = methodLineTSD.Replace("<<RET_TYPE>>", $"IServerMethod<{typeNameOutputParms}, {retType}, {typeNameInputParms}>"); } else { typeDefResult = $"type {typeNameResult} = {retType};"; methodLineTSD = methodLineTSD.Replace("<<RET_TYPE>>", $"IServerMethod<{typeNameOutputParms}, __.{typeNameResult}, {typeNameInputParms}>"); } } definitionsTSD[namespaceKeyTSD].Add(new Definition() { MethodName = method.Name, Line = methodLineTSD, TypesLines = new List <string>() { typeDefInputParms, typeDefOutputParms, typeDefResult } }); } // foreach (var method in this.Methods) var jsLines = string.Join("\n", definitionsJS.Select(kv => $"{kv.Key}§{string.Join('\n', kv.Value.Select(d => d.Line).ToArray())}").ToArray()); var tsdLines = string.Join("\n", definitionsTSD.Select(kv => $"{kv.Key}§{string.Join('\n', kv.Value.Select(d => d.Line).ToArray())}").ToArray()); using (var sha = System.Security.Cryptography.SHA256.Create()) { var jsHash = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(jsLines)); var tsdHash = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tsdLines)); // TODO: Compare with current Hashes. If different then let all relevant Apps know this.JavaScriptDefinitionsHash = jsHash; this.TypescriptDefinitionsHash = tsdHash; } } catch (Exception ex) { SessionLog.Exception(ex); } }
//AssemblyLoadContext, AssemblyName, Assembly? // private Assembly OnResolveAssembly(AssemblyLoadContext ctx, AssemblyName asmName) // { // try // { // // try and find relative to existing assebmlies loaded in ctx // foreach (var asm in ctx.Assemblies) // { // var fi = new FileInfo(asm.Location); // var path = Path.Combine(fi.DirectoryName, asmName.Name + ".dll"); // if (File.Exists(path)) // { // try // { // var loadedAsm = ctx.LoadFromAssemblyPath(path); // if (loadedAsm != null) return loadedAsm; // } // catch { } // } // } // SessionLog.Error($"Failed to resolve {asmName.FullName} in context {ctx.Name}"); // return null; // } // catch // { // return null; // } // } // private Assembly OnResolveAssembly(object sender, ResolveEventArgs e) // { // try // { // var asmName = new AssemblyName(e.Name); // var requestingLocation = e.RequestingAssembly.Location; // var requestingDir = Path.GetDirectoryName(requestingLocation); // // look for a dll in the same location as the requesting assembly // var path = Path.Combine(requestingDir, asmName.Name + ".dll"); // if (!File.Exists(path)) return null; // Assembly.LoadFrom(path); // return null; // } // catch // { // return null; // } // } // private void InitServerWidePlugins() // { // try // { // if (PluginAssemblies == null) return; // foreach (var pluginAssembly in PluginAssemblies) // { // var pluginInfoCollection = pluginAssembly.Plugins.Where(p => p.Type == OM.PluginType.ServerMethod || p.Type == OM.PluginType.BackgroundThread); // foreach (var pluginInfo in pluginInfoCollection) // { // if (pluginInfo.Type == OM.PluginType.BackgroundThread) // { // _backgroundThreadManager.Register(pluginInfo); // } // else if (pluginInfo.Type == OM.PluginType.ServerMethod) // { // ServerMethodManager.Register(pluginAssembly.InstanceId, pluginInfo); // } // } // } // } // catch (Exception ex) // { // SessionLog.Exception(ex); // } // } // parses an Assembly and checks for Plugin-type interface. If found each of those interfaces are tested for validity in terms of mandatory Attribute values and uniqueness private bool ParsePluginAssembly(Assembly pluginAssembly, out List <PluginInfo> pluginInfoList, out List <string> errorList, bool checkForConflict = true) { errorList = new List <string>(); pluginInfoList = new List <PluginInfo>(); if (pluginAssembly.DefinedTypes != null) { var pluginTypeList = pluginAssembly.DefinedTypes.Where(typ => typ.IsSubclassOf(typeof(PluginBase))).ToList(); if (pluginTypeList != null && pluginTypeList.Count > 0) { foreach (var pluginType in pluginTypeList) { var pluginInfo = new PluginInfo(); try { var pluginData = pluginType.GetCustomAttribute(typeof(PluginDataAttribute)) as PluginDataAttribute; if (pluginData == null) { errorList.Add($"Plugin '{pluginType.FullName}' from assembly '{pluginAssembly.FullName}' does not have a PluginData attribute defined on the class level. Add a jsdal_plugin.PluginDataAttribute to the class."); continue; } if (!Guid.TryParse(pluginData.Guid, out var pluginGuid)) { errorList.Add($"Plugin '{pluginType.FullName}' does not have a valid Guid value set on its PluginData attribute."); continue; } if (checkForConflict) { var conflict = PluginAssemblies.SelectMany(a => a.Plugins).FirstOrDefault(p => p.Guid.Equals(pluginGuid)); if (conflict != null) { errorList.Add($"Plugin '{pluginType.FullName}' has a conflicting Guid. The conflict is on assembly {conflict.TypeInfo.FullName} and plugin '{conflict.Name}' with Guid value {conflict.Guid}."); continue; } } if (pluginType.IsSubclassOf(typeof(ExecutionPlugin))) { pluginInfo.Type = OM.PluginType.Execution; } else if (pluginType.IsSubclassOf(typeof(BackgroundThreadPlugin))) { pluginInfo.Type = OM.PluginType.BackgroundThread; } else if (pluginType.IsSubclassOf(typeof(ServerMethodPlugin))) { pluginInfo.Type = OM.PluginType.ServerMethod; // TODO: Additional validation: Look for at least on ServerMethod? otherwise just a warning? // What about unique names of ServerMethods? // Validate Custom Namespace validity (must be JavaScript safe) } else { errorList.Add($"Unknown plugin type '{pluginType.FullName}'."); continue; } pluginInfo.Assembly = pluginAssembly; pluginInfo.Name = pluginData.Name; pluginInfo.Description = pluginData.Description; pluginInfo.TypeInfo = pluginType; pluginInfo.Guid = pluginGuid; //errorList.Add($"Plugin '{pluginInfo.Name}' ({pluginInfo.Guid}) loaded. Assembly: {pluginAssembly.FullName}"); pluginInfoList.Add(pluginInfo); } catch (Exception ex) { errorList.Add("Failed to instantiate type '{pluginType.FullName}'."); errorList.Add(ex.ToString()); SessionLog.Error("Failed to instantiate type '{0}'. See the exception that follows.", pluginType.FullName); SessionLog.Exception(ex); } } } else { errorList.Add($"Failed to find any jsDAL Server plugins in the assembly '{pluginAssembly.Location}'. Make sure you have a public class available that derives from one of the plugin types."); } } else { errorList.Add($"Failed to find any jsDAL Server plugins in the assembly '{pluginAssembly.Location}'. Make sure you have a public class available that derives from one of the plugin types."); } return(errorList == null || errorList.Count == 0); }
// public static async Task<(bool, List<string>)> Evaluate(string code) // { // try // { // var options = ScriptOptions.Default.WithReferences(GetCommonMetadataReferences()); // await CSharpScript.EvaluateAsync(code, options, globals: null, globalsType: null, cancellationToken: CancellationToken.None); // return (true, null); // } // catch (CompilationErrorException ce) // { // return (false, (from d in ce.Diagnostics // select d.ToString()).ToList()); // } // } // public static async Task<(bool/*isValid*/, List<string>/*problems*/, List<BasePluginRuntime>/*parsedPluginCollection*/)> EvalAndParseAsync(string id, string code) // { // bool isValid; // List<string> problems = null; // List<BasePluginRuntime> parsedPluginCollection = null; // (isValid, problems) = await CSharpCompilerHelper.Evaluate(code); // if (isValid) // if compilation went well // { // isValid = CSharpCompilerHelper.ParseForPluginTypes(id, code, out parsedPluginCollection, out problems); // } // return (isValid, problems, parsedPluginCollection); // } public static byte[] CompileIntoAssembly(string assemblyName, string source, out List <string> problems) { problems = new List <string>(); try { if (string.IsNullOrEmpty(assemblyName)) { problems.Add("Assembly name must have a value"); } if (string.IsNullOrEmpty(source)) { problems.Add("No source provided"); } if (problems.Count > 0) { return(null); } CSharpParseOptions parseOptions = CSharpParseOptions.Default; SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, options: parseOptions); // if (Debugger.IsAttached) // { // pluginBasePath = "./../jsdal-plugin/bin/Debug/netcoreapp2.0/jsdal-plugin.dll"; // } //var pluginBaseRef = MetadataReference.CreateFromFile(pluginBasePath); //var trustedAssembliesPaths = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator); MetadataReference[] references = GetCommonMetadataReferences(); var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) .WithUsings(new string[] { "System" }); CSharpCompilation compilation = CSharpCompilation.Create( assemblyName, syntaxTrees: new[] { syntaxTree }, references: references, options: compilationOptions ); using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms); if (!result.Success) { IEnumerable <Diagnostic> failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); problems = failures.Select(f => f.GetMessage()).ToList(); foreach (Diagnostic diagnostic in failures) { Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); } return(null); } else { ms.Seek(0, SeekOrigin.Begin); //?AssemblyLoadContext.Default.LoadFromStream //Assembly assembly = Assembly.Load(ms.ToArray()); //?var assembly = asmCtx.LoadFromStream(ms); return(ms.ToArray()); //ParseAndLoadPluginAssembly(assembly); } } } catch (Exception ex) { problems.Add(ex.ToString()); SessionLog.Exception(ex); return(null); } }
private void OnUnloading(AssemblyLoadContext ctx) { SessionLog.Info($"Unloading assembly context {ctx.Name}"); PluginLoader.RemoveAssemblyContextRef(this); }
// public void HandleAssemblyUpdated(PluginInfo pluginInfo) // { // this.PluginInfo = pluginInfo; // this.Process(); // } public void CombineOutput(OM.Application appContext, ref Dictionary <string /*Namespace*/, List <Definition> > combinedJS, ref Dictionary <string /*Namespace*/, List <Definition> > combinedTSD, ref List <string> combinedConverterLookup) { // converters foreach (var c in this.ConverterLookup) { if (combinedConverterLookup.IndexOf(c) == -1) { combinedConverterLookup.Add(c); } } foreach (var namespaceKV in this.JavaScriptDefinitions) { // js foreach (var definition in namespaceKV.Value) { if (!combinedJS.ContainsKey(namespaceKV.Key)) { combinedJS.Add(namespaceKV.Key, new List <Definition>()); } if (combinedJS[namespaceKV.Key].FirstOrDefault(m => m.MethodName.Equals(definition.MethodName, StringComparison.Ordinal)) != null) { // TODO: Consider allowing overloads SessionLog.Warning($"{appContext.Project.Name}/{appContext.Name} - ServerMethods - conflicting method name '{definition.MethodName}'."); continue; } var hasConverter = definition.InputConverter != null || definition.OutputConverter != null || definition.ResultsConverter != null; if (hasConverter) { var convertersSB = new StringBuilder("{ "); var lst = new List <string>(); string inputConverter = definition.InputConverter; string outputConverter = definition.OutputConverter; string resultConverter = definition.ResultsConverter; foreach (var converterJson in combinedConverterLookup) { if (inputConverter != null) { inputConverter = inputConverter.Replace(converterJson, combinedConverterLookup.IndexOf(converterJson).ToString()); } if (outputConverter != null) { outputConverter = outputConverter.Replace(converterJson, combinedConverterLookup.IndexOf(converterJson).ToString()); } if (resultConverter != null) { resultConverter = resultConverter.Replace(converterJson, combinedConverterLookup.IndexOf(converterJson).ToString()); } } if (inputConverter != null) { lst.Add($"input: {{ {inputConverter} }}"); } if (outputConverter != null) { lst.Add($"output: {{ {outputConverter} }}"); } if (resultConverter != null) { lst.Add($"results: {{ {resultConverter} }}"); } convertersSB.Append(string.Join(", ", lst)); convertersSB.Append(" }"); definition.Line = definition.Line.Replace("<<CONV_SEP>>", ", "); definition.Line = definition.Line.Replace("<<CONVERTERS>>", convertersSB.ToString()); } combinedJS[namespaceKV.Key].Add(definition); } } // tsd foreach (var namespaceKV in this.TypescriptDefinitions) { foreach (var definition in namespaceKV.Value) { if (!combinedTSD.ContainsKey(namespaceKV.Key)) { combinedTSD.Add(namespaceKV.Key, new List <Definition>()); } if (combinedTSD[namespaceKV.Key].FirstOrDefault(m => m.MethodName.Equals(definition.MethodName, StringComparison.Ordinal)) != null) { // just skip, should have already been handled on the .js side continue; } combinedTSD[namespaceKV.Key].Add(definition); } } }
public async Task InitAsync() { // load from plugin directory try { // AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => // { // try // { // var asmName = new AssemblyName(e.Name); // var requestingLocation = e.RequestingAssembly.Location; // var requestingDir = Path.GetDirectoryName(requestingLocation); // // look for a dll in the same location as the requesting assembly // var path = Path.Combine(requestingDir, asmName.Name + ".dll"); // if (!File.Exists(path)) return null; // Assembly.LoadFrom(path); // return null; // } // catch // { // return null; // } // }; var pluginPath = Path.GetFullPath("plugins"); if (Directory.Exists("./plugins")) { var dllCollection = Directory.EnumerateFiles("plugins", "*.dll", SearchOption.TopDirectoryOnly); foreach (var dllPath in dllCollection) { // skip jsdal-plugin base if (dllPath.Equals("plugins\\jsdal-plugin.dll", StringComparison.OrdinalIgnoreCase)) { continue; } try { var ctxName = $"Plugin Context {++_asmCtxCounter}"; var asmCtx = new PluginAssemblyLoadContext(pluginPath, ctxName, true /*enable unloading*/); ASM_CTXES.Add(asmCtx); SessionLog.Info($"Created {ctxName} for {dllPath}".PadRight(35)); var dllFullPath = Path.GetFullPath(dllPath); var pluginAssembly = asmCtx.LoadFromAssemblyPath(dllFullPath); ParseAndLoadPluginAssembly(asmCtx, pluginAssembly, null); } catch (Exception ee) { SessionLog.Error("Failed to load plugin DLL '{0}'. See exception that follows.", dllPath); SessionLog.Exception(ee); } } } } catch (Exception ex) { SessionLog.Exception(ex); } // load inline assemblies try { foreach (var inlineEntry in InlineModuleManifest.Instance.Entries) { var sourcePath = Path.Combine(InlinePluginSourcePath, inlineEntry.Id); if (File.Exists(sourcePath)) { var code = await File.ReadAllTextAsync(sourcePath); CompileCodeIntoAssemblyContext(inlineEntry, code); } else { SessionLog.Error($"Inline module {inlineEntry.Name} not found at '{sourcePath}'"); } } } catch (Exception ex) { SessionLog.Exception(ex); } // init server-wide types // try // { // InitServerWidePlugins(); // } // catch (Exception ex) // { // SessionLog.Exception(ex); // } }