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 void BuildAndCacheServerMethodJsAndTSD() { try { var registrations = ServerMethodManager.GetRegistrationsForApp(this); if (registrations.Count() > 0) { this.GenerateX(registrations); } else { this.ServerMethodJs = this.ServerMethodTSD = this.ServerMethodJsEtag = this.ServerMethodTSDEtag = null; } } catch (Exception ex) { SessionLog.Error($"Failed to generate ServerMethod output files for {this.Project.Name}/{this.Name}.See exception that follows."); SessionLog.Exception(ex); } }
public bool LoadOrUpdateInlineAssembly(InlineModuleManifestEntry inlineEntry, string code, out List <string> errorList) { errorList = null; var existingPluginAssembly = this.PluginAssemblies.FirstOrDefault(p => p.InlineEntryId != null && p.InlineEntryId.Equals(inlineEntry.Id, StringComparison.Ordinal) && p.IsInline); if (existingPluginAssembly != null) { var existingAsmCtx = AssemblyLoadContext.GetLoadContext(existingPluginAssembly.Assembly); // unload existing PluginAssembly existingPluginAssembly.Unload(); this._pluginAssemblies.Remove(existingPluginAssembly); Instance.CompileCodeIntoAssemblyContext(inlineEntry, code); // if (!ParsePluginAssembly(newAssembly, out var pluginInfoList, out errorList, checkForConflict: false)) // { // return false; // } //existingPluginAssembly.UpdatePluginList(pluginInfoList); //SessionLog.Info($"Assembly {existingPluginAssembly.Assembly.FullName} updated"); } else { // new? Instance.CompileCodeIntoAssemblyContext(inlineEntry, code); } // TODO: Perhaps this can be optimised to run only on apps that are affected by plugin change ServerMethodManager.RebuildCacheForAllApps(); return(true); }
// public void UpdatePluginList(List<PluginInfo> list) // { // this._plugins.Clear(); // this._plugins.AddRange(list); // } public void Unload() { var serverMethodPlugins = _plugins.Where(pi => pi.Type == PluginType.ServerMethod); if (serverMethodPlugins.Count() > 0) { // TODO: remove from SM manager entirely??!?!?! ServerMethodManager.HandleAssemblyUpdated(InstanceId, serverMethodPlugins.ToList()); } var bgThreadPlugins = _plugins.Where(pi => pi.Type == PluginType.BackgroundThread).ToList(); if (bgThreadPlugins.Count() > 0) { BackgroundThreadPluginManager.Instance.StopAll(bgThreadPlugins); } this._plugins.Clear(); this.AssemblyLoadContext.Unload(); this.InlineEntryId = null; this.Assembly = null; this.AssemblyLoadContext = null; }
public CommonReturnValue UpdatePluginList(dynamic pluginList) { var oldList = this.Plugins; if (oldList == null) { oldList = new List <string>(); } this.Plugins = new List <string>(); if (pluginList == null) { return(CommonReturnValue.Success()); } foreach (Newtonsoft.Json.Linq.JObject p in pluginList) { bool included = (bool)p["Included"]; Guid g = (Guid)p["Guid"]; if (included) { this.Plugins.Add(g.ToString()); } } ; var newlyEnabled = this.Plugins.Where(p => oldList.FirstOrDefault(x => x.Equals(p, StringComparison.OrdinalIgnoreCase)) == null).Select(g => g.ToLower()); var disabled = oldList.Where(p => this.Plugins.FirstOrDefault(x => x.Equals(p, StringComparison.OrdinalIgnoreCase)) == null).Select(g => g.ToLower()); var ret = PluginLoader.Instance.PluginAssemblies .SelectMany(a => a.Plugins, (pa, plugin) => new { PluginAssembly = pa, PluginInfo = plugin }) .Where(p => p.PluginInfo.Type == PluginType.BackgroundThread || p.PluginInfo.Type == PluginType.ServerMethod) .ToList(); var startList = ret.Where(p => newlyEnabled.Contains(p.PluginInfo.Guid.ToString().ToLower())); var stopList = ret.Where(p => disabled.Contains(p.PluginInfo.Guid.ToString().ToLower())); // START { foreach (var item in startList) { if (item.PluginInfo.Type == PluginType.BackgroundThread) { BackgroundThreadPluginManager.Instance.Register(item.PluginInfo); } else if (item.PluginInfo.Type == PluginType.ServerMethod) { ServerMethodManager.Register(item.PluginAssembly.InstanceId, item.PluginInfo); } } } // STOP { foreach (var item in stopList) { if (item.PluginInfo.Type == PluginType.BackgroundThread) { // TODO: Implement a stop and call for specific EP! BackgroundThreadPluginManager.Instance.StopForApp(this, item.PluginInfo); //BackgroundThreadPluginManager.Instance.Register(item.PluginInfo); } else if (item.PluginInfo.Type == PluginType.ServerMethod) { //ServerMethodManager.Register(item.PluginAssembly.InstanceId, item.PluginInfo); } } } return(CommonReturnValue.Success()); }
public (plugin.ServerMethodPlugin, ServerMethodRegistrationMethod /*matched Method*/, string /*error*/) GetServerMethodPluginInstance(string nameSpace, string methodName, Dictionary <string, string> inputParameters) { // find all registered ServerMethods for this app var registrations = ServerMethodManager.GetRegistrationsForApp(this.Application); // TODO: To support overloading we need to match name + best fit parameter list var methodCandidates = registrations.SelectMany(reg => reg.Methods) .Where(m => ((nameSpace == null && m.Namespace == null) || (m.Namespace?.Equals(nameSpace, StringComparison.Ordinal) ?? false)) && m.Name.Equals(methodName, StringComparison.Ordinal)) .Select(m => m); if (methodCandidates.Count() == 0) { return(null, null, "Method name not found."); } var weightedMethodList = new List <(decimal /*weight*/, string /*error*/, ServerMethodRegistrationMethod)>(); // find the best matching overload (if any) foreach (var regMethod in methodCandidates) { var methodParameters = regMethod.AssemblyMethodInfo.GetParameters(); if (inputParameters.Count > methodParameters.Length) { weightedMethodList.Add((1M, "Too many parameters specified", regMethod)); continue; } var joined = from methodParam in methodParameters join inputParam in inputParameters on methodParam.Name equals inputParam.Key into grp from parm in grp.DefaultIfEmpty() select new { HasMatch = parm.Key != null, Param = methodParam }; var matched = joined.Where(e => e.HasMatch); var notmatched = joined.Where(e => !e.HasMatch); var expectedCnt = methodParameters.Count(); var matchedCnt = matched.Count(); // out/ref/optional parameters are added as extra credit below (but does not contribute to actual weight) var outRefSum = (from p in joined where (p.Param.IsOut || p.Param.IsOptional || p.Param.ParameterType.IsByRef) && !p.HasMatch select 1.0M).Sum(); if (matchedCnt == expectedCnt || matchedCnt + outRefSum == expectedCnt) { weightedMethodList.Add((matchedCnt, null, regMethod)); } else { //weightedMethodList.Add((matchedCnt, $"Following parameters not specified: {string.Join("\r\n", notmatched.Select(nm => nm.Param.Name))}", regMethod)); weightedMethodList.Add((matchedCnt, "Parameter mismatch", regMethod)); } } var bestMatch = weightedMethodList.OrderByDescending(k => k.Item1).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(bestMatch.Item2)) { var parms = bestMatch.Item3.AssemblyMethodInfo.GetParameters(); var parmDesc = "(no parameters)"; if (parms.Length > 0) { parmDesc = string.Join("\r\n", parms.Select(p => $"{p.Name} ({p.ParameterType.ToString()})")); // TODO: Provide "easy to read" description for type, e.g. nullabe Int32 can be something like 'int?' and 'List<string>' just 'string[]' } return(null, bestMatch.Item3, $"Failed to find suitable overload.\r\nError: {bestMatch.Item2}\r\nBest match requires parameters:\r\n{parmDesc}"); } var matchedRegMethod = bestMatch.Item3; var cacheKey = $"{matchedRegMethod.Registration.PluginAssemblyInstanceId}; {matchedRegMethod.Registration.TypeInfo.FullName}"; plugin.ServerMethodPlugin pluginInstance = null; lock (ServerMethodInstanceCache) { if (ServerMethodInstanceCache.ContainsKey(cacheKey)) { pluginInstance = ServerMethodInstanceCache[cacheKey]; } else // instantiate a new instance { try { pluginInstance = (plugin.ServerMethodPlugin)matchedRegMethod.Registration.Assembly.CreateInstance(matchedRegMethod.Registration.TypeInfo.FullName); var initMethod = typeof(plugin.ServerMethodPlugin).GetMethod("InitSM", BindingFlags.Instance | BindingFlags.NonPublic); if (initMethod != null) { initMethod.Invoke(pluginInstance, new object[] { new Func <SqlConnection>(() => { if (this.ExecutionConnection != null) { var con = new SqlConnection(this.ExecutionConnection.ConnectionStringDecrypted); con.Open(); return(con); } return(new SqlConnection()); }) }); } else { SessionLog.Warning($"Failed to find InitSM method on plugin {matchedRegMethod.Registration.TypeInfo.FullName} from assembly {matchedRegMethod.Registration.Assembly.FullName}. Make sure the correct version of the jsdal plugin is used and that you derive from the correct base class (should be ServerMethodPlugin)."); } var setGetServicesFuncMethod = typeof(plugin.PluginBase).GetMethod("SetGetServicesFunc", BindingFlags.Instance | BindingFlags.NonPublic); if (setGetServicesFuncMethod != null) { setGetServicesFuncMethod.Invoke(pluginInstance, new object[] { new Func <Type, plugin.PluginService>(serviceType => { if (serviceType == typeof(plugin.BlobStoreBase)) { return(BlobStore.Instance); } return(null); }) }); } ServerMethodManager.RegisterInstanceUse(this, matchedRegMethod); ServerMethodInstanceCache.Add(cacheKey, pluginInstance); } catch (Exception ex) { SessionLog.Error($"Failed to instantiate plugin {matchedRegMethod.Registration.TypeInfo.FullName} from assembly {matchedRegMethod.Registration.Assembly.FullName}. See exception that follows."); SessionLog.Exception(ex); } } } // lock return(pluginInstance, matchedRegMethod, null); }
// look for all supported types of plugins and validate public static bool ParsePluginSource(string existingId, string code, out List <BasePluginRuntime> parsedPlugins, out List <string> problems) { problems = new List <string>(); parsedPlugins = new List <BasePluginRuntime>(); try { var allPluginTypes = new Type[] { typeof(jsdal_plugin.ServerMethodPlugin), typeof(jsdal_plugin.ExecutionPlugin), typeof(jsdal_plugin.BackgroundThreadPlugin) }; var allPluginTypeFullNames = from t in allPluginTypes select t.FullName; var tree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("TmpAsm", syntaxTrees: new[] { tree }, references: GetCommonMetadataReferences()); var model = compilation.GetSemanticModel(tree); var root = model.SyntaxTree.GetRoot(); // get a list of all supported Plugin classes var pluginClasses = root .DescendantNodes() .OfType <ClassDeclarationSyntax>() .Select(cls => new { ClassDeclaration = cls, Symbol = model.GetDeclaredSymbol(cls) }) .Where(cls => allPluginTypeFullNames.Contains(cls.Symbol.BaseType.ToString())) //.Where(cls => cls.Symbol.BaseType.ToString() == typeof(BasePluginRuntime).FullName) .ToList() ; if (pluginClasses.Count == 0) { problems.Add($"You need to have at least one type that inherits from on of the following: {string.Join(", ", allPluginTypeFullNames)}."); return(false); } foreach (var pc in pluginClasses) { var pluginDataAttrib = pc.Symbol.GetAttributes().FirstOrDefault(a => a.AttributeConstructor?.ContainingType? .ConstructedFrom? .ToDisplayString() .Equals("jsdal_plugin.PluginDataAttribute", StringComparison.OrdinalIgnoreCase) ?? false); if (pluginDataAttrib != null) { if (pluginDataAttrib.ConstructorArguments.Length == 0) { problems.Add($"Type '{pc.Symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}' has a PluginData attribute with no value. Please provide a valid GUID value."); } else { var namePart = pluginDataAttrib.ConstructorArguments[0]; var guidPart = pluginDataAttrib.ConstructorArguments[1]; var descPart = pluginDataAttrib.ConstructorArguments[2]; var newParsedPlugin = new BasePluginRuntime(); parsedPlugins.Add(newParsedPlugin); if (namePart.IsNull || string.IsNullOrWhiteSpace(namePart.Value.ToString())) { problems.Add($"Type '{pc.Symbol.ToDisplayString()}' has a PluginData attribute with a null or empty Name value."); } else { newParsedPlugin.Name = namePart.Value.ToString(); } if (guidPart.IsNull || string.IsNullOrWhiteSpace(guidPart.Value.ToString()) || !Guid.TryParse(guidPart.Value.ToString(), out var gg) || gg == Guid.Empty) { problems.Add($"Type '{pc.Symbol.ToDisplayString()}' has a PluginData attribute with an invalid Guid value."); } else { newParsedPlugin.PluginGuid = gg.ToString().ToLower(); } if (!descPart.IsNull) { newParsedPlugin.Description = descPart.Value.ToString(); if (string.IsNullOrWhiteSpace(newParsedPlugin.Description)) { newParsedPlugin.Description = null; } } // if adding a new module if (existingId == null || existingId.Equals("new", StringComparison.OrdinalIgnoreCase)) { var existing = ServerMethodManager .GetRegistrationByPluginGuid(newParsedPlugin.PluginGuid); if (existing != null) { problems.Add($"The type '{pc.Symbol.ToDisplayString()}' has a Plugin Guid that conflicts with an existing loaded plugin. The conflict occurred with '{existing.TypeInfo.FullName}'."); } } } } else { problems.Add($"The type '{pc.Symbol.ToDisplayString()}' is missing a PluginData attribute declaration."); } } return(problems.Count == 0); } catch (CompilationErrorException ce) { problems.Add("Compilation errors."); foreach (var d in ce.Diagnostics) { problems.Add(d.GetMessage()); } return(false); } }
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?}"); }); }