public async IAsyncEnumerable <IntegrationRegistration> GetIntegrations() { var rootDirectory = Path.GetFullPath(this.Configuration.GetValue <string>("Mifs:IntegrationPath")); var integrationRegistrations = new List <IntegrationRegistration>(); var configurationFilesByDirectory = Directory.EnumerateFiles(rootDirectory, "*appsettings.json", SearchOption.AllDirectories) .GroupBy(file => Path.GetDirectoryName(file)); foreach (var configurationFiles in configurationFilesByDirectory) { var directory = Path.GetFullPath(configurationFiles.Key !); var configuration = this.BuildConfigurationForIntegration(configurationFiles); var integrationConfiguration = configuration.GetSection("Integration") .Get <IntegrationConfiguration>(); if (!this.IsIntegrationConfigurationValid(integrationConfiguration)) { this.Logger.LogWarning("Failed to register Integration in directory '{directory}' because it was invalid. Check its configuration contains the Integration Name and Entry Assembly.", directory); continue; } this.Logger.LogInformation("Found Integration {integrationName} in directory {directory}.", integrationConfiguration.Name, directory); var integrationRegistration = new IntegrationRegistration(directory, configuration); yield return(integrationRegistration); await Task.Yield(); } }
public void Register(IntegrationRegistration integrationRegistration) { var integrationName = integrationRegistration.Name; if (this.TryGetRegistration(integrationName, out var existingRegistration)) { if (existingRegistration is not null && integrationRegistration.Directory != existingRegistration.Directory) { this.Logger.LogWarning("Trying to register {integrationName} from directory {newDirectory} but it is already registered for directory {existingDirectory}. If you intended to change the directory deregister the old one first.", integrationName, integrationRegistration.Directory, existingRegistration.Directory); } else { this.Logger.LogInformation("Attempted to register {integrationName} but it is already registered.", integrationName); } return; }
private async Task <IntegrationHostData> CreateAndStartIntegrationHost(ICacheEntry cacheEntry, IntegrationRegistration integrationRegistration, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { return(new IntegrationHostData(null, HostStatus.Errored) { Message = "Startup cancelled." }); } // Pull a fresh copy of the IntegrationConfiguration to do some validation var integrationConfiguration = integrationRegistration.IntegrationConfiguration; var integrationName = integrationConfiguration.Name; var entryAssembly = integrationConfiguration.EntryAssembly; if (integrationName is null) { return(new IntegrationHostData(null, HostStatus.Errored) { Message = "Integration Name not set properly in configuration." }); } if (entryAssembly is null) { return(new IntegrationHostData(null, HostStatus.Errored) { Message = "Entry assembly not set properly in configuration." }); } if (!integrationConfiguration.IsEnabled) { this.Logger.LogInformation("Did not start {integrationName}. The Integration is currently disabled.", integrationName); return(new IntegrationHostData(null, HostStatus.Disabled)); } var entryAssemblyPath = Path.Combine(integrationRegistration.Directory, entryAssembly); if (!File.Exists(entryAssemblyPath)) { this.Logger.LogError("Failed to Start {integrationName} because the entry assembly {entryAssemblyPath} could not be found", integrationName, entryAssemblyPath); return(new IntegrationHostData(null, HostStatus.Errored) { Message = $"Failed to Start integration {integrationName} because the entry assembly {entryAssemblyPath} could not be found" }); } // Use a custom Assembly load context to allow each Host to use its own dependencies. // If a custom dependency is not defined in the same folder, it will fallback to using any Assemblies loaded in the default context. // The only requirement is that the Mifs.dll cannot be different because that would result in different instances of IInitializationInstance's // making FindAndCreateIntegrationInitializationInstance never find a type to instantiate. var assemblyLoadContext = new IntegrationAssemblyLoadContext(integrationRegistration.Directory); var bootstrapInstance = this.FindAndCreateIntegrationBootstrapInstance(entryAssemblyPath, AssemblyLoadContext.Default); if (bootstrapInstance is null) { return(new IntegrationHostData(null, HostStatus.Errored) { Message = "" }); } var host = await this.IntegrationHostFactory.CreateAndStart(bootstrapInstance, integrationRegistration.Configuration, cancellationToken); if (host is null) { return(new IntegrationHostData(host, HostStatus.Errored) { Message = $"Host failed to create" }); } // Make sure that when the host is shut down the assembly load context gets unloaded. var applicationLifetime = host.Services.GetRequiredService <IHostApplicationLifetime>(); applicationLifetime.ApplicationStopping.Register(() => assemblyLoadContext.Unload()); cacheEntry.RegisterPostEvictionCallback(this.OnCacheEntryEvicted); this.Logger.LogInformation("{integrationName} Started.", integrationName); return(new IntegrationHostData(host, HostStatus.Started)); }