Seit Microsoft Dotnet Core auch unter Linux zur Verfügung stellt, ist es ein leichtest Applikationen mit C# auch auf dieser Plattform laufen zu lassen. Möchte man eine bestehende Applikation allerdings portieren stösst man auf verschiedene Hindernisse, welche nicht so offensichtlich sind.
Viele C#-Applikationen verwenden eine CRUD-Architektur, sie verwenden Applikationslogik und greifen auf eine SQL-Server Datenbank zu. Innerhalb einer Windows-Domäne kommt dabei häufig Integrated Security zum Einsatz. Dies hat den Vorteil, dass in der Applikation keine Passwörter für den Zugriff auf Ressourcen aller Art hinterlegt werden müssen.
Dasselbe Konzept klappt auch unter Linux und damit auch innerhalb von Docker-Container, wenn man es richtig anstellt. Das folgende Beispiel zeigt, wie man in einer C#-Consolen Applikation auf eine SQL-Server Datenbank zugreift.
Rahmenbedingungen
Auf der Linux-Kiste wird ein Dotnet Core SDK benötigt um das Projekt erstellen, builden und laufen lassen zu können. Letzteres benötigt kein SDK, man kann eine Dotnet-Core Applikation auch standalone unter Linux laufen lassen.
Dotnet-Projekt erstellen
dotnet new console -lang "C#" -n SqlClientTest
cd SqlClientTest
Sicherstellen, dass im csproj-File die LangVersion-Einstellung vorhanden ist:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Data.SqlClient" Version="4.6.0" />
</ItemGroup>
</Project>
Beispielprogramm
using System;
using System.Threading.Tasks;
using System.Data.SqlClient;
class Program
{
private const string TestConnectionString =
"data source=db.beispiel.local;Initial Catalog=Test;MultipleActiveResultSets=True;App=SqlClientTest;Integrated Security=true";
private const string ProdConnectionString =
"data source=db.beispiel.local;Initial Catalog=Test;MultipleActiveResultSets=True;App=SqlClientTest;Integrated Security=true";
static async Task Main(string[] args)
{
var useProd = args != null && args.Length > 0 && args[0].Equals("prd", StringComparison.InvariantCultureIgnoreCase);
var connectionString = useProd ? ProdConnectionString : TestConnectionString;
try {
Console.WriteLine("Query SQL-Server Edition");
await RunQuery(connectionString);
} catch(Exception ex) {
Console.Error.WriteLine(ex.Message);
}
}
static async Task RunQuery(string connectionString)
{
Console.WriteLine(connectionString);
using (var db = new SqlConnection(connectionString)) {
await db.OpenAsync();
var sqlQuery = "select serverproperty('MachineName') as MachineName, serverproperty('Edition') as Edition, serverproperty('ProductVersion') as ProductVersion";
var cmd = new SqlCommand(sqlQuery, db);
var rdr = await cmd.ExecuteReaderAsync();
while (await rdr.ReadAsync()) {
Console.WriteLine($"MachineName={rdr[0]}, Edition={rdr[1]}, Version={rdr[2]}");
}
}
}
}
Nuget-Package System.Data.SqlClient hinzufügen
dotnet add package System.Data.SqlClient -s http://packages.argus.local/nuget/Argus/
Projekt kompilieren
dotnet build
Projekt laufen lassen
user@testhost:~/thomy/SqlClientTest$ dotnet run
Query SQL-Server Edition
data source=db.beispiel.local;Initial Catalog=Test;MultipleActiveResultSets=True;App=SqlClientTest;Integrated Security=true
MachineName=dbserver, Edition=Standard Edition (64-bit), Version=13.0.4466.4
a
Preisfrage: Was fehlt, damit genau das läuft? Ohne weiteres Zutun wird da eher eine Fehlermeldung kommen wie:
Cannot authenticate using Kerberos. Ensure Kerberos has been initialized on the client with 'kinit' and a Service Principal Name has been registered for the SQL Server to allow Kerberos authentication.
Die Fehlermeldung liefert auch schon die Antwort: Kerberos installieren und mit kinit ein Ticket vom KDC abholen.
Wenn nicht bereits vorhanden, stellt man sicher, dass das Package krb5-user auf dem System installiert ist.
apt install krb5-user
Bei der Installation wird eine Beispiel-Konfiguration in /etc/krb5.conf angelegt, welche man aber besser noch etwas anpasst. Ich verwende folgende Konfiguration:
# /etc/krb5.conf -- Kerberos V5 general configuration.
#
[appdefaults]
default_lifetime = 25hrs
krb4_convert = false
krb4_convert_524 = false
[libdefaults]
default_realm = BEISPIEL.LOCAL
ticket_lifetime = 25h
renew_lifetime = 7d
forwardable = true
noaddresses = true
allow_weak_crypto = true
rdns = false
[realms]
BEISPIEL.LOCAL = {
kdc = ads1.beispiel.local
kdc = ads2.beispiel.local
default_domain = beispiel.local
}
[domain_realm]
argus.local = ARGUS.LOCAL
[logging]
kdc = SYSLOG:NOTICE
admin_server = SYSLOG:NOTICE
default = SYSLOG:NOTICE
Mit kinit kann man sich nun ein Ticket vom KDC abholen, danach funktioniert auch die SQL-Server-Connection
user@testhost:~/thomy/SqlClientTest$ kinit vorname.nachname
Password for vorname.nachname@BEISPIEL.LOCAL: