From 84fca2297bdcea2351a5bd2e83e88632406df633 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:39:46 -0400 Subject: [PATCH] Connection dialog cleanup follow-up to #302 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename "(Secondary)" indicator to "(Read-only)" — read-only intent requests routing to a secondary but doesn't guarantee it (the listener may still route to the primary if no readable replica is available). - Deduplicate SqlConnectionStringBuilder construction by adding a GetConnectionString(username, password, db) overload on ServerConnection and routing both the dialog test path and the runtime path through it. - XAML nits: drop redundant Margin and trailing whitespace on the new Read-only intent checkbox. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Controls/QuerySessionControl.axaml.cs | 2 +- .../Dialogs/ConnectionDialog.axaml | 8 ++-- .../Dialogs/ConnectionDialog.axaml.cs | 44 ++----------------- .../Models/ServerConnection.cs | 39 ++++++++++++---- 4 files changed, 39 insertions(+), 54 deletions(-) diff --git a/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs b/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs index 420a9a4..38623ea 100644 --- a/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs +++ b/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs @@ -810,7 +810,7 @@ private async Task ShowConnectionDialogAsync() _connectionString = _serverConnection.GetConnectionString(_credentialService, _selectedDatabase); ServerLabel.Text = _serverConnection.ApplicationIntentReadOnly - ? $"{_serverConnection.ServerName} (Secondary)" + ? $"{_serverConnection.ServerName} (Read-only)" : _serverConnection.ServerName; ServerLabel.Foreground = Brushes.LimeGreen; ConnectButton.Content = "Reconnect"; diff --git a/src/PlanViewer.App/Dialogs/ConnectionDialog.axaml b/src/PlanViewer.App/Dialogs/ConnectionDialog.axaml index e9fc75e..0ae333d 100644 --- a/src/PlanViewer.App/Dialogs/ConnectionDialog.axaml +++ b/src/PlanViewer.App/Dialogs/ConnectionDialog.axaml @@ -81,10 +81,10 @@ + Content="Read-only intent (for AG listeners and readable replicas - including Query Store on secondary replicas)" + Foreground="{DynamicResource ForegroundBrush}" + ToolTip.Tip="Sets ApplicationIntent=ReadOnly. Required when connecting via an AG listener or Azure failover group endpoint to route to a readable secondary (including Query Store on secondary replicas)." + FontSize="12"/> diff --git a/src/PlanViewer.App/Dialogs/ConnectionDialog.axaml.cs b/src/PlanViewer.App/Dialogs/ConnectionDialog.axaml.cs index 5603b3b..38e11c2 100644 --- a/src/PlanViewer.App/Dialogs/ConnectionDialog.axaml.cs +++ b/src/PlanViewer.App/Dialogs/ConnectionDialog.axaml.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Interactivity; using Microsoft.Data.SqlClient; @@ -133,7 +132,10 @@ private async void TestConnection_Click(object? sender, RoutedEventArgs e) try { var connection = BuildServerConnection(); - var connectionString = BuildConnectionString(connection); + var connectionString = connection.GetConnectionString( + LoginBox.Text?.Trim(), + PasswordBox.Text, + "master"); await using var conn = new SqlConnection(connectionString); await conn.OpenAsync(); @@ -237,42 +239,4 @@ private string GetSelectedEncryptMode() return "Mandatory"; } - private string BuildConnectionString(ServerConnection connection) - { - var builder = new SqlConnectionStringBuilder - { - DataSource = connection.ServerName, - InitialCatalog = "master", - ApplicationName = "PlanViewer", - ConnectTimeout = 15, - TrustServerCertificate = connection.TrustServerCertificate, - Encrypt = connection.EncryptMode switch - { - "Optional" => SqlConnectionEncryptOption.Optional, - "Strict" => SqlConnectionEncryptOption.Strict, - _ => SqlConnectionEncryptOption.Mandatory - }, - ApplicationIntent = connection.ApplicationIntentReadOnly - ? ApplicationIntent.ReadOnly - : ApplicationIntent.ReadWrite - }; - - switch (connection.AuthenticationType) - { - case AuthenticationTypes.SqlServer: - builder.UserID = LoginBox.Text?.Trim() ?? ""; - builder.Password = PasswordBox.Text ?? ""; - break; - case AuthenticationTypes.EntraMFA: - builder.Authentication = SqlAuthenticationMethod.ActiveDirectoryInteractive; - if (!string.IsNullOrEmpty(LoginBox.Text?.Trim())) - builder.UserID = LoginBox.Text!.Trim(); - break; - default: - builder.IntegratedSecurity = true; - break; - } - - return builder.ConnectionString; - } } diff --git a/src/PlanViewer.Core/Models/ServerConnection.cs b/src/PlanViewer.Core/Models/ServerConnection.cs index b21c073..e4c1a47 100644 --- a/src/PlanViewer.Core/Models/ServerConnection.cs +++ b/src/PlanViewer.Core/Models/ServerConnection.cs @@ -27,6 +27,32 @@ public class ServerConnection }; public string GetConnectionString(ICredentialService credentialService, string? databaseName = null) + { + string? username = null; + string? password = null; + + switch (AuthenticationType) + { + case AuthenticationTypes.EntraMFA: + var mfaCred = credentialService.GetCredential(Id); + if (mfaCred.HasValue) + username = mfaCred.Value.Username; + break; + + case AuthenticationTypes.SqlServer: + var cred = credentialService.GetCredential(Id); + if (!cred.HasValue) + throw new InvalidOperationException( + $"SQL Server authentication credentials are missing for server '{ServerName}'. Please configure credentials before connecting."); + username = cred.Value.Username; + password = cred.Value.Password; + break; + } + + return GetConnectionString(username, password, databaseName); + } + + public string GetConnectionString(string? username, string? password, string? databaseName = null) { var builder = new SqlConnectionStringBuilder { @@ -53,18 +79,13 @@ public string GetConnectionString(ICredentialService credentialService, string? { case AuthenticationTypes.EntraMFA: builder.Authentication = SqlAuthenticationMethod.ActiveDirectoryInteractive; - var mfaCred = credentialService.GetCredential(Id); - if (mfaCred.HasValue && !string.IsNullOrEmpty(mfaCred.Value.Username)) - builder.UserID = mfaCred.Value.Username; + if (!string.IsNullOrEmpty(username)) + builder.UserID = username; break; case AuthenticationTypes.SqlServer: - var cred = credentialService.GetCredential(Id); - if (!cred.HasValue) - throw new InvalidOperationException( - $"SQL Server authentication credentials are missing for server '{ServerName}'. Please configure credentials before connecting."); - builder.UserID = cred.Value.Username; - builder.Password = cred.Value.Password; + builder.UserID = username ?? ""; + builder.Password = password ?? ""; break; default: // Windows