Im ersten Teil der Blogreihe haben wir euch den Client und dessen allgemeine Funktionsweise vorgestellt. Heute wagen wir den Schritt in die Praxis und ich zeige euch, wie ihr diesen in eure Projekte integriert und verwendet – am Beispiel einer kleinen Konsolenanwendung.
Dynamics365 xRM Web API-Client (Teil 1): Vorstellung und Einführung
Dynamics365 xRM Web API-Client (Teil 2): Arbeiten mit dem Client
Dynamics365 xRM Web API-Client (Teil 3): Tipps, Tricks & Work in Progress
Installation
Die Integration des Clients in eure Projekte gestaltet sich denkbar einfach – denn wir haben diesen für euch gepackt und als NuGet
veröffentlicht. Zum Zeitpunkt der Verfassung dieses Artikels liegt die Softwarebibliothek in der Version 0.2.0 vor und kann eurem Projekt oder eurer Solution in Visual Studio wie gewohnt über den NuGet-Paketmanager hinzugefügt werden.
Verwendung
XRM Models
In Dataverse liegen einige bestimmte Tabellen immer vor – zu diesen Kerntabellen zählen beispielsweise Firma oder Kontakt. Je nach Unternehmensbedürfnis können diese zusätzlich angepasst oder erweitert werden, etwa um neue Spalten oder Beziehungen. Weiter können Unternehmen auch eigene Tabellen in Dataverse erstellen.
Um mit diesen unternehmensspezifischen Daten arbeiten zu können, muss der xRM Client mit den entsprechenden Tabellen und Spalten zunächst bekannt gemacht werden. Dies geschieht über Models (ähnlich wie bei Entity Framework Core), die in Form von Klassen erbracht werden und einer bestimmten Form folgen müssen.
Beim Abrufen von Reihen aus Dataverse mit dem Client werden diese aus der Web API-Response in Objekte der entsprechenden Model-Klassen deserialisiert. Beim Schreiben von Reihen in Dataverse werden die entsprechenden Objekte der Model-Klassen serialisiert und in einem HTTP-Request an die Web API übertragen.
Beim Schreiben der Model-Klassen gilt es grundsätzlich folgendes zu beachten:
Jede Model-Klasse muss mit dem DataverseTable-Attribut als solche gekennzeichnet werden
Über das DataverseTable-Attribut wird dem Client der (Plural-)Name der Tabelle in Dataverse mitgeteilt. Dieser wird für jeden Request gegen die Web API benötigt und kann vom Klassennamen abweichen.
Jede Model-Klasse muss eine Guid-Eigenschaft implementieren, die mit dem DataverseRowId-Attribut gekennzeichnet ist
Jede Reihe in Dataverse besitzt eine einzigartige ID, die dem Client für Einzeloperationen (z.B. Updates) bekannt sein muss. Über das Attribut wird dem Client kenntlich gemacht, bei welcher Eigenschaft der Model-Klasse es sich um diesen eindeutigen Bezeichner handelt.
Die Spalten einer Tabelle werden grundsätzlich über den Schemanamen auf die Eigenschaften der Model-Klassen und vice versa abgebildet
Der Client verwendet für die Serialisierung und Deserialisierung der Reihen zwischen eurer Anwendung und der Web API die Klassen aus dem System.Text.Json
Namespace. Sollten die Namen der Eigenschaften der Model-Klasse also vom Schemanamen der Spalten der entsprechenden Tabelle in der JSON abweichen, muss dies über das JsonPropertyName
-Attribut kenntlich gemacht werden.
Da sich dieses Vorgehen zwar als vollkommen gangbar, wie wir in Teil 3 allerdings noch sehen werden für kompliziertere Szenarien etwas umständlich erwiesen hat, darf in zukünftigen Versionen an dieser Stelle eine Verbesserung erwartet werden (wahrscheinlich in Form von Custom-Attributen, hierzu muss ich mir noch eine elegante Lösung einfallen lassen).
Machen wir ein einfaches Beispiel hierzu.
Für unsere Konsolenanwendung soll eine Model-Klasse für den Umgang mit Aufträgen aus dem Sales-Modul implementiert werden. Die Tabelle wurde um ein Feld für den geschätzten Aufwand des Auftrags bis zur Erfüllung in Stunden (new_estimatedeffort_number, Ganze Zahl) erweitert.
Ein passendes Model hierzu könnte daher wie folgt aufgebaut sein:
[DataverseTable("salesorder", "salesorders")]
class SalesOrder
{
[DataverseRowId]
[JsonPropertyName("salesorderid")]
public Guid Id { get; set; }
[JsonPropertyName("new_estimatedeffort_number")]
public int EstimatedEffort { get; set; }
}
Alle weiteren Spalten der Tabelle, die hier nicht im Model abgebildet sind, werden standardmäßig einfach ignoriert. Das Verhalten des Clients bei der Serialisierung und Deserialisierung ist über JsonSerializerOptions
anpassbar.
Initialisierung
Um eine einsatzbereite Instanz des xRM Client in eurer Anwendung zu erhalten, wird die ConnectAsync-Fabrikmethode verwendet. Sie kümmert sich um die Authentifizierung der Anwendung gegenüber Azure mittels OAuth 2.0 Client Credential Flow und baut anschließend eine autorisierte Verbindung zu Dynamics365 auf. Dazu müssen folgende Verbindungsinformationen über ein Objekt der DynamicsXrmConnectionParams-Klasse bereitgestellt werden:
- Die (Service-Root-)URL der Instanz
- Die Mandanten-ID, in der sich die Instanz befindet
- Application-ID und ClientSecret der App-Registrierung, über welche sich die Anwendung gegen Azure authentifizieren und Dynamics365 autorisieren kann
Im Rahmen unserer Konsolenanwendung hard-coden wir die Verbindungsinformationen statisch in den Code. Das ist Bad Practice, soll uns an dieser Stelle aber zu Demonstrationszwecken genügen. Im Kontext einer ASP.NET Core Anwendung können die Verbindungsinformationen beispielsweise über die AppSettings eingespielt und der Client dann mittels Dependency Injection erstellt und innerhalb der Anwendung verfügbar gemacht werden.
var client = await DynamicsXrmWebApiClient.ConnectAsync(
new DynamicsXrmConnectionParams
{
ServiceRootUri = "<service-root-uri>",
TenantId = "<tenant-id>",
ClientId = "<client-id>",
ClientSecret = "<client-secret>"
});
CRUD-Operationen
Habt ihr einen authentifizierten und autorisierten xRM Client, steht euch über diesen eine Auswahl generischer Methoden auf Basis eurer Model-Klassen für die Kommunikation mit der Web API zur Verfügung. Derzeit (v0.2.0) werden folgende Operationen unterstützt:
- Erstellen (CreateAsync, UpsertAsync)
- Abrufen (RetrieveAsync, RetrieveMultipleAsync)
- Aktualisieren (UpdateAsync, UpsertAsync)
- Löschen (DeleteAsync)
- Massenverarbeitung (ExecuteBatchAsync) (aktuell nur teilweise unterstützt)
- Für das Abrufen von Reihen unterstützen die Retrieve-Methoden neben Paging auch die Verwendung aller OData-Queries, die die Web API unterstützt
. Letztere können optional als String über einen Parameter mitgegeben werden.
Um an das Beispiel von eben anzuknüpfen – ein Abruf aller Aufträge aus dem System, bei denen der geschätzte Aufwand mehr als 8 Stunden beträgt, lässt sich wie folgt umsetzen:
string whereEffortGreater8Hours = "?$filter=new_estimatedeffort_number gt 8";
List<SalesOrder> orders = await client.RetrieveMultipleAsync<SalesOrder>(whereEffortGreater8Hours);
Ausblick
Das soll uns für heute und an dieser Stelle erstmal genügen – die komplette Beispielanwendung aus diesem Artikel habe ich euch nochmal zusammenfassend auf GitHub
bereitgestellt.
Im nächsten und letzten Teil dieser Blogreihe werdet ihr erfahren, wie auch komplexere Szenarien mit dem Client umzusetzen sind. Dazu zählen beispielweise der Umgang mit Beziehungen zwischen Tabellen (Lookup-Spalten) oder das Absetzen von Batch Requests. Außerdem berichten wir euch von ein paar Fallstricken und Besonderheiten, die es bei der Programmierung gegen die Dynamics Web API mit dem xRM Client zu beachten gilt und wie wir damit umgegangen sind.