BeepService
Central service that bootstraps and connects all BeepDM core components
DataManagementEngineStandard/Services/BeepService.csDataManagementModelsStandard/Services/IBeepService.csDataManagementEngineStandard/Services/RegisterBeepinServiceCollection.csDataManagementEngineStandard/Services/BeepConnectionRepository.cs,IConnectionStorageProvider.cs,JsonConnectionStorageProvider.cs,ConnectionHealthMonitor.csDataManagementEngineStandard/Services/EnvironmentService.csBeepServiceExtensions.*.cs—Desktop,Web,Blazor,Maui,Logging,Audit,AddinDiscovery,DataSources,Diagnostics,Retention,Schema,EntityDiscovery
Table of Contents
Overview
BeepService is the central bootstrap service that creates and wires together all core BeepDM components:
- DMEEditor - Main data management orchestrator
- ConfigEditor - Configuration persistence manager
- AssemblyHandler - Plugin discovery and loading
- DMLogger - Logging subsystem
- JsonLoader - Configuration file I/O
- Util/ErrorsInfo - Utilities and error handling
It implements IBeepService and IDisposable, with thread-safe initialization.
BeepService Class
// namespace TheTechIdea.Beep.Container.Services
public class BeepService : IBeepService, IDisposable
{
public BeepService();
public BeepService(IServiceCollection services);
// Core Components
public IDMEEditor DMEEditor { get; set; }
public IConfigEditor Config_editor { get; set; }
public IDMLogger lg { get; set; }
public IUtil util { get; set; }
public IErrorsInfo Erinfo { get; set; }
public IJsonLoader jsonLoader { get; set; }
public IAssemblyHandler LLoader { get; set; }
public IServiceCollection Services { get; }
// Configuration
public string AppRepoName { get; }
public BeepConfigType ConfigureationType { get; }
public string BeepDirectory { get; }
public AssemblyHandlerType AssemblyHandlerType { get; set; }
// Bootstrap
public void Configure(string directorypath, string containername,
BeepConfigType configType, bool AddasSingleton = false);
public void ConfigureForDesignTime();
public void LoadConfigurations(string AppReponame);
// Assemblies
public void LoadAssemblies();
public void LoadAssemblies(Progress<PassedArgs> progress);
public Task LoadAssembliesAsync(Progress<PassedArgs> progress);
// DI registration
public void LoadServicesSingleton();
public void LoadServicesScoped();
// Environments
public Dictionary<EnvironmentType, IBeepEnvironment> Environments { get; set; }
public void LoadEnvironments();
public void SaveEnvironments();
public virtual void Dispose(bool disposing);
public void Dispose();
// Legacy (deprecated)
[Obsolete("Use AppRepoName instead")]
public string Containername { get; }
}
TheTechIdea.Beep.Container.Services, while the
IBeepService interface lives in TheTechIdea.Beep.Services.
Services and AssemblyHandlerType are on the concrete class only —
they are not part of the IBeepService interface, so set
AssemblyHandlerType against a concrete BeepService reference.
Configuration
Configure
Initializes all core components and folder structure:
public void Configure(
string directorypath,
string containername,
BeepConfigType configType,
bool AddasSingleton = false)
BeepConfigType
| Type | Description |
|---|---|
Application | Full application configuration |
DataConnector | Data connector configuration — also what ConfigureForDesignTime() uses |
Container | Container/host configuration |
DataManagementModelsStandard/Enums/Enums.cs as
Application, DataConnector, Container. Earlier revisions of this page listed
Plugin and DesignTime members — neither exists. Design-time mode
is not a BeepConfigType; it is detected via
LicenseManager.UsageMode and simply bootstraps with
DataConnector.
ConfigureForDesignTime
public void ConfigureForDesignTime()
// Automatically called in the parameterless constructor when
// LicenseManager.UsageMode == LicenseUsageMode.Designtime
// Calls: Configure(AppContext.BaseDirectory, "DesignTimeContainer",
// BeepConfigType.DataConnector, AddasSingleton: true)
Internal Initialization Flow
- Store
AppRepoName(defaults to"Beep"when null) and configuration type - Resolve
BeepDirectory— usedirectorypath, or fall back toEnvironmentService.CreateMainFolder()when it is null/empty - Initialize
ErrorsInfo,DMLogger,JsonLoader - Create
ConfigEditorwith root pathPath.Combine(BeepDirectory, "Beep") - Create
Utilwith logger, errors, config - Create the assembly handler (Default or SharedContext) via
CreateAssemblyHandler - Create
DMEEditorwired to all above - Wire
BeepConnectionRepositoryintoConfig_editor.ConnectionCatalogRepository - Register DI services (singleton or scoped) — only when a
Servicescollection was supplied - Initialize
DMEEditor.Passedargumentsand setErrorObject.Flag = Errors.Ok - Load configurations (drivers, mappings, queries, folders) if not already loaded
Configure() attaches a BeepConnectionRepository to the
ConfigEditor so every connection create/update/delete flows through a single,
scope- and profile-aware source of truth:
var catalogRepo = new BeepConnectionRepository(this);
Config_editor.ConnectionCatalogRepository = catalogRepo;
See Connection Catalog Repository below.
try/catch that logs to lg and Console and then
returns. A failed Configure() therefore leaves DMEEditor and
other components null rather than surfacing an exception — check
service.DMEEditor != null before use if the path or permissions are uncertain.
Assembly Loading
LoadAssemblies
// Synchronous
public void LoadAssemblies();
public void LoadAssemblies(Progress<PassedArgs> progress);
// Async wrapper
public async Task LoadAssembliesAsync(Progress<PassedArgs> progress);
Loading is idempotent - subsequent calls are no-ops after first load.
Internal Flow
- Thread-safe via
_assembliesLock - Calls
AssemblyHandler.LoadAllAssembly(progress, token) - Syncs loaded assemblies to
ConfigEditor.LoadedAssemblies - Calls
RestoreDriverConfigAndAutoLoad()
RestoreDriverConfigAndAutoLoad
After assembly loading, automatically restores driver configurations and loads driver DLLs from local packages:
private void RestoreDriverConfigAndAutoLoad()
{
if (Config_editor == null || LLoader == null)
return;
// 1. Load connection driver configs
Config_editor.LoadConnectionDriversConfigValues();
if (Config_editor.DataDriversClasses == null || Config_editor.DataDriversClasses.Count == 0)
return;
// 2. For each driver with the auto-load flag:
bool changed = false;
foreach (var driver in Config_editor.DataDriversClasses.Where(ShouldAutoLoadDriver))
{
try
{
// Try loading from the local package first
if (LLoader.LoadDriverFromLocalPackage(driver, out _))
{
changed = true;
continue;
}
// Otherwise record whether the NuGet package is missing
var hasLocalPackage = LLoader.HasLocalPackage(driver);
if (driver.NuggetMissing == hasLocalPackage)
{
driver.NuggetMissing = !hasLocalPackage;
changed = true;
}
}
catch (Exception ex)
{
lg?.WriteLog($"AutoLoad driver '{driver?.PackageName}' failed: {ex.Message}");
}
}
// 3. Refresh the loaded-assembly list; persist only if something actually changed
if (LLoader?.Assemblies != null)
Config_editor.LoadedAssemblies = LLoader.Assemblies.Select(c => c.DllLib).ToList();
if (changed)
Config_editor.SaveConnectionDriversConfigValues();
}
// A driver is auto-loaded when it is flagged AutoLoad, names a package,
// and is either marked missing or has a package already present.
private static bool ShouldAutoLoadDriver(ConnectionDriversConfig driver)
{
return driver != null
&& driver.AutoLoad
&& !string.IsNullOrWhiteSpace(driver.PackageName)
&& (driver.IsMissing || !driver.NuggetMissing);
}
Per-driver failures are logged and skipped — one bad driver does not abort the loop.
SaveConnectionDriversConfigValues() is called only when a driver was loaded
or its NuggetMissing state actually changed, so a steady-state startup
performs no config write.
DI Service Registration
BeepService can register core components in the DI container:
Singleton Registration
public void LoadServicesSingleton()
{
Services.AddSingleton<IDMLogger>(lg);
Services.AddSingleton<IConfigEditor>(Config_editor);
Services.AddSingleton<IDMEEditor>(DMEEditor);
Services.AddSingleton<IUtil>(util);
Services.AddSingleton<IJsonLoader>(jsonLoader);
Services.AddSingleton<IAssemblyHandler>(LLoader);
}
Scoped Registration
public void LoadServicesScoped()
{
Services.AddKeyedScoped<IDMLogger, DMLogger>("Logger");
Services.AddKeyedScoped<IConfigEditor, ConfigEditor>("ConfigEditor");
Services.AddKeyedScoped<IDMEEditor, DMEEditor>("Editor");
Services.AddKeyedScoped<IUtil, Util>("Util");
Services.AddKeyedScoped<IJsonLoader, JsonLoader>("JsonLoader");
// The assembly handler honours AssemblyHandlerType
if (AssemblyHandlerType == AssemblyHandlerType.SharedContext)
Services.AddKeyedScoped<IAssemblyHandler, SharedContextAssemblyHandler>("AssemblyHandler");
else
Services.AddKeyedScoped<IAssemblyHandler, AssemblyHandler>("AssemblyHandler");
}
Note: Scoped registration uses keyed services for resolution by name.
LoadServicesSingleton()
registers the already-constructed instances that Configure() built, so
the container hands back exactly the objects BeepService is using.
LoadServicesScoped() registers types — the container constructs new
instances per scope, and they are not the ones held by
BeepService. Resolve scoped components by key:
var editor = provider.GetRequiredKeyedService<IDMEEditor>("Editor");
Which path runs is decided by the AddasSingleton argument to
Configure(), and registration is skipped entirely when the service was built
with the parameterless constructor (no Services collection).
Assembly Handler Types
| Type | Class | When to Use |
|---|---|---|
Default |
AssemblyHandler |
Standard applications. Each instance manages its own assembly context. |
SharedContext |
SharedContextAssemblyHandler |
Multi-tenant or plugin-heavy apps. Assemblies are shared across instances, reducing memory. |
var service = new BeepService();
service.AssemblyHandlerType = AssemblyHandlerType.SharedContext;
service.Configure(path, "MyApp", BeepConfigType.Application);
Lifecycle
Thread Safety
_configLock- Guards configuration loading_assembliesLock- Guards assembly loading- Configuration loading is idempotent (single call only)
- Assembly loading is idempotent (single call only)
Disposal
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
if (disposedValue) return;
if (disposing)
{
DMEEditor?.Dispose();
Config_editor?.Dispose();
LLoader?.Dispose();
}
// Null out the component fields
DMEEditor = null; Config_editor = null; lg = null;
util = null; Erinfo = null; jsonLoader = null; LLoader = null;
disposedValue = true;
}
Dispose() does not call tokenSource.Cancel(), and the
CancellationTokenSource is not disposed. A
LoadAssembliesAsync() still running will keep going against components that
have just been set to null. Await any outstanding load before disposing the service.
lg, util, Erinfo and jsonLoader are
dropped without being disposed.
Environments
BeepService maintains an environment dictionary, initialized in both constructors so it is never null:
public Dictionary<EnvironmentType, IBeepEnvironment> Environments { get; set; }
public void LoadEnvironments(); // read <BeepDirectory>/Environments/*.json
public void SaveEnvironments(); // write <BeepDirectory>/Environments/<EnvironmentName>.json
Use IBeepEnvironment implementations to configure environment-specific behavior (Development, Staging, Production).
LoadEnvironments
Ensures the app-repo folder exists, then deserializes every *.json file under
<BeepDirectory>/Environments and adds it to the dictionary keyed by
env.EnvironmentType:
var service = new BeepService();
service.Configure(AppContext.BaseDirectory, "MyApp", BeepConfigType.Application);
service.LoadEnvironments();
if (service.Environments.TryGetValue(EnvironmentType.Production, out var prod))
Console.WriteLine($"Loaded environment: {prod.EnvironmentName}");
SaveEnvironments
service.Environments[EnvironmentType.Development] = myDevEnvironment;
service.SaveEnvironments(); // one file per environment, named after EnvironmentName
Environments folder is not created for you. Both methods
check Directory.Exists(envpath) and silently do nothing when the folder is
absent — so a first SaveEnvironments() on a fresh install writes nothing.
Create it once before saving:
Directory.CreateDirectory(Path.Combine(service.BeepDirectory, "Environments"));
Note also that LoadEnvironments() uses Environments.Add(...), which
throws on a duplicate EnvironmentType — calling it twice, or shipping two files
that declare the same type, raises ArgumentException.
Connection Catalog Repository
Configure() constructs a BeepConnectionRepository and assigns it to
Config_editor.ConnectionCatalogRepository. It implements
IConnectionCatalogRepository and is the single source of truth for connection
CRUD, adding scope and profile awareness on top of DataConnections.json.
public sealed class BeepConnectionRepository : IConnectionCatalogRepository
{
public BeepConnectionRepository(IBeepService beepService,
IConnectionStorageProvider? storageProvider = null);
public event EventHandler? ConnectionsChanged;
public ConnectionStorageScope ActiveScope { get; set; } // default: Project
public string ActiveProfileName { get; set; } // default: "Default"
public bool UseScopePrecedence { get; set; } // default: true
public IReadOnlyList<ConnectionProperties> LoadConnections();
public bool AddOrUpdate(ConnectionProperties connection, bool persist = true);
public bool Remove(string connectionName, bool persist = true);
public bool Save(List<ConnectionProperties> connections);
public bool Promote(ConnectionStorageScope targetScope,
ConnectionConflictPolicy conflictPolicy, out string message);
public bool ExportPackage(string packagePath, bool includeEncryptedSecretsOnly, out string message);
public bool ImportPackage(...);
}
Storage is pluggable through IConnectionStorageProvider; the default is
JsonConnectionStorageProvider. Subscribe to ConnectionsChanged to
refresh UI when the catalog is mutated from elsewhere.
ConnectionHealthMonitor
A separate opt-in helper in the same folder that polls connections and raises transitions.
It is not started by BeepService — construct and start it yourself:
var monitor = new ConnectionHealthMonitor(service.DMEEditor, TimeSpan.FromMinutes(1));
monitor.HealthChanged += (s, e) =>
Console.WriteLine($"{e.ConnectionName}: {e.PreviousStatus} -> {e.CurrentStatus}");
monitor.Start();
var status = await monitor.CheckAsync("northwind");
await monitor.CheckAllAsync();
IReadOnlyDictionary<string, ConnectionHealthStatus> all = monitor.GetAllStatuses();
await monitor.ReconnectAsync("northwind");
monitor.Stop();
monitor.Dispose();
Service Extensions
The BeepServiceExtensions.*.cs partial files add opt-in capabilities on top of
the core bootstrap. They are independent of BeepService.Configure() — none of
them run unless you call them.
Platform presets (logging & audit)
Each platform file exposes a matched logging/audit pair that picks sensible storage paths
for that host. All return IServiceCollection for chaining, and all take an
optional tweak delegate to override the preset.
| Platform | Signature |
|---|---|
| Desktop | AddBeepLoggingForDesktop(string appName = PlatformPaths.DefaultAppName, Action<BeepLoggingOptions> tweak = null)AddBeepAuditForDesktop(string appName = …, Action<BeepAuditOptions> tweak = null) |
| Web | AddBeepLoggingForWeb(string contentRootPath = null, string appName = …, Action<BeepLoggingOptions> tweak = null)AddBeepAuditForWeb(string contentRootPath = null, string appName = …, Action<BeepAuditOptions> tweak = null) |
| Blazor | AddBeepLoggingForBlazor(IIndexedDbBridge bridge, Action<BeepLoggingOptions> tweak = null)AddBeepAuditForBlazor(IIndexedDbBridge bridge, Action<BeepAuditOptions> tweak = null) |
| MAUI | AddBeepLoggingForMaui(Func<string> appDataDirectoryProvider, string appName = …, Action<BeepLoggingOptions> tweak = null)AddBeepAuditForMaui(Func<string> appDataDirectoryProvider, string appName = …, Action<BeepAuditOptions> tweak = null) |
| Generic | AddBeepLogging(Action<BeepLoggingOptions> configure = null)AddBeepAudit(Action<BeepAuditOptions> configure = null) |
The Blazor and MAUI overloads are the only ones with a required first
argument — Blazor needs an IIndexedDbBridge for browser storage, and MAUI needs
a Func<string> returning the platform app-data directory
(FileSystem.AppDataDirectory), because that value is not resolvable from this
assembly. The pipelines are published as keyed services under
BeepServiceLoggingExtensions.LoggingPipelineKey
("Beep.Logging.Pipeline") and
BeepServiceAuditExtensions.AuditPipelineKey
("Beep.Audit.Pipeline").
See Audit, Logging & Telemetry for the pipeline internals behind these registrations.
Retention
// Registers IBudgetEnforcer -> DefaultBudgetEnforcer (TryAddSingleton)
services.AddBeepBudgetEnforcer();
// Adds the enforcer plus a hosted background sweeper
services.AddBeepRetentionSweeper(TimeSpan.FromHours(6));
AddBeepRetentionSweeper calls AddBeepBudgetEnforcer() for you and
registers an IHostedService, so it only sweeps in a host that runs hosted
services (Generic Host, ASP.NET Core).
Schema, entity discovery and addins
services.AddBeepSchemaManager();
services.AddBeepEntityDiscovery();
// Reflection-based discovery of IDM_Addin views and IBeepViewModel view models,
// registered as keyed services keyed on type name
services.AddBeepViews();
services.AddBeepViewModels();
// Or register one explicitly
services.AddBeepView(typeof(MyAddinView));
services.AddBeepViewModel(typeof(MyViewModel));
BeepServiceAddinDiscoveryExtensions also exposes two static hook events —
OnRegisterViewModels and OnRegisterAddins — which fire during
discovery so a host can contribute extra registrations. The fluent builder enables view
discovery by default; use WithViewDiscovery(false) to opt out, or
WithAssembliesToScan(...) to narrow the scan.
Registering a driver
beepService.AddDataSourceDriver(new ConnectionDriversConfig
{
PackageName = "Npgsql",
DriverClass = "Npgsql",
classHandler = "PostgreDataSource",
version = "8.0.3",
DatasourceType = DataSourceType.Postgre
});
// Lazy overload — the factory is only invoked if needed
beepService.AddDataSourceDriver(() => BuildDriverConfig());
Both overloads default to skipIfPresent: true and return the
IBeepService for chaining. They are null-tolerant: a null
beep, Config_editor, or config is returned unchanged rather than
throwing. Remember that classHandler must match the
AssemblyClassDefinition.className of the implementing type, or the driver will
silently fail to resolve at GetDataSource() time.
BeepServiceExtensions.Diagnostics.cs
(AttachDiagnostics, RegisterMetricsSnapshot) is
internal — it is wired automatically by the logging/audit registrations and is
not part of the public API.
Usage Examples
Basic Bootstrap
var service = new BeepService();
service.Configure(
directorypath: AppContext.BaseDirectory,
containername: "MyApp",
configType: BeepConfigType.DataConnector);
// Load plugins
service.LoadAssemblies();
// Use
var editor = service.DMEEditor;
var ds = editor.GetDataSource("MyDB");
With DI Container
var services = new ServiceCollection();
var service = new BeepService(services);
service.Configure(path, "MyApp", BeepConfigType.Application,
AddasSingleton: true);
var provider = services.BuildServiceProvider();
var editor = provider.GetRequiredService<IDMEEditor>();
Desktop App with Progress
var service = new BeepService();
service.Configure(baseDir, "DesktopApp", BeepConfigType.Application);
var progress = new Progress<PassedArgs>(args =>
{
Console.WriteLine($"Loading: {args.Messege}");
});
await service.LoadAssembliesAsync(progress);
Console.WriteLine("BeepDM initialized successfully");
Design-Time Support
// When in Visual Studio designer, BeepService auto-configures:
var service = new BeepService();
// Internally calls ConfigureForDesignTime() if design-time
// This enables drag-drop datasource browsing in WinForms/WPF designer