Tech Talk: "Dynamically created OData Datasource" mit Gerald Leitner von Fabasoft

Tech Talk: "Dynamically created OData Datasource" mit Gerald Leitner von Fabasoft

Ja hallo, willkommen! Vielen Dank für die Einladung. Ich freue mich, dass ich da bin und ich möchte heute über das Thema OData sprechen. Es gibt ja viele Standardbeispiele für OData und wir wollen heute versuchen die größtmögliche Flexibilität der Dynamik in OData reinzubringen. Deswegen der Titel Dynamically created OData datasource in ASP.NET 5.

Wir gehen kurz zu meiner Person. Ich bin Gerald Leitner. Ich arbeite bei der Fabasoft als Scrum Master und Dev Team Lead in der Abteilung Cloud. Das heißt wir beschäftigen uns hoch skalierbare Anwendungen, hochperformanten Anwendungen und wir machen alles, was Spaß macht. Das heißt, wir arbeiten mit Microservices, wir arbeiten mit Cloud, wir arbeiten an skalierbare Produkten. Und der Fokus ist, wir haben einerseits C++, wir haben .NET, wir haben Java Microservices, wir haben node.js Microservices. Das heißt quer durch die Bank arbeiten wir. Und ja, wir suchen natürlich immer Leute, so wie jedes Unternehmen. Meine persönlichen Main Topics sind jetzt .NET, Web, Cloud, Angular, TypeScript und allgemein alles Scrum als Entwicklungsprozess. Das sind meine Kernthemen – heute geht es zum Beispiel um .NET. Bitte, wenn irgendwelche Fragen sind, kontaktierte mich [email protected] Ich bin auf Twitter zu finden und hab ein GitHub Repo eingerichtet, wo der gesamte Source Code, den ich heute herzeige, vorhanden ist. Das heißt bitte einfach dort drauf schauen – ich probiere, dass ich die Präsentation auch dort raufstelle.

Steigen wir ein: heute geht’s um OData – das heißt ich werden einmal grundsätzlich erklären was ist es, wo sind standardmäßigen Limitierungen und dann wollen wir versuchen das ganze sehr dynamisch zu machen – das heißt wir wollen probieren, dass wir uns von dem statischen Modell wegbewegen auf extrem dynamische Anwendungen, mit der man dann quasi alles machen kann.

Steigen wir da gleich ein. Das heißt grundsätzlich wurde OData von Microsoft entwickelt. Ist ein http-basiertes Protokoll zur Abfrage von Daten. Ist recht lässig, wenn man große Mengen an Daten hat, die man über standardisierte Schnittstellen zur Verfügung stellen will und damit gewisse Tools, es gibt z.B. PowerBI, das ist ein mächtiges Berichts-Tool, irgendwelche Statistiken, Reports, Jahresberichte oder was auch immer man machen will – da ist OData perfekt geeignet. OData grundsätzlich wird jetzt von Oasis weiterentwickelt. Der aktuelle Standard ist 4.0. Das mal zum Allgemeinen. Ich will mich gar nicht zu sehr in die Folien aufhalten sondern immer wieder switchen auf den Code. Wir werden ein bisschen Live Coding machen und die Folien dienen quasi so dem Überblick.

Das heißt, machen wir unsere erste Live Coding Session.

Jetzt geht's darum wir wollen einmal OData kennenlernen. Was macht OData aus.

Dazu wechsle ich in Visual Studio. Wenn ich eine neu ASP.NET Web Anwendung kreiere, dann entsteht genau das was ich hier habe. Wir haben ein Modell, ein relativ kleines Modell – es geht hier um eine Wettervorhersage, das heißt wir haben ein WeatherForecast-modell und einen WeatherForecastController. Nichts Spannendes dabei. In dem Controller gibt es eine Get Methode – der holt sich einfach irgendwelche random-Daten die generiert werden – das heißt jedes mal werden die Daten neu generiert und im Startup.cs werden einfach diese Controller und alles eingerichtet. Also Standard Sachen zum initialisieren. Wir schauen uns einfach einmal an was dieser WeatherForecast tut – jetzt noch komplett ohne OData. Das heißt wir lassen das Ding jetzt mal so wie es generiert wird mal laufen und schauen uns das an. Falls das länger dauert, können wir schon mal das Model durchschauen. Ist schon da. Das heißt, wir rufen localhost plus Port /weatherforecast an und kommen direkt auf den WeatherForecast-Controller. Was wir dann gleich sehen sollten ist, dieser Controller liefert uns ein standard JSON Array. Ich hab da natürlich schon ein bisschen herumentwickelt. Ich habe da schon mal vorbereitet OData ausprobiert und deswegen müssen wir hier mal die OData Assembly hinzufügen und dann sollte es gleich funktionieren. Währenddessen schauen wir uns das Ganze mal an. Wir arbeiten hier mit einem statischen Modell mit einem statischen Controller – damit meine ich, dieser Controller ist fix in dem Projekt drinnen, er liefert einfach ein Modell was zur Compilezeit schon fest steht. Das heißt dieser WeatherForecast hat diese Properties drinnen und zur Laufzeit das einzige dynamische was da drinnen ist, ist dass randommäßig irgendwelche Werte eingefüllt werden. Das heißt wir haben einen fixen Controller und ein fixes Modell und im Startup.cs werden quasi mit dem AddControllers Befehl diese Controller geladen.

Jetzt probieren wir es nochmals aus ob es funktioniert. Das heißt grundsätzlich ohne OData aber irgendwo ist schon eine Referenz auf OData drinnen deswegen brauche wir jetzt dieser Package. Und wir brauchen es dann sowieso noch nachher, daher ist es ganz praktisch wenn es schon drinnen ist. Das heißt dieser WeatherForecast-Controller liefert einfach ein Array an Daten – in dem Fall von WeatherForecast und wie gesagt: es sind nur random Daten. Das ist komplett ohne OData, das heißt was wir jetzt versuchen werden ist wir machen einfach aus dem einen OData-Controller. Den ersten Schritt hat man schon gesehen, das heißt wir sind in die NuGet-Packages reingegangen und haben die Assembly Microsoft AspNetCore.OData hinzugefügt. Ich verwende hier die Version 7.x. es gibt inzwischen schon preview von 8.0 aber da ist zu bedenken, dass 8.0 ein bisschen anders funktioniert. In den Beispielen auf dem GitHub Repo habe ich das bereits berücksichtigt – das heißt es gibt eine 7.x und eine 8.0 Version. Wir konzentrieren uns jetzt mal auf 7.x weil diese ist stable und diese verwende ich jetzt mal. Es sind nur ganz wenige Schritte nötig, um OData zu aktivieren. Als erstes schauen wir in das Startup.cs und fügen bei die Services ein addOData hinzu. Und mit STRG-. lassen wir uns den Namespace hinzufügen Microsoft.AspNet.OData.Extensions. Damit haben wir mal OData grundsätzlich aktiviert. Und jetzt müssen wir angeben welche Features wollen wir von OData nützen. Dazu gehen wir in die Configure Methode, in die useEndpoint und hier aktivieren wir jetzt die Features, die wir brauchen. Das heißt zum Beispiel Select(), das heißt normale Datenabfrage. Wir fügen ein paar mehr Features hinzu: wir fügen eine Filter() und einen Count(), ein MaxTop() hinzu. Das sollte mal reichen. Das heißt hier kann man einfach grundsätzlich auswählen welche Features ich für meine OData Controller aktiviert haben will. OData grundsätzlich arbeitet mit einem Modell. Dieses Modell kann es sich nicht selbst generieren oder würde keinen Sinn machen, weil es kann ein großes Modell geben und nur ein Teil von dem Modell will ich jetzt über OData zur Verfügung stellen. Wenn wir jetzt die OData-Route registrieren, können wie ein Modell angeben. Also als erstes sagen wir einmal unter welcher Adresse soll unser OData zur Verfügung stehen. Der Standard den man da immer nimmt ist immer OData aber das kann man natürlich immer anpassen wie man will.

Und jetzt geht es um das Modell. Das heißt hier machen wir uns eine Methode GetModel – die lassen wir generieren. Und dieses GetModel liefert ein IEdmModel. Das heißt das ist das mit dem OData dann arbeitet. Um dieses zu erzeugen, generieren wir uns einen Builder mit einem ODataConventionModelBuilder. Also dieser Builder generiert uns das Modell und es gibt verschiedene Builder. Wir verwenden diesen ODataConventionModelBuilder, den ich fast richtig geschrieben habe. Wir müssen quasi registrieren, welche Entities, welchen Teil vom Model wir hinzufügen wollen. Das machen wir mit EntitySet und in dem Fall geben wir unseren WeatherForecast an. Wir sagen jetzt: OData, bitte verarbeite mir diesen WeatherForecast. Und rückgeben werden wir dann das generierte Model. Was wir beim EntitySet noch brauchen – also wir habe ihm jetzt gesagt welchen Typ wir wollen – OData verlangt immer nach einen Namen. Das heißt theoretisch können wir das alles noch irgendwie anders benennen. Jetzt macht es aber nicht viel Sinn, das heißt, wir nennen es genauso WeatherForecast. Im Modell wird der WeatherForecast auch WeatherForecast heißen. Was dann noch nötig ist, ist, dass wir natürlich den Controller ein bisschen umbauen müssen. Bis jetzt ist es ein ASB.NET-Controller und wir wollen aus diesem Controller einen OData-Controller machen. Und das Attribut was wir dazu brauchen ist EnableQuery. Das schreiben wir mal da hin und fügen den Namespace hinzu und ab dann ist es eine OData-aktivierte Methode quasi. Also wenn wir diese aufrufen, kommt die OData-Logik zum Tragen und brauchen das ganze ASP.NET nicht mehr, weil das ganze macht OData für uns. Das heißt wir löschen einfach alle anderen Attribute quasi weg und ab dem Zeitpunkt, wenn diese Methode aufgerufen wird, verarbeitet OData die Daten und bereitet sie dann auf. Das werden wir dann gleich sehen. Ein einziges Ding ist dann noch nötig: am Modell möchte OData einen Key haben, einen primary Key. Wir sagen jetzt mal das Datum, weil das ist eh eindeutig. Dazu verwenden wir einfach das Key Attribute im Componentmodel.DataAnnotations. Und jetzt probieren wir es aus. Schauen wir mal, ob’s dieses Mal funktioniert auf Anhieb.

Im besten Fall bekommen wir jetzt natürlich eine Fehlermeldung – aber in dem Fall erschreckt mich das nicht, weil das erwarten wir. Wir haben jetzt quasi OData gesagt, wir wollen unter /odata die OData Logik drinnen haben. Das heißt, wir müssen natürlich hier den Call auch anpassen, dass wir hier schreiben /odata/weatherforecast. Jawoll! Wie erwartet hat das funktioniert. Man sieht, dass OData aktiviert ist, wenn man in dem JSON Response OData Metadaten drinnen hat – das hier. Wenn man diese URL aufrufen würde – können wir eh kurz ausprobieren – dann müsste man das Modell an sich bekommen. Da steht genau das drinnen, was wir registriert haben mit dem Entity Set, wir haben den weatherforecast registriert und die Properties lest es sich automatisch aus. Dieser Name hier ist der, den ich definiert habe.

Was sehen wir sonst? Ansonsten schauen die Daten eigentlich komplett gleich aus. Was kann man tun mit OData? Tools wie PowerBI arbeiten auch mit besonderen Befehlen, es gibt da zum Beispiel $top=1, also nur das erste Element. Oder man kann machen $orderby= – schreibt man das so? Schauen wir schnell welche Properties es gibt in diesem WeatherForecast. Man kann es zum Beispiel nach dem Date sortieren, $orderby=date – schauen wir mal ob das funktioniert. Probieren wir etwas anderes – achso, $top=1 haben wir schon gemacht – wir könnten ein Filter ausprobieren? Okay, ja in diesem Falle haben wir einen Filter ausprobiert. Diesen angegebenen Filter gibt es gerade nicht. Das heißt OData verarbeitet unsere Daten und je nach dem wie wir da – filter, count. Ja genau count könnte man probieren. Naja, es funktioniert gar nicht. Nagut, dann bleiben wir bei unserem ersten, das $top=1, da können wir auch mehrere eingeben. Das heißt da können wir mehrere eingeben – auf jeden Fall wir haben gesehen, OData verwendet unsere Daten, modifiziert sie anhand von den Query-Parameter, die wir angeben und schickt das dann raus. Gut, soweit so gut.

Was wir jetzt gesehen haben, grundsätzlich, war: wir haben ein fixes Modell, wir haben einen fixen Controller. Das führt mich jetzt zu unserer Präsentation zurück. Unser Ziel ist jetzt, wir wollen das jetzt so flexibel wie möglich machen. Das heißt im besten Fall haben wir gar nichts mehr statisch drinnen, sondern wir starten eine Anwendung wo nichts drinnen ist. Kein Controller, kein Model, OData Referenzen sind schon drinnen, aber ansonsten nichts. Wir generieren uns zur Laufzeit, wenn ein Request reinkommt – also wenn man im Browser oder im PowerBI die URL aufrufen –, generieren wir unseren Controller eine Assembly, registrieren das in OData und lassen dann über OData diese Daten modifizieren. Das probieren wir aus. Das Big Picture würde dann so ausschauen: wir haben einen Client, in dem Fall im Browser oder PowerBI und wir schicken einen Request rein. Dieser Request könnte zum Beispiel folgendermaßen ausschauen. Schauen wir mal, wenn wir da haben – zur Zeit hab ich es auf localhost laufen, ist irgendeine Portnummer – https://localhost:12000/. Dann wollen wir irgendwas aufrufen, ich habe mir ein Beispiel überlegt, welches Sinn macht: eine Zooverwaltung. Das heißt wir wollen diese Zooverwaltung aufrufen und wir wollen uns in verschiedenen Sprachen Endpoints generieren lassen. Wir wollen einmal auf Deutsch alle Tiere, einmal auf Englisch alle Tiere. Im besten Fall schreiben wir dann einfach /deutsch und generieren uns dann einen deutschen Endpoint – das heißt wir generieren einen Controller, der dann auf Deutsch arbeiten kann. Und dieser Controller übernimmt dann zum Beispiel das Wort „Bär“. Das heißt wir können uns mit /bär das Tier „Bär“ laden. Oder mit /katze eine „Katze“ anzeigen lassen. Wenn man nur /deutsch eingibt, dann bekommen wir die deutschen Metadaten dieser Verwaltung. Das soll unser Ziel sein, schauen wir mal, ob wir das schaffen.

Das könnte so funktionieren: der Client schickt diesen Request, also das /deutsch oder /deutsch/bär. Unser .NET Webservice analysiert diesen Request, das heißt es parst sich raus welche Sprache, welches Tier. Dann – das in orange – das sind Middlewares. Wir verwenden hier Middlewares, erstens zum Authentifizieren – das lassen wir jetzt einmal aus in unserem einfachen Beispiel, aber man kann jede Authentifizierung hernehmen, die so angeboten wird –, als nächstes wollen wir uns ein Assembly generieren – wir brauchen von irgendwo her die Informationen, wie ist so ein Bär und so eine Katze aufgebaut, wie schaut so ein Modell aus. Wenn wir dann diese Informationen haben, dann generieren wir einen Code und aus diesem Code generieren wir dann eine Assembly. Und diese Assembly hängen wir in der Middleware zur aktuellen Anwendung hinzu – ihr müsst euch vorstellen, wir haben einen Prozess laufen, da kommt ein Request rein, während dieser Request verarbeitet wird, generieren wir eine neue Assembly und hängen es zum aktuellen Prozess dazu. Dann ist als letzten Schritt nötig, wir wollen dann diese URL – also /deutsch/bär – intern so verändern, dass es dann auf diesen neu generierten Controller weiterleitet. Schauen wir mal ob wir das hinbringen.

Dazu Step 1: wir wollen jetzt einmal einen Code kompilieren und diese Assembly dann erhalten, sagen wir mal so. Gut, wie fangen wir an? Ich habe gesagt wir brauchen eine Middleware, um das zu tun. Ihr seht, hier – also im Startup.cs im Configure – verwenden wir diese Middleware, das heißt wir rufen auf app.UseMiddleware und verwenden da unser selbst geschriebene Middleware. Hüpfen wir da gleich einmal rein, was macht diese Middleware? Diese Middleware analysiert den Request, also was steht da drinnen? Je nachdem wird es dann irgendwas machen. Das passiert im Invoke. Über den httpContext.Request.Path.Value bekommen wir diesen Pfad. Das heißt /deutsch/bär oder nur /deutsch. Das heißt im ersten Teil steht einmal die Language drinnen, das ist jetzt relativ einfach – Errorhandling ist jetzt noch nicht so toll ausgebaut, ich habe mich da jetzt einmal auf das ganz Essentielle konzentriert und das Drumherum könnten wir ja dann noch hinzufügen. Wir merken uns jetzt einmal diese Language. Und mittels dieser language rufen wir einen controllerGenerator auf, den habe ich mir selber geschrieben. Und der schaut folgendermaßen aus: wir hüpfen zuerst einmal nach unten, weil wir wissen, immer wenn wir einmal was kompilieren wollen, brauchen wir zuerst einmal einen Code. Das einfachste, wie man einen Code erzeugen kann, ist, man nimmt einen String Builder und schreibt den Code runter. In dem Fall wird unser Code so ausschauen: wir haben ein paar usings, wir verwenden OData, wir verwenden ASP.NET Sachen, die DataAnnotations für die Attribute und Serialization. Wir generieren unseren Namespace und dann wird es spannend. Dann generieren wir uns einen Controller und das Model – jetzt fragt ihr euch sicher, wo kriegen wir diese Informationen her, was generieren wir da? In dem Fall sind wir da eigentlich in unseren Möglichkeiten nicht eingeschränkt: wir können ein Webservice aufrufen und diese Metadaten irgendwo herholen, oder eine Google Suche machen und die Ergebnisse dann quasi in einem Controller verwenden oder was auch immer. In dem Falle – dass es einfach ist – habe ich einfach JSON Dateien angelegt. Ihr seht in den Resources einen Bear.json und ein Cat.json, wir schauen uns zum Beispiel mal diese Katze an. In dieser Katze ist einfach definiert, wir bieten diese Katze in zwei Sprachen an, in Deutsch heißt sie „Katze“ und auf Englisch heißt sie „Cat“. Da drinnen steht auch die Information, wie ist so eine Katze aufgebaut, das heißt diese Katze hat ein Name, ein Gewicht, eine Farbe, einen Geburtstag und eine Anzahl an Impfungen. Wenn wir uns hingegen den Bär anschauen, den wir da haben – der hat nur zwei Properties: Name und Gewicht. Okay, das ist einmal unser Modell. Diese Modell quasi laden wir je nach dem was wir aufrufen und generieren wir. Das heißt wir waren da im ControllerGenerator.cs. Das heißt für jedes von den files – in dem Fall Bär und Katze – generieren wir uns einen so einen Controller. der Name wirD heißen language_name, also in diesem Falle würde es deutsch_bearController sein, der Katzencontroller würde dann heißen deutsch_katzeController. Wir generieren uns da drinnen einfach einen Konstruktor dazu und die Get-Methode, die wir schon gesehen haben. Wir haben gesagt das EnableQuery brauchen wir wieder für OData. Was dann passiert, wenn dieser Controller aufgerufen wird bzw. diese Methode, ist: es wird von der Basisklasse, von AnimalBaseController das Get aufgerufen mit einem generischen Typ – das ist wieder der Name, in dem Fall Katze. Dies existiert aber auch nicht wenn wir das ganze starten, das heißt wir müssen uns dann auch diese Katze selber generieren. Das findet dann da noch statt. Das heißt wir bauen uns da eine Klasse Katze und da drinnen haben wir dann noch die Properties, die wir uns gerade im JSON angeschaut haben. Das heißt wir werden uns einen String Name oder ein Double Gewicht oder sowas generieren lassen. Dieser ganze Code, was passiert mit dem? Schauen wir, wo das aufgerufen wird, das wird genau da aufgerufen. Das heißt dieser Code, den wir da raus bekommen, wollen wir jetzt einfach kompilieren. Zur Erinnerung, wir befinden uns noch immer in der Middleware. Da drinnen rufen wir jetzt den C# Compiler auf. Das funktioniert so, indem wir uns jetzt einmal einen SyntaxTree erzeugen lassen. Wir brauchen natürlich Referenzen für diese Assembly – weil diese Assembly hat eine Referenz auf OData, hat auf die ASP.NET Core Dinge Referenzen. Da machen wir es uns einfach und sagen, wir wollen einfach von der CurrentDomain – also von der aktuell ausgeführten Web Anwendung – die Referenzen nehmen und für die Assembly, die wir kompilieren wollen, verwenden. Das funktioniert ganz gut, weil wir haben ja eine Web Anwendung mit OData aktiviert und genau das selbe wollen für das, was wir kompilieren auch. Deswegen ist es relativ einfach, wenn wir exakt die selben Referenzen nehmen. Der zweite Aufruf, den wir brauchen ist CSharpComilation.Create – da geben wir einfach ein paar Optionen an, beispielsweise verwenden wir das neueste C# und wir optimieren es noch auf Release, aber das ist nichts spannendes mehr. Mit Emit – quasi das Create kompiliert die Assembly und hat es dann im Speicher – schreiben wir dann diese generierte Assembly auf die Festplatte. Wenn das ganze funktioniert hat, dann werden wir diese Assembly laden und zurückgeben. Was machen wir dann mit diesen? Wir waren dann in der Middleware, quasi. Mit dieser Assembly, die wir generiert haben, machen wir dann weitere spannende Sachen, das mache ich noch kurz auf der Folie noch.

Das heißt jetzt haben wir eine frische Assembly generiert, ein Modell und einen Controller. Diese Assembly wollen wir jetzt in den laufenden Prozess hinzufügen – da machen wir genau dort weiter, wo wir jetzt aufgehört haben. Diese Assembly fügen wir bei einem partManager hinzu. Dieser partManager – den habe ich mir natürlich wieder selber geschrieben – verwendet ein Konzept, was sich applicationParts nennt. applicationParts ist glaube ich mit .NET Core 3.0 eingeführt worden. Diese applicationParts ermöglichen es, Assemblies zum laufenden Prozess hinzuzufügen. Das machen wir so, indem wir den applicationPartManager, welcher von ASP.NET Core angeboten wird, verwenden und einfach zu den ApplicationParts unsere neue Assembly hinzufügen. So einfach. Wenn man das jetzt alleine macht, dann wird noch nichts passieren, das ist eher eine Performance Geschichte – man muss sich vorstellen, es passieren sehr viele Calls, die auf irgendwelche Methoden und Controller und so zugreifen und wenn man da jedes Mal checken würde, ob sich diese AssemblyParts verändert haben oder so, dann wär das eher eine Performance Geschichte. Deswegen gibt es einen ActionDescriptor quasi, dem wir sagen können „es hat sich was geändert“, man setzt hier einfach ein .HasChanged auf true. In dem Fall ist das eine selber geschriebene Klasse, die aber von IActionDescriptorChangeProvider abgeleitet ist und diese haben wir registriert. Und auf diese IActionDescriptorChangeProvider reagiert das ASP.NET und ladet genau das HasChanged. Das heißt, jedes Mal wenn irgendein Controller gesucht wird, wird vorher gecheckt ist das HasChanged auf true? Wenn das der Fall ist, dann werden die applicationParts neu reingeladen und dann die dementsprechend neuen Assemblies verwendet. Gut, das heißt wir haben gesagt, wir setzen das HasChanged einfach auf true und nachher müssen wir noch die TokenSource auf Cancel(). Das ist so wie: brich das aktuell Vorhandene ab und beim nächsten Zugriff quasi baut sich das dann eh wieder neu auf mit unserer neu hinzugefügten Assembly. Gut, damit sollte das eigentlich schon funktionieren.

Damit haben wir unseren zweiten Schritt schon erledigt, das heißt im beste Fall haben wir jetzt eine Web Anwendung laufen und durch das, dass wir /deutsch aufgerufen haben, haben wir eine deutsche API hinzugefügt zur aktuellen Anwendung. Und jetzt haben wir ja gesagt, dieser neue Controller heißt so wie deutsch_bearController. Wir haben jetzt aber in der URL, wenn wir uns erinnern, Deutsch und da vielleicht Bär angegeben. Das heißt, es gibt keinen Bär Controller, sondern es gibt einen deutsch_bearController. Damit man das trotzdem richtig aufrufen können, müssen wir jetzt die URL, die reinkommt, ein bisschen umschreiben. Das machen wir folgendermaßen: im Startup.cs fügen wir da eine Zeile hinzu – oder in dem Fall zwei Zeilen. Nämlich wir mappen ein DynamicControllerRoute. Diese Klasse – die zeige ich gleich her – ist ein Transformer, die genau diese Transformation macht. Das heißt das was reinkommt nenn wir Controller und Method und jetzt schauen wir uns an, was wir aus diesen machen. Wir nehmen diesen Controller, wo unsere Sprache drinnensteht und merken uns das einmal. Dann nehmen wir den „method“ – also den zweiten Teil, wo dann eventuell das Tier drinnensteht, also Bär, Katze oder Hund oder was auch immer – und merken uns das dann in „animal“. Wir schreiben jetzt diesen Controller – also den ersten Part – auf den echten Controllernamen um. Da haben wir gesagt, das ist language_tiername und genau das machen wir da: $“{language}_{animal}“; Und dann fügen wir noch ein neues Property hinzu, da drinnen, nämlich „action“. In „action“ steht immer die Methode drinnen in diesem Controller, die aufgerufen wird. In diesem Fall wollen wir von diesem deutsch_baerController die Get Methode aufrufen, das heißt wir befüllen „controller“ und „action“ und geben genau diese Werte zurück und im besten Fall, wenn wir das jetzt ausführen und zum Beispiel deutsch/bär aufrufen, wird folgendes passieren. Da kann ich da ein bisschen – also da in diesem Ordner liegt derweilen noch kein dll oder sonst irgendwas drinnen und im besten Fall – jetzt wird gerade deutsch/bär aufgerufen, das ist die Standard Route die ich da hinterlegt habe. Und wir sehen es wird uns da gerade ein deutsch.dll generiert. Das heißt wir haben im Hintergrund in der Middleware einen Controller und Model generiert und das ganze kompiliert heißt deutsch.dll und dann haben wir die URL noch umgeschrieben, damit sie auch wirklich den neu generierten Controller aufruft und das Ergebnis ist, wir laden uns jetzt aus diesem JSON gleich Daten. Jetzt probieren wir es noch mit english/cat, ob das auch funktioniert. Zum Beispiel cat. Auweh. Vielleicht habe ich Englisch anders geschrieben. Probieren wir es mit deutsch/katze, jawoll. Schauen wir uns noch an, wie das mit Englisch funktionieren würde, da müssen wir natürlich in das file reinschauen. Ha! Funktioniert! Okay, ich glaub in dem Fall habe ich nur die Groß-/Kleinschreibung nicht berücksichtigt, aber wir sehen jetzt, ich habe das Ganze jetzt mit Englisch aufgerufen und es hat uns ein Englisch.dll erzeugt. Das heißt, wir haben jetzt zwei Assemblies – nämlich eine deutsche Variante und eine englische Variante – und je nachdem was ich da jetzt aufrufe – entweder Englisch oder Deutsch –, ruft es das eine oder das andere auf. Der Unterschied ist, ihr seht, wir haben jetzt English/Cat aufgerufen und da heißen die Properties „name“, „weight“, „color“, „birthday“ und „vaccinations“. Wenn wir jetzt die deutsche Variante aufrufen, nämlich Deutsch/Katze, dann haben wir hier „Name“, „Gewicht“, „Farbe“, „Geburtstag“ und „Impfungen“. Das heißt, wir haben zwei komplett verschiedene Models und zwei komplett verschiedene Controller, die wir zur Laufzeit erzeugt haben und auf diesige weiterleiten. Das ganze Drumherum ein bisschen failsave machen – das heißt, wenn man da klein katze schreibt, dann sollte das natürlich auch das – ahja, das funktioniert jetzt sogar – aber wenn man klein deutsch schreibt, glaube ich, dann funktioniert es nicht – ah, funktioniert auch. Okay, bisschen eine failsave Logik haben wir eh drinnen, aber nicht recht viel. Also, wunderbar! Das heißt, wir haben genau das erreicht, was wir wollten. Wir haben zur Laufzeit etwas kompiliert, diese Controller rufen wir direkt auf. Eins fehlt natürlich noch als Erklärung: wie kommt OData jetzt zu dem? Da muss man noch wissen, wenn man das jetzt einfach kompilieren und reinhängen, dann weiß OData noch nichts davon. Deswegen müssen wir das in unserer Middleware noch machen. Wir haben gesagt, wir haben den partManager damit wir die Assembly hinzufügen und jetzt müssen wir noch das MapODataRoute aufrufen. Das ist quasi der selbe Aufruf wie wir es zuvor gehabt haben bei unserem einfachen Beispiel, nur dass wir jetzt das Modell zur Laufzeit erzeugen müssen. Das machen wir folgendermaßen, indem wir diese Assembly – also die geben wir quasi da in die GetEdmModel Methode rein. Diese Assembly, die wir uns gerade generiert haben – das deutsch.dll – und da suchen wir, welche Typen da drinnen sind. Alle Typen, die ein Entity darstellen – das heißt bei uns vom BaseEntity abgeleitet sind – fügen wir zu diesem Modell hinzu. Dieser kryptisch aussehende Aufruf ist nicht mehr als dieses EntitySet generisch aufzurufen für Katze oder für Bär. Genau das machen wir – das heißt wir rufen auf der Instanz von ODataConventionModelBuilder genau dieses EntitySet auf. Das machen wir eben da drinnen mit jeder Klasse, also mit Katze und Bär. Dadurch – ich zeige es noch einmal her – haben wir erreicht, dass OData quasi dynamisch die neu erstellten Assemblies in die OData Logik aufnimmt. Wenn wir Deutsch/Bär angeben, dann bekommen wir auch die OData Metadaten dazu. Das können wir jetzt auch ausprobieren – dieses Ding da heißt „ä“, als Umlaut a. Wenn wir das direkt aufrufen, dann sehen wir auch hier macht OData uns wieder das gesamte Modell und PowerBI beispielsweise braucht dieses Modell, um die gesamte OData Funktionalität zu verwenden.

Okay, wunderbar, haben wir geschafft. Das heißt, ich gebe noch eine kurze Summary was wir jetzt gemacht haben. Wir haben uns zuerst einmal angeschaut wie funktioniert OData selber. Wir haben gesehen, es ist ganz einfach, wir fügen quasi OData grundsätzlich hinzu. Wir registrieren die MapODataRoute, geben an, unter welche Route OData zur Verfügung sein soll. Dann machen wir bei unserem Controller auf der Methode, zum Beispiel auf dem GetEnableQuery. Das letzte was wir brauchen, ist, am Modell brauchen wir zumindest einen Primary Key. Wir haben das ganze dann so dynamisch wie möglich gemacht, das heißt wir haben zur Startup Zeit noch gar nichts. Wir haben noch kein Modell, wir haben noch keinen Controller. Wenn dieser Request reinkommt, laden wir die Daten von irgendwo – wir haben gesagt es geht ein Webservice oder von der Datenbank, oder von der Festplatte –, in diesem Fall haben wir von der Festplatte ein JSON geladen. Da drinnen sind Informationen, mit denen wir einen Code generieren können. Wir haben dann zur Laufzeit in der Middleware – das ist wichtig, weil wenn es später passiert, dann kann er nicht mehr direkt auf den Controller zugreifen, sondern es muss wirklich in der Middleware passieren – den Code generiert, die Assembly gemacht, mit Application Parts zur aktuellen Anwendung hinzugefügt und über das URL-Umschreiben haben wir dann gleich diesen Controller aufgerufen.

Damit ist der Beweis vollbracht – wir können eine komplett dynamische OData Solution machen. Ich hoffe, es war interessant, vielen Dank für die Einladung und freue mich gern, wenn ich noch einmal kommen darf. Danke!

 

Technologien in diesem Artikel