Azure IoT SDK v2 für .NET - Migration Guide und Breaking Changes

Azure IoT SDK v2 für .NET - Migration Guide und Breaking Changes

Einleitung

Wenn du Azure IoT-Anwendungen mit .NET entwickelst, hast du bisher die Pakete Microsoft.Azure.Devices.Client oder Microsoft.Azure.Devices verwendet. Microsoft arbeitet an einem grundlegend überarbeiteten Azure IoT SDK v2 - und damit kommen erhebliche Breaking Changes auf dich zu.

Der v2-Branch wurde kürzlich in den main-Branch des GitHub-Repositorys gemerged. Auf NuGet sind bisher sieben Preview-Releases erschienen (zuletzt 2.0.0-preview007 im August 2023), das stable Release 2.0.0 steht noch aus. Im offiziellen Migration Guide sind die API-Änderungen dokumentiert.

⚠️ Wichtiger Hinweis: Der offizielle Migration Guide und der tatsächliche Quellcode im main-Branch weichen an einigen Stellen voneinander ab. Die Code-Beispiele in diesem Blogpost basieren auf dem kompilierbaren Quellcode (Stand März 2026), nicht auf dem Migration Guide. Betroffene Stellen sind mit einem 🔄 gekennzeichnet. Da sich das SDK bis zum stable Release noch ändern kann, prüfe im Zweifel immer den aktuellen Stand im GitHub-Repository.

In diesem Blogpost gehe ich die wichtigsten Änderungen durch, zeige dir den Migrationspfad und erkläre, was du jetzt schon vorbereiten kannst.

Was du in diesem Blogpost findest

  • Warum ein neues SDK?
  • Was ändert sich an den NuGet-Paketen?
  • Breaking Changes im Detail
  • Neue Features in v2
  • Schritt-für-Schritt Migration
  • Empfehlung: Wann migrieren?

Warum ein neues SDK?

Das bisherige Azure IoT SDK für .NET ist über die Jahre gewachsen - und damit auch die technische Schuld. Das v2 SDK wurde von Grund auf überarbeitet, um:

  • Modernere .NET-Patterns zu nutzen (IAsyncDisposable, System.Text.Json, durchgehend async/await)
  • .NET 10 als Mindestversion zu unterstützen (LTS)
  • Weniger NuGet-Pakete zu verwalten - Transport- und Shared-Pakete werden konsolidiert
  • Konsistentere APIs über alle Clients hinweg bereitzustellen
  • Bessere Performance durch System.Text.Json statt Newtonsoft.Json
  • Mocking-Support für Unit Tests durch echte Konstruktoren statt statischer Factory-Methoden

💡 Hinweis: Das v1 SDK wird weiterhin unterstützt und erhält Sicherheitsupdates. Es gibt keinen harten Stichtag für die Migration. Die letzte v1-Stable-Release war am 11. Dezember 2025 (Microsoft.Azure.Devices 1.41.0). Aber für neue Projekte ist v2 die empfohlene Wahl, sobald es stable verfügbar ist.


Was ändert sich an den NuGet-Paketen?

Wichtig: Die Paketnamen bleiben gleich - es ist ein Major-Version-Bump, kein Rename. Aber mehrere Pakete wurden konsolidiert oder entfernt:

Pakete, die bleiben (Version 2.0.0)

Paket Beschreibung
Microsoft.Azure.Devices.Client Device-zu-Cloud Kommunikation
Microsoft.Azure.Devices Service-seitige Operationen
Microsoft.Azure.Devices.Provisioning.Client Device Provisioning Service (Client)
Microsoft.Azure.Devices.Provisioning.Service Device Provisioning Service (Service)

Pakete, die entfernt werden

Paket Was passiert damit?
Microsoft.Azure.Devices.Shared Entfällt - Typen wie Twin wandern in die jeweiligen Client-Pakete
Microsoft.Azure.Devices.Provisioning.Transport.Amqp In Provisioning.Client integriert
Microsoft.Azure.Devices.Provisioning.Transport.Http In Provisioning.Client integriert
Microsoft.Azure.Devices.Provisioning.Transport.Mqtt In Provisioning.Client integriert
Microsoft.Azure.Devices.Provisioning.Security.Tpm Entfernt (TPM-Support deprecated)

Installation (sobald v2 stable erscheint)

# Device Client
dotnet add package Microsoft.Azure.Devices.Client --version 2.0.0

# Service Client
dotnet add package Microsoft.Azure.Devices --version 2.0.0

# DPS Client
dotnet add package Microsoft.Azure.Devices.Provisioning.Client --version 2.0.0

⚠️ Stand März 2026: Auf NuGet ist aktuell nur 2.0.0-preview007 verfügbar. Der v2-Code liegt bereits im main-Branch auf GitHub. Das stable Release wird erwartet, ist aber noch nicht erschienen. Du kannst den Preview nutzen, um dich vorzubereiten.


Breaking Changes im Detail

1. Client-Erstellung und Lebenszyklus

Die statischen Factory-Methoden sind weg. Stattdessen gibt es echte Konstruktoren, die auch Mocking ermöglichen. Und der Client implementiert jetzt IAsyncDisposable, nicht IDisposable:

// ALT (v1)
var deviceClient = DeviceClient.CreateFromConnectionString(connectionString);
await deviceClient.SendEventAsync(message);
await deviceClient.CloseAsync();
deviceClient.Dispose();

// NEU (v2)
await using var deviceClient = new IotHubDeviceClient(connectionString);
await deviceClient.OpenAsync();
await deviceClient.SendTelemetryAsync(telemetryMessage);
// CloseAsync + Dispose passiert automatisch am Ende des using-Blocks

💡 Tipp: await using ist die bevorzugte Syntax. Alternativ kannst du await client.DisposeAsync() manuell aufrufen. OpenAsync() muss in v2 explizit aufgerufen werden, sonst gibt es eine InvalidOperationException.

2. Telemetrie senden

Die Message-Klasse wurde in spezifischere Typen aufgeteilt:

// ALT (v1)
using Microsoft.Azure.Devices.Client;

var message = new Message(Encoding.UTF8.GetBytes(jsonPayload));
message.Properties.Add("temperatureAlert", "true");
await deviceClient.SendEventAsync(message);

// NEU (v2)
using Microsoft.Azure.Devices.Client;

var telemetry = new TelemetryMessage(Encoding.UTF8.GetBytes(jsonPayload));
telemetry.Properties.Add("temperatureAlert", "true");
await deviceClient.SendTelemetryAsync(telemetry);
v1 v2 Hinweis
Message (für alles) TelemetryMessage Für Device-to-Cloud
Message (empfangen) IncomingMessage Für Cloud-to-Device
SendEventAsync() SendTelemetryAsync() Klarerer Name
Message.GetBytes() IncomingMessage.Payload Property statt Methode

3. Cloud-to-Device Nachrichten empfangen

🔄 Abweichung vom Migration Guide: Der Callback nimmt laut Quellcode nur ein Argument (IncomingMessage) entgegen - kein CancellationToken. Payload wird über die Property Payload abgerufen, nicht über eine Methode.

Statt Polling mit ReceiveAsync gibt es jetzt einen Callback:

// ALT (v1)
await deviceClient.SetReceiveMessageHandlerAsync(
    async (message, context) =>
    {
        Console.WriteLine($"Nachricht empfangen: {Encoding.UTF8.GetString(message.GetBytes())}");
        return MessageResponse.Completed;
    },
    null);

// NEU (v2)
await deviceClient.SetIncomingMessageCallbackAsync(
    (message) =>
    {
        Console.WriteLine($"Nachricht: {Encoding.UTF8.GetString(message.Payload)}");
        return Task.FromResult(MessageAcknowledgement.Complete);
    });
v1 v2
SetReceiveMessageHandlerAsync() SetIncomingMessageCallbackAsync()
MessageResponse.Completed MessageAcknowledgement.Complete

4. Direct Methods

🔄 Abweichung vom Migration Guide: Auch hier nimmt der Callback laut Quellcode nur ein Argument (DirectMethodRequest) entgegen - kein CancellationToken.

Individuelle Method-Handler sind deprecated. Es gibt nur noch einen globalen Callback:

// ALT (v1) - Handler pro Methode
await deviceClient.SetMethodHandlerAsync("reboot",
    async (request, context) =>
    {
        Console.WriteLine($"Reboot angefordert: {request.DataAsJson}");
        return new MethodResponse(200);
    }, null);

// NEU (v2) - Globaler Callback, Filterung im Handler
await deviceClient.SetDirectMethodCallbackAsync(
    (request) =>
    {
        if (request.MethodName == "reboot")
        {
            Console.WriteLine($"Reboot angefordert");
            return Task.FromResult(new DirectMethodResponse(200));
        }
        return Task.FromResult(new DirectMethodResponse(404));
    });
v1 v2
SetMethodHandlerAsync("name", ...) Deprecated
SetMethodDefaultHandlerAsync(...) SetDirectMethodCallbackAsync(...)
MethodRequest DirectMethodRequest
MethodResponse DirectMethodResponse

5. Device Twins

🔄 Abweichung vom Migration Guide: Der Guide erwähnt separate Typen DesiredProperties und ReportedProperties. Im Quellcode existiert stattdessen ein einheitlicher Typ PropertyCollection für beides.

// ALT (v1)
var twin = await deviceClient.GetTwinAsync();
Console.WriteLine($"Desired: {twin.Properties.Desired}");

var reported = new TwinCollection();
reported["firmware"] = "2.0.0";
await deviceClient.UpdateReportedPropertiesAsync(reported);

// NEU (v2)
var properties = await deviceClient.GetTwinPropertiesAsync();
Console.WriteLine($"Desired: {properties.Desired}");

var reported = new PropertyCollection
{
    { "firmware", "2.0.0" }
};
await deviceClient.UpdateReportedPropertiesAsync(reported);
v1 v2 Hinweis
GetTwinAsync() GetTwinPropertiesAsync() Device bekommt nur Properties, nicht den vollen Twin
Twin TwinProperties Enthält .Desired und .Reported (beides PropertyCollection)
TwinCollection (für alles) PropertyCollection Einheitlicher Typ für Desired und Reported

6. Service Client - Konsolidierung

🔄 Abweichung vom Migration Guide: IotHubServiceClient implementiert laut Quellcode IDisposable, nicht IAsyncDisposable. Daher using var statt await using var.

Das ist der größte strukturelle Umbau. Statt RegistryManager, ServiceClient und JobClient separat gibt es einen Client:

// ALT (v1) - Drei separate Clients
var registryManager = RegistryManager.CreateFromConnectionString(connectionString);
var serviceClient = ServiceClient.CreateFromConnectionString(connectionString);
var jobClient = JobClient.CreateFromConnectionString(connectionString);

var device = await registryManager.GetDeviceAsync("myDevice");
await serviceClient.SendAsync("myDevice", new Message(payload));

// NEU (v2) - Ein Client mit Subclients
using var serviceClient = new IotHubServiceClient(connectionString);

var device = await serviceClient.Devices.GetAsync("myDevice");
await serviceClient.Messages.SendAsync("myDevice", new OutgoingMessage(payload));
var twin = await serviceClient.Twins.GetAsync("myDevice");

Die Subclients im Überblick:

Operation v1 Client v2 Subclient
Device CRUD RegistryManager IotHubServiceClient.Devices
Twin Operations RegistryManager IotHubServiceClient.Twins
C2D Messages ServiceClient IotHubServiceClient.Messages
Direct Methods ServiceClient IotHubServiceClient.DirectMethods
Scheduled Jobs JobClient IotHubServiceClient.ScheduledJobs
Configurations RegistryManager IotHubServiceClient.Configurations
Digital Twins DigitalTwinClient IotHubServiceClient.DigitalTwins
Queries RegistryManager IotHubServiceClient.Query

Neue Features in v2

System.Text.Json statt Newtonsoft.Json

Das v2 SDK nutzt durchgehend System.Text.Json. Das bringt bessere Performance und weniger Dependencies.

Verbesserte Connection-Diagnostik

// Connection-Status ist jetzt als Property abrufbar
var status = deviceClient.ConnectionStatusInfo;
Console.WriteLine($"Status: {status.Status}");

// Callback für Änderungen - jetzt mit RecommendedAction
deviceClient.ConnectionStatusChangeCallback = (info) =>
{
    Console.WriteLine($"Status: {info.Status}, Reason: {info.ChangeReason}");
    Console.WriteLine($"Empfohlene Aktion: {info.RecommendedAction}");
};

💡 Praxis-Tipp: Die neue RecommendedAction-Property ist Gold wert. Vorher musstest du aus der Kombination von Status und Reason selbst ableiten, was zu tun ist. Jetzt sagt dir das SDK direkt, ob du Retry, Reconnect oder Dispose machen sollst.

Verbesserte Retry-Logik

🔄 Abweichung vom Migration Guide: Die Klasse heißt im Quellcode IotHubClientExponentialBackoffRetryPolicy (kleines 'o' in Backoff) und der Konstruktor erwartet einen zusätzlichen Parameter maxWait.

var options = new IotHubClientOptions
{
    RetryPolicy = new IotHubClientExponentialBackoffRetryPolicy(
        maxRetries: 10,
        maxWait: TimeSpan.FromSeconds(30),
        useJitter: true),
};

await using var client = new IotHubDeviceClient(connectionString, options);

Neue Retry-Policies: IotHubClientExponentialBackoffRetryPolicy, IotHubClientIncrementalDelayRetryPolicy, IotHubClientFixedDelayRetryPolicy.

Transport-Änderungen

  • Default-Transport wechselt von AMQP auf MQTT TCP
  • HTTP als Transport-Option wurde komplett entfernt
  • Transport wird über IotHubClientOptions konfiguriert (via IotHubClientMqttSettings oder IotHubClientAmqpSettings)

Client kann nach Close erneut geöffnet werden

In v1 musstest du nach einem CloseAsync() den Client komplett neu erstellen. In v2 kannst du einfach OpenAsync() erneut aufrufen:

await using var client = new IotHubDeviceClient(connectionString);
await client.OpenAsync();

// ... arbeiten ...
await client.CloseAsync();

// Erneut öffnen ohne Neuinitialisierung
await client.OpenAsync();

⚠️ Beachte: Subscriptions (C2D, Direct Methods, Twin) werden beim Close nicht beibehalten. Nach dem erneuten OpenAsync() musst du sie neu registrieren. Und nach DisposeAsync() kann nicht mehr geöffnet werden.


Schritt-für-Schritt Migration

1. Voraussetzungen prüfen

✔ .NET 10 SDK installiert (sobald stable verfügbar)
✔ Visual Studio 2025 oder VS Code mit C# Extension
✔ Alle Tests für den bestehenden Code laufen

2. NuGet-Pakete aktualisieren

# Shared-Paket entfernen (Typen sind jetzt in den Hauptpaketen)
dotnet remove package Microsoft.Azure.Devices.Shared

# Transport-Pakete entfernen (jetzt in Provisioning.Client integriert)
dotnet remove package Microsoft.Azure.Devices.Provisioning.Transport.Amqp
dotnet remove package Microsoft.Azure.Devices.Provisioning.Transport.Http
dotnet remove package Microsoft.Azure.Devices.Provisioning.Transport.Mqtt

# Hauptpakete auf v2 aktualisieren
dotnet add package Microsoft.Azure.Devices.Client --version 2.0.0
dotnet add package Microsoft.Azure.Devices --version 2.0.0

3. Deprecated Sub-Namespaces bereinigen

// Diese Namespaces gibt es nicht mehr - entfernen:
// using Microsoft.Azure.Devices.Shared;
// using Microsoft.Azure.Devices.Common.Exceptions;
// using Microsoft.Azure.Devices.Client.Exceptions;
// using Microsoft.Azure.Devices.Client.Transport.Mqtt;

// Die Hauptnamespaces bleiben:
using Microsoft.Azure.Devices.Client;    // Device Client
using Microsoft.Azure.Devices;           // Service Client

4. Client-Initialisierung anpassen

  • Factory-Methoden → Konstruktoren
  • IDisposableIAsyncDisposable (await using)
  • Explizit OpenAsync() aufrufen

5. API-Aufrufe migrieren

Folge den Mapping-Tabellen oben für Telemetrie, Messages, Twins und Direct Methods. Die wichtigsten Renames auf einen Blick:

v1 v2
DeviceClient IotHubDeviceClient
ModuleClient IotHubModuleClient
Message TelemetryMessage / IncomingMessage / OutgoingMessage
SendEventAsync SendTelemetryAsync
SetReceiveMessageHandlerAsync SetIncomingMessageCallbackAsync
SetMethodDefaultHandlerAsync SetDirectMethodCallbackAsync
GetTwinAsync GetTwinPropertiesAsync
TwinCollection PropertyCollection
MethodRequest / MethodResponse DirectMethodRequest / DirectMethodResponse
ExponentialBackOff IotHubClientExponentialBackoffRetryPolicy

6. Exception-Handling anpassen

Das v2 SDK konsolidiert Exceptions:

// ALT (v1) - Viele verschiedene Exception-Typen
try { /* ... */ }
catch (DeviceNotFoundException ex) { /* ... */ }
catch (IotHubThrottledException ex) { /* ... */ }
catch (IotHubCommunicationException ex) { /* ... */ }

// NEU (v2) - Konsolidiert
try { /* ... */ }
catch (IotHubClientException ex) when (ex.IsTransient)
{
    // Transient - Retry
    Console.WriteLine($"Transient error: {ex.ErrorCode}");
}
catch (IotHubClientException ex)
{
    // Permanent - Handle
    Console.WriteLine($"Error: {ex.ErrorCode}, TrackingId: {ex.TrackingId}");
}

📌 Tipp: Nutze ex.IsTransient um zu prüfen, ob ein Retry sinnvoll ist, und ex.ErrorCode für strukturierte Fehlerbehandlung. Die vielen Exception-Subklassen aus v1 gibt es nicht mehr.

7. Tests ausführen

Stelle sicher, dass alle bestehenden Tests weiterhin bestehen. Prüfe insbesondere:

  • Telemetrie wird korrekt gesendet
  • Cloud-to-Device Nachrichten werden empfangen
  • Device Twin Updates funktionieren
  • Reconnection-Logik greift (beachte: Default ist jetzt MQTT statt AMQP!)

Empfehlung: Wann migrieren?

Szenario Empfehlung
Neues Projekt Auf v2 stable warten, dann direkt v2 verwenden
Bestehende Anwendung, aktiv weiterentwickelt Jetzt den Migration Guide studieren, Migration planen, aber auf v2 stable warten
Bestehende Anwendung, kaum Änderungen v1 weiterverwenden, beobachten
Umstieg auf Azure IoT Operations geplant Für IoT Operations die separaten IoT Operations SDKs evaluieren

💡 Mein Rat: Lies den offiziellen Migration Guide auf GitHub. Er enthält für jeden Client eine vollständige API-Mapping-Tabelle. Wenn du die Preview testen willst: dotnet add package Microsoft.Azure.Devices.Client --version 2.0.0-preview007. Aber für Production-Code: Warte auf das stable Release.


Fazit

Das Azure IoT SDK v2 für .NET ist eine überfällige Modernisierung. IAsyncDisposable statt IDisposable, System.Text.Json statt Newtonsoft, echte Konstruktoren statt Factory-Methoden, konsolidierte Exception-Hierarchie - alles Dinge, die längst fällig waren.

Der größte Aufwand bei der Migration liegt in den umbenannten Typen und Methoden. DeviceClientIotHubDeviceClient, SendEventAsyncSendTelemetryAsync, MessageTelemetryMessage - das zieht sich durch den gesamten Code. Aber die Konzepte bleiben gleich: Telemetrie senden, C2D empfangen, Twins lesen und schreiben, Direct Methods implementieren.

Auf der Service-Seite ist der Umbau radikaler: Drei separate Clients (RegistryManager, ServiceClient, JobClient) werden zu einem IotHubServiceClient mit Subclients. Das ist langfristig besser, erfordert aber mehr Refactoring.

Fang jetzt schon an, dich vorzubereiten - und migriere, sobald v2 stable auf NuGet erscheint.


Weiterlesen

Praxisprojekt: End-to-End Deployment mit Azure Arc-enabled Kubernetes: GitOps, Policy und Monitoring

Praxisprojekt: End-to-End Deployment mit Azure Arc-enabled Kubernetes: GitOps, Policy und Monitoring

Einleitung Freitagabend, 22:30 Uhr. Maren, Ops-Leiterin bei einem mittelständischen Logistikdienstleister, sitzt mit dem Laptop auf der Couch. In ihrem Slack explodiert es: Der Label-Service im Lager Hamburg druckt seit einer Stunde falsche Versandlabels. DHL-Pakete bekommen DPD-Labels. Die Nachtschicht stapelt Pakete, die nicht raus können. Maren weiß, was jetzt kommt.

Von Tim Steiner