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.JsonstattNewtonsoft.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.Devices1.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-preview007verfügbar. Der v2-Code liegt bereits immain-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 usingist die bevorzugte Syntax. Alternativ kannst duawait client.DisposeAsync()manuell aufrufen.OpenAsync()muss in v2 explizit aufgerufen werden, sonst gibt es eineInvalidOperationException.
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
IotHubClientOptionskonfiguriert (viaIotHubClientMqttSettingsoderIotHubClientAmqpSettings)
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 nachDisposeAsync()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
IDisposable→IAsyncDisposable(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.IsTransientum zu prüfen, ob ein Retry sinnvoll ist, undex.ErrorCodefü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. DeviceClient → IotHubDeviceClient, SendEventAsync → SendTelemetryAsync, Message → TelemetryMessage - 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.