Source of truth (2026)
  • DataManagementEngineStandard/Services/BeepService.cs
  • DataManagementModelsStandard/Services/IBeepService.cs
  • DataManagementEngineStandard/Services/RegisterBeepinServiceCollection.cs
  • DataManagementEngineStandard/Services/BeepConnectionRepository.cs, IConnectionStorageProvider.cs, JsonConnectionStorageProvider.cs, ConnectionHealthMonitor.cs
  • DataManagementEngineStandard/Services/EnvironmentService.cs
  • BeepServiceExtensions.*.cs — Desktop, Web, Blazor, Maui, Logging, Audit, AddinDiscovery, DataSources, Diagnostics, Retention, Schema, EntityDiscovery

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; }
}
Namespace note. The concrete class lives in 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

TypeDescription
ApplicationFull application configuration
DataConnectorData connector configuration — also what ConfigureForDesignTime() uses
ContainerContainer/host configuration
These are the only three values. Declared in 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

  1. Store AppRepoName (defaults to "Beep" when null) and configuration type
  2. Resolve BeepDirectory — use directorypath, or fall back to EnvironmentService.CreateMainFolder() when it is null/empty
  3. Initialize ErrorsInfo, DMLogger, JsonLoader
  4. Create ConfigEditor with root path Path.Combine(BeepDirectory, "Beep")
  5. Create Util with logger, errors, config
  6. Create the assembly handler (Default or SharedContext) via CreateAssemblyHandler
  7. Create DMEEditor wired to all above
  8. Wire BeepConnectionRepository into Config_editor.ConnectionCatalogRepository
  9. Register DI services (singleton or scoped) — only when a Services collection was supplied
  10. Initialize DMEEditor.Passedarguments and set ErrorObject.Flag = Errors.Ok
  11. Load configurations (drivers, mappings, queries, folders) if not already loaded
Connection catalog wiring. Since the repository refactor, 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.
Configure() does not throw. The whole body is wrapped in a 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

  1. Thread-safe via _assembliesLock
  2. Calls AssemblyHandler.LoadAllAssembly(progress, token)
  3. Syncs loaded assemblies to ConfigEditor.LoadedAssemblies
  4. 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.

The two paths are not symmetric. 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

TypeClassWhen 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;
}
Disposal does not cancel in-flight assembly loading. 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
The 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.

PlatformSignature
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.

Internal. 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