REST Service selfmade

Wer meinen Einträge die letzten Wochen verfolgt, wird bemerkt haben, das ich mich der Service Schicht angenommen habe. Ich bin mit ASP.NET Web Api nicht sonderlich glücklich, weil zu komplex und teilweise unfertig. Die (ADO.NET) WCF Dataservices sind wesentlich weiter, sehen für mich aber nicht zukunftsfähig aus. Also habe ich mir überlegt einen schmutzigen Service Prototypen zu bauen.

Meine Anforderungen

  • Sicherheit mit ASP.NET Membership und Basic Authentifizierung
  • REST in Reinform- Hateoas- also mindestens die Möglichkeit LINK einzubauen
  • Nur Read
  • Odata Query Syntax Support
  • Einfach zu verstehen

und ich habe ein Ergebnis mit dem ich ganz leidlich zufrieden bin. Manchmal schreibe ich den Code so richtig von Hand komplett selber und manchmal habe ich eine Bibliothek gefunden die fast perfekt passt. Ich habe mir auch Sourcen von diversen Code Portalen reingezogen habe mich dann aber immer dagegen entschieden, weil mir der Aufwand zu hoch schien das an meine Bedürfnisse anzupassen.

Über das Security Konzept schreibe ich einen eigenen Blog Artikel.

HATEOAS

Ich hatte entdeckt, das man mit dem JsonSerializer quasi mit einer Zeile Code aus einer ASPX Webform Seite eine REST Service machen kann.

image

Ich bin mir sicher, das es einige gibt die mich für so was hassen.

Damit kann ich auch mein Ziel nicht erreichen. Also wechsle ich in der Webform einfach in das Codebehind. Der HTML Part  in der ASPX Seite wird komplett entfernt. Der Namensraum Newtonsoft muss natürlich importiert werden bzw. das Paket per Nuget geladen werden.

   1:  Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
   2:          Dim nw = New NorthwindEntities1
   3:          Dim json As String = 
JsonConvert.SerializeObject(nw, Formatting.Indented, New LinkJsonConverter(GetType(Customers)))
   4:          Response.Write(json)
   5:  End Sub

Also nun kann der Benutzer die ASPX Seite aufrufen und erhält die Daten im Json Format. Wer obigen Code ausführt wird eine Fehlermeldung erhalten, weil die Klasse LinkJsonConverter erst noch implementiert werden muss. Zweck dieser Klasse ist es, beim konvertieren [Link] einzufügen um den Hypermedia Ansprüchen aus HATEOAS zu genügen. Jedenfalls kann so aus einer Liste von Customers der Empfänger erkennen, wie er einen einzelnen Eintrag abrufen kann.

image

Für die Art und Weise wie man in den Json Text die Verlinkung einbaut, gibt es keinen Standard, aber eine Menge Leute die glauben einen definieren zu können (HALSiren, Collection+Json) Ist auch nicht wichtig. Ich kann in den Json Serializer eingreifen und damit meinen eigenen Standard schaffen. Dafür gibt's eine vom JsonConverter geerbte Klasse, die im Event Write immer für jeden neuen Kunden ein Link Element anfügt.

   1:  Public Class LinkJsonConverter
   2:      Inherits JsonConverter
   3:      Private ReadOnly _types As Type()
   4:   
   5:      Public Sub New(ParamArray types As Type())
   6:          _types = types
   7:      End Sub
   8:   
   9:      Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
  10:          Dim t As JToken = JToken.FromObject(value)
  11:          If t.Type <> JTokenType.Object Then
  12:              t.WriteTo(writer)
  13:          Else
  14:              Dim o As JObject = DirectCast(t, JObject)
  15:              Dim Link = o.Properties().Select(Function(p) p.Name)
  16:              o.AddFirst(New JProperty("Link", HttpContext.Current.Request.Path + 
"?Customer_ID=" + o.Item("Customer_ID").ToString))
  17:              o.WriteTo(writer)
  18:          End If
  19:      End Sub
  20:   

21: Public Overrides Function ReadJson(reader As JsonReader, objectType As Type,

existingValue As Object, serializer As JsonSerializer) As Object

  22:   
  23:      End Function
  24:   
  25:      Public Overrides ReadOnly Property CanRead() As Boolean
  26:          Get
  27:              Return False
  28:          End Get
  29:      End Property
  30:   
  31:      Public Overrides Function CanConvert(objectType As Type) As Boolean
  32:          Return _types.Any(Function(t) t = objectType)
  33:      End Function
  34:  End Class

Wie soll nun so ein Link aufgebaut sein? Relativ, Absolut? Per /ID oder (id) oder ganz anders? Ihre Entscheidung. In diesem Fall per Querystring.

Man muss nicht unbedingt einen JsonConverter schreiben. Alternativen sind z.B. zwischen Objekte die die passenden Attribute und Werte halten oder per Attribut (<JsonProperty()>) im Modell. Im ersteren Fall würde dann eine Klasse JsconCustomer geschaffen die ein Link Property Zusätzlich besitzt. Dies füllt man per LINQ und serialisiert das dann direkt wie hier schon gezeigt.

Als nächstes geht es um das Problem aus einem Odata ähnlichen Querstring ein LINQ Kommando zu machen. Ich habe dafür die ganz wunderbare Library gefunden LINQ2REST. Sie stammt von Jacob Reimers als Source oder Nuget. Dies hängt sich mit einer Extension Method Filter in LINQ rein

   1:  Imports Linq2Rest
   2:  ...
   3:   
   4:  filterdNW = nw.Customers.Filter(Request.Params)
   5:  Dim json As String = JsonConvert.SerializeObject(filterdNW, Formatting.Indented, 
New LinkJsonConverter(GetType(Customers)))
   6:  Response.Write(json)

Damit kann zb folgendes erfolgreich $top oder $filter verwenden um die Ergebnismenge einzuschränken. Das hat mich wirklich begeistert.

Es gibt allerdings noch ein kleines Problem. Ich habe mir in den Kopf gesetzt einen Bypass verwenden zu können um auf einen Entität zugreifen zu können. Orientiert habe ich mich an dem Hypermedia Link customers.aspx?Customer_ID=ALFKI. Der steht laut meiner Definition so in den Json Daten und sollte für den Client auch aufrufbar sein. Darüberhinaus habe ich mir Überlegt das bei Relationen dieses Szenario flexibler sein sollte, so das ich alle Felder auswählen kann. Gefunden habe ich eine Lösung mit Dynamic LINQ die schon rund 5 Jahre alt ist und in der Tat auch per Nuget installiert werden kann (System.Linq.Dynamic). Sogar VB und C# Quellcode findet sich im Blog von Scott Guthrie, so das man die Klasse auch als Datei einbinden kann. Es wird die Where Methode überladen.

Kurz gesagt kann man damit aus einem Text direkt LINQ generieren lassen.

   1:    Dim nw = New NorthwindEntities1
   2:    Dim filterdNW
   3:    If Request.Params.ToString.Contains("$") Then
   4:              filterdNW = nw.Customers.Filter(Request.Params)
   5:    ElseIf Request.QueryString.Count = 0 Then
   6:              filterdNW = nw.Customers
   7:   Else
   8:          Dim q = Request.Params.Item(0)
   9:          Dim k = Request.Params.Keys(0)
  10:          Dim l = k + "= @0"
  11:          filterdNW = nw.Customers.Where(l, q).FirstOrDefault()
  12:    End If
  13:    Dim json As String = JsonConvert.SerializeObject(filterdNW, Formatting.Indented, 
New LinkJsonConverter(GetType(Customers)))
  14:    Response.Write(json)

Letztendlich habe ich das Ziel erreicht. Der Code ist einfach und hängt nicht von Manipulationen der IIS Request Pipline ab. Damit kann ich auch in bestehende Web Anwendungen REST Schnittstellen integrieren.

Die Lösung ist nicht perfekt und auch nicht fertig. Für mich eine Frage ist, warum ich noch über LINQ gehen muss. Eigentlich könnte man ja auch Odata2SQL direkt wählen. Aber das wird sich auch noch klären

 

 
Kommentare sind geschlossen