Générateurs de chaînes de connexion dans Microsoft.Data.SqlClient

Utilisez SqlConnectionStringBuilder pour créer et modifier des chaînes de connexion via des propriétés typées. Il hérite de DbConnectionStringBuilder. Utilisez-le chaque fois que votre code ajoute ou modifie une valeur, au lieu de concaténer des chaînes.

Le constructeur :

  • Expose chaque mot-clé sous forme de propriété typée, par exemple ConnectTimeout en tant que int et Encrypt en tant que SqlConnectionEncryptOption.
  • Rejette des mots-clés inconnus et de nombreuses valeurs hors plage avec ArgumentException lorsque vous les définissez.
  • Associe des synonymes tels que Server et uid à des mots-clés canoniques tels que Data Source et User ID.
  • Met entre guillemets les valeurs contenant des points-virgules, des guillemets ou des espaces au début ou à la fin.

Pour la liste des mots-clés, voir Options de connexion. Pour le format textuel, voir règles d’analyse.

Chaque échantillon est un programme complet. Les onglets .NET Framework utilisent C# 7.3. Pour créer un projet qui exécute les exemples, voir Créer le projet.

Empêcher l’injection de chaîne de connexion

Lorsque votre code insère un texte non fiable dans une chaîne de connexion, un point-virgule dans ce texte peut ajouter un mot-clé ou remplacer un précédent, car la dernière valeur l’emporte. Le builder stocke la totalité de la valeur dans une seule propriété et la met entre guillemets, donc le texte ne peut pas devenir un autre mot-clé.

Ce programme fixe InitialCatalog à une valeur qui tente de désactiver le pooling :

using Microsoft.Data.SqlClient;

// Simulates a value that came from a user or another untrusted source.
string databaseFromUser = "Orders;Pooling=false";

var builder = new SqlConnectionStringBuilder
{
    DataSource = "tcp:sql.example.com,1433",
    InitialCatalog = databaseFromUser,
    IntegratedSecurity = true,
    Encrypt = SqlConnectionEncryptOption.Strict,
};

Console.WriteLine(builder.ConnectionString);
Console.WriteLine($"Pooling: {builder.Pooling}");

La sortie est la suivante :

Data Source=tcp:sql.example.com,1433;Initial Catalog="Orders;Pooling=false";Integrated Security=True;Encrypt=Strict
Pooling: True

Le générateur place la valeur entre guillemets et Pooling reste True. Le pilote envoie Orders;Pooling=false au serveur comme nom de la base de données.

Mettre entre guillemets empêche l’injection de mots-clés. Il ne décide pas si une valeur est autorisée. Vérifiez l’entrée non fiable par rapport à une liste de serveurs ou bases de données autorisées avant de l’utiliser.

Complétez une chaîne de connexion de base

Stockez les parties d'une chaîne de connexion qui ne changent pas, comme le serveur et la base de données, en configuration. Ajoutez les valeurs secrètes et spécifiques à la charge de travail à l’exécution. Passez la chaîne de base au constructeur du builder, puis définissez les propriétés.

Ce programme lit une chaîne de base et un mot de passe provenant de variables d’environnement distinctes, se connecte avec l’authentification SQL et confirme que le mot de passe n’est pas visible après l’ouverture de la connexion.

using Microsoft.Data.SqlClient;

// The base string holds no secret. The password comes from a separate secret source.
string baseConnectionString =
    Environment.GetEnvironmentVariable("SQL_BASE_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_BASE_CONNECTION_STRING environment variable.");
string password =
    Environment.GetEnvironmentVariable("SQL_PASSWORD")
    ?? throw new InvalidOperationException(
        "Set the SQL_PASSWORD environment variable.");

var builder = new SqlConnectionStringBuilder(baseConnectionString)
{
    Password = password,
    ApplicationName = "Orders.Worker",
};

await using var connection = new SqlConnection(builder.ConnectionString);
await connection.OpenAsync();

await using var command = new SqlCommand("SELECT APP_NAME();", connection);
Console.WriteLine($"APP_NAME(): {await command.ExecuteScalarAsync()}");

var opened = new SqlConnectionStringBuilder(connection.ConnectionString);
Console.WriteLine($"Password visible after open: {opened.ContainsKey("Password") && opened.Password.Length > 0}");

Définissez SQL_BASE_CONNECTION_STRING sur une valeur telle que Server=tcp:<server>,1433;Database=<database>;User ID=<user>;Encrypt=true. La sortie est la suivante :

APP_NAME(): Orders.Worker
Password visible after open: False

Persist Security Info a pour valeur par défaut false, donc le pilote supprime le mot de passe de SqlConnection.ConnectionString après l’ouverture de la connexion. Gardez cette valeur par défaut.

Privilégiez une méthode d’authentification sans mot de passe, comme l’authentification intégrée Windows ou une identité gérée Microsoft Entra ID. Pour plus d’informations, voir l’authentification Microsoft Entra ID et Protéger les informations de connexion.

Lire et modifier des valeurs

Le constructeur fonctionne aussi comme un dictionnaire. L’indexeur, ContainsKey, Remove et TryGetValue acceptent un mot-clé ou l’un de ses synonymes.

using Microsoft.Data.SqlClient;

var b = new SqlConnectionStringBuilder("Server=sql.example.com;Database=Orders;uid=app_user;pwd=x");
b["server"] = "tcp:sql2.example.com,1433";
Console.WriteLine($"DataSource: {b.DataSource}");
Console.WriteLine($"ContainsKey(\"uid\"): {b.ContainsKey("uid")}");
Console.WriteLine($"Remove(\"pwd\"): {b.Remove("pwd")}");
Console.WriteLine($"TryGetValue: {b.TryGetValue("Database", out object? db)} {db}");
Console.WriteLine($"ShouldSerialize(\"Pooling\"): {b.ShouldSerialize("Pooling")}");
Console.WriteLine(b.ConnectionString);
try { b["Bogus"] = 1; } catch (Exception e) { Console.WriteLine($"{e.GetType().Name}: {e.Message}"); }
try { b.ConnectTimeout = -1; } catch (Exception e) { Console.WriteLine($"{e.GetType().Name}: {e.Message}"); }

La sortie est la suivante :

DataSource: tcp:sql2.example.com,1433
ContainsKey("uid"): True
Remove("pwd"): True
TryGetValue: True Orders
ShouldSerialize("Pooling"): False
Data Source=tcp:sql2.example.com,1433;Initial Catalog=Orders;User ID=app_user
ArgumentException: Keyword not supported: 'Bogus'.
ArgumentException: Invalid value for key 'Connect Timeout'.

ShouldSerialize renvoie false pour un mot-clé qui n’est pas dans la chaîne. ContainsKey retourne true pour chaque mot-clé pris en charge, que la chaîne le définisse ou non. La ConnectionString propriété ne contient que les mots-clés que vous avez définis.

Comprendre ce que le générateur ne vérifie pas

Le builder rejette des mots-clés inconnus et de nombreuses valeurs invalides, mais il ne vérifie pas toutes les valeurs. Par exemple, il accepte tout texte pour Type System Version et Transaction Binding, et ne vérifie pas la longueur des chaînes. Il ne vérifie pas non plus les combinaisons de mots-clés, donc il accepte Authentication=Active Directory Default avec Integrated Security=true. SqlConnection rejette cette combinaison avec ArgumentException lorsque vous lui transmettez la chaîne. Pour la liste complète des règles, voir Règles de validation.

Le constructeur écrit les valeurs Authentication sans espaces, telles que Authentication=ActiveDirectoryDefault. Le pilote accepte les deux formes.

Le générateur ne protège pas non plus un mot de passe une fois qu’il se trouve dans la mémoire du processus et ne détermine pas si un paramètre de serveur, d’identité ou de certificat est sûr.

Conservez une chaîne de connexion canonique

Chaque chaîne de connexion distincte possède son propre pool de connexions. Deux chaînes manuscrites qui ne diffèrent que par la séquence des mots-clés ou par les synonymes créent deux groupes. Le constructeur écrit les mots-clés canoniques dans un ordre cohérent, donc Server=a;Database=d et Database=d;Server=a deviennent tous deux Data Source=a;Initial Catalog=d. Construisez la chaîne de connexion une fois au démarrage et réutilisez-labuilder.ConnectionString. N’ajoutez pas de valeurs par requête, comme les noms d’utilisateur ou les identifiants de requête. Pour plus de conseils sur le pooling, voir Utiliser le pooling de connexions.