Files
jdescopingtool/PLANS/ConnectionStringsEditor-Implementation.md
Joseph Doherty db663cc82d feat(configmanager): add ConnectionStrings editor with test connection support
Adds a new ConnectionStrings section to ConfigManager allowing users to manage
database connection strings with provider selection, connection testing, and
visual feedback for connection state.
2026-01-22 11:12:08 -05:00

13 KiB

ConnectionStrings Editor - Implementation Plan

Overview

Step-by-step implementation guide for adding ConnectionStrings editor to ConfigManager.

Estimated tasks: 12 tasks in 4 batches


Batch 1: Data Models

Task 1: Create ConnectionProvider enum

File: NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionProvider.cs

namespace JdeScoping.ConfigManager.Models;

/// <summary>
/// Database provider types supported by the ConnectionStrings editor.
/// </summary>
public enum ConnectionProvider
{
    Generic,
    SqlServer,
    Oracle
}

Verification: Build succeeds.


Task 2: Create ConnectionStringEntry model

File: NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringEntry.cs

namespace JdeScoping.ConfigManager.Models;

/// <summary>
/// Represents a single connection string entry with provider-specific fields.
/// </summary>
public class ConnectionStringEntry
{
    public string Name { get; set; } = string.Empty;
    public ConnectionProvider Provider { get; set; } = ConnectionProvider.Generic;

    // SqlServer fields
    public string? Server { get; set; }
    public string? Database { get; set; }
    public string? UserId { get; set; }
    public string? Password { get; set; }
    public string Encrypt { get; set; } = "True";
    public bool TrustServerCertificate { get; set; }
    public int ConnectionTimeout { get; set; } = 30;
    public string? ApplicationName { get; set; }

    // Oracle fields
    public string? Host { get; set; }
    public int Port { get; set; } = 1521;
    public string? ServiceName { get; set; }

    // Generic fields
    public string? RawConnectionString { get; set; }

    /// <summary>
    /// Generates the connection string based on the Provider type.
    /// </summary>
    public string GenerateConnectionString()
    {
        return Provider switch
        {
            ConnectionProvider.SqlServer => GenerateSqlServerConnectionString(),
            ConnectionProvider.Oracle => GenerateOracleConnectionString(),
            ConnectionProvider.Generic => RawConnectionString ?? string.Empty,
            _ => string.Empty
        };
    }

    private string GenerateSqlServerConnectionString()
    {
        var parts = new List<string>();

        if (!string.IsNullOrWhiteSpace(Server))
            parts.Add($"Server={Server}");
        if (!string.IsNullOrWhiteSpace(Database))
            parts.Add($"Database={Database}");
        if (!string.IsNullOrWhiteSpace(UserId))
            parts.Add($"User Id={UserId}");
        if (!string.IsNullOrWhiteSpace(Password))
            parts.Add($"Password={Password}");
        if (!string.IsNullOrWhiteSpace(Encrypt))
            parts.Add($"Encrypt={Encrypt}");
        if (TrustServerCertificate)
            parts.Add("TrustServerCertificate=True");
        if (ConnectionTimeout != 30)
            parts.Add($"Connection Timeout={ConnectionTimeout}");
        if (!string.IsNullOrWhiteSpace(ApplicationName))
            parts.Add($"Application Name={ApplicationName}");

        return string.Join(";", parts);
    }

    private string GenerateOracleConnectionString()
    {
        var parts = new List<string>();

        var host = Host ?? "localhost";
        var port = Port > 0 ? Port : 1521;
        var service = ServiceName ?? "";

        parts.Add($"Data Source=//{host}:{port}/{service}");

        if (!string.IsNullOrWhiteSpace(UserId))
            parts.Add($"User Id={UserId}");
        if (!string.IsNullOrWhiteSpace(Password))
            parts.Add($"Password={Password}");
        if (ConnectionTimeout > 0 && ConnectionTimeout != 30)
            parts.Add($"Connection Timeout={ConnectionTimeout}");

        return string.Join(";", parts);
    }
}

Verification: Build succeeds.


Task 3: Create ConnectionStringsSection model

File: NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringsSection.cs

namespace JdeScoping.ConfigManager.Models;

/// <summary>
/// Configuration section for connection strings.
/// </summary>
public class ConnectionStringsSection
{
    public List<ConnectionStringEntry> Entries { get; set; } = new();
}

Verification: Build succeeds.


Task 4: Add ConnectionStrings to ConfigModel

File: NEW/src/Utils/JdeScoping.ConfigManager/Models/ConfigModel.cs

Add property to existing ConfigModel class:

public ConnectionStringsSection ConnectionStrings { get; set; } = new();

Verification: Build succeeds.


Batch 2: ViewModels

Task 5: Create ConnectionStringEntryViewModel

File: NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/Forms/ConnectionStringEntryViewModel.cs

Create ViewModel that wraps ConnectionStringEntry with:

  • All properties exposed with change notification
  • GeneratedConnectionString computed property
  • IsPasswordVisible toggle property
  • TogglePasswordVisibilityCommand
  • ProviderDisplay and ServerDisplay for table columns

Key implementation details:

  • Constructor takes ConnectionStringEntry model and Action onChanged
  • All setters call OnPropertyChanged() and _onChanged()
  • When Provider changes, also notify GeneratedConnectionString
  • ServerDisplay returns Server (SqlServer), Host (Oracle), or "-" (Generic)

Verification: Build succeeds.


Task 6: Create ConnectionStringsFormViewModel

File: NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/Forms/ConnectionStringsFormViewModel.cs

Create main ViewModel with:

  • Connections : ObservableCollection<ConnectionStringEntryViewModel>
  • SelectedConnection : ConnectionStringEntryViewModel?
  • HasSelection : bool (computed from SelectedConnection != null)
  • ConnectionCount : int (computed from Connections.Count)
  • AvailableProviders : IReadOnlyList<ConnectionProvider>
  • EncryptOptions : IReadOnlyList<string> = ["True", "False", "Strict"]

Commands:

  • AddConnectionCommand - creates new entry with default name "NewConnection", selects it
  • DeleteConnectionCommand - requires confirmation via IDialogService, removes selected
  • ValidateConnectionCommand - validates syntax, shows result via IDialogService
  • TestConnectionCommand - tests actual connection, shows modal result

Constructor takes:

  • ConnectionStringsSection model
  • Action onChanged
  • IDialogService dialogService

Verification: Build succeeds.


Task 7: Create IConnectionTestService interface and implementation

File: NEW/src/Utils/JdeScoping.ConfigManager/Services/IConnectionTestService.cs

namespace JdeScoping.ConfigManager.Services;

public interface IConnectionTestService
{
    Task<ConnectionTestResult> TestConnectionAsync(string connectionString, ConnectionProvider provider);
}

public class ConnectionTestResult
{
    public bool Success { get; init; }
    public string Message { get; init; } = string.Empty;
    public TimeSpan? Duration { get; init; }
}

File: NEW/src/Utils/JdeScoping.ConfigManager/Services/ConnectionTestService.cs

Implementation that:

  • Uses Microsoft.Data.SqlClient for SqlServer
  • Uses Oracle.ManagedDataAccess.Client for Oracle (or stub if not available)
  • Returns success/failure with timing and error message

Verification: Build succeeds.


Batch 3: Views (AXAML)

Task 8: Create ConnectionStringsFormView.axaml

File: NEW/src/Utils/JdeScoping.ConfigManager/Views/Forms/ConnectionStringsFormView.axaml

Create view with:

  1. Header section ("Connection Strings" with separator)
  2. Connections list section:
    • DataGrid with Name, Provider, Server columns
    • Height="200", selection mode single
    • Toolbar with Add, Delete buttons
  3. Placeholder section (when no selection):
    • "Select a connection string to edit" text
  4. Edit form section (when selection exists):
    • Name + Provider fields (always visible)
    • ContentControl with DataTemplates for provider-specific fields
  5. Action buttons section:
    • Validate, Test Connection buttons

Use existing form styling:

  • Background="#0D0F12", BorderBrush="#2D3540"
  • Input Background="#232A35"
  • FontFamily="JetBrains Mono" for values
  • MaxWidth="800" (wider than other forms for table)

Verification: Build succeeds and view renders.


Task 9: Create provider-specific DataTemplates

Within ConnectionStringsFormView.axaml, create DataTemplates in UserControl.Resources:

SqlServerTemplate:

  • Server, Database fields (row)
  • UserId, Password fields (row, password with reveal button)
  • Encrypt dropdown, TrustServerCertificate checkbox (row)
  • ConnectionTimeout, ApplicationName fields (row)
  • Connection string preview box

OracleTemplate:

  • Host, Port fields (row)
  • ServiceName field
  • UserId, Password fields (row, password with reveal button)
  • ConnectionTimeout field
  • Connection string preview box

GenericTemplate:

  • Info banner explaining generic usage
  • Connection String multiline TextBox

Use ContentControl with binding to switch templates based on Provider.

Verification: Build succeeds, templates render correctly.


Task 10: Create ConnectionStringsFormView.axaml.cs code-behind

File: NEW/src/Utils/JdeScoping.ConfigManager/Views/Forms/ConnectionStringsFormView.axaml.cs

Standard Avalonia code-behind:

using Avalonia.Controls;

namespace JdeScoping.ConfigManager.Views.Forms;

public partial class ConnectionStringsFormView : UserControl
{
    public ConnectionStringsFormView()
    {
        InitializeComponent();
    }
}

Verification: Build succeeds.


Batch 4: Integration & Tests

Task 11: Integrate into MainWindowViewModel

File: NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/MainWindowViewModel.cs

Updates needed:

  1. In BuildSettingsNodes() method, add ConnectionStrings node:

    new TreeNodeViewModel("ConnectionStrings", "🔗", TreeNodeType.SettingsSection)
    {
        SectionKey = "ConnectionStrings"
    }
    
  2. In LoadFormViewModelForNode() method, add case for ConnectionStrings:

    "ConnectionStrings" => new ConnectionStringsFormViewModel(
        _appSettingsConfig!.ConnectionStrings,
        MarkCurrentNodeModified,
        _dialogService,
        _connectionTestService),
    
  3. Register IConnectionTestService in DI (App.axaml.cs or Program.cs)

Verification:

  • Build succeeds
  • ConnectionStrings appears in Settings tree
  • Clicking node shows form

Task 12: Add unit tests

File: NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringEntryTests.cs

Test cases:

  • GenerateConnectionString_SqlServer_ProducesCorrectFormat
  • GenerateConnectionString_SqlServer_OmitsDefaultTimeout
  • GenerateConnectionString_Oracle_ProducesEZConnectFormat
  • GenerateConnectionString_Generic_ReturnsRawString
  • DefaultValues_AreCorrect

File: NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringEntryViewModelTests.cs

Test cases:

  • Constructor_InitializesFromModel
  • PropertyChange_UpdatesModel
  • PropertyChange_InvokesOnChanged
  • TogglePasswordVisibility_TogglesIsPasswordVisible
  • ProviderDisplay_ReturnsCorrectString
  • ServerDisplay_ReturnsServerForSqlServer
  • ServerDisplay_ReturnsHostForOracle
  • ServerDisplay_ReturnsDashForGeneric

File: NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringsFormViewModelTests.cs

Test cases:

  • Constructor_InitializesFromModel
  • AddConnection_CreatesNewEntryAndSelectsIt
  • DeleteConnection_RemovesSelectedEntry
  • SelectedConnection_UpdatesHasSelection
  • HasSelection_IsFalseWhenNothingSelected
  • ConnectionCount_ReflectsCollectionSize

Verification: All tests pass.


Summary

Batch Tasks Description
1 1-4 Data models (ConnectionProvider, ConnectionStringEntry, ConnectionStringsSection, ConfigModel update)
2 5-7 ViewModels (ConnectionStringEntryViewModel, ConnectionStringsFormViewModel, IConnectionTestService)
3 8-10 Views (ConnectionStringsFormView, DataTemplates, code-behind)
4 11-12 Integration (MainWindowViewModel, DI registration) and unit tests

Post-Implementation Verification

After all tasks complete:

  1. dotnet build NEW/JdeScoping.slnx - should succeed
  2. dotnet test NEW/JdeScoping.slnx - all tests should pass
  3. Run ConfigManager app:
    • Open a config folder
    • Navigate to Settings → ConnectionStrings
    • Add a new SqlServer connection
    • Fill in fields, verify preview updates
    • Test connection works
    • Save config, verify appsettings.json updated