REST API

    • C#
      Hi,

      Im Rahmen einer Webservices Vorlesung habe ich diverse Dokumentationen für REST APIs geschrieben und ich dachte ich teile das hier mit euch.

      Was ist REST?
      = Representational State Transfer

      REST ist eine Abstraktion der Struktur und des Verhaltens des Word Wide Webs. Bei REST wird über die Zugriffsmethode des Protokolls sowie einer URI eine Repräsentation oder Modifikation einer Ressource angefragt.
      REST setzt auf dem HTTP Protokoll auf, was sehr günstig ist da das Protokoll bereits REST konform arbeitet. REST Services sind zustandslos, d.h. der Server speichert keine Zwischenzustände zwischen zwei Anfragen. Das bringt den Vorteil das REST-APIs sehr hoch skalierbar sind, da die Last auf mehrere Rechner verteilt werden kann, da alle Informationen die vom Client benötigt werden in der Anfrage enthalten sind.

      HTTP gestüztes REST bietet diese Methoden:
      GET
      Fordert die angegebene Ressource vom Server an. GET weist keine Nebeneffekte auf. Der Zustand am Server wird nicht verändert, weshalb GET als sicher bezeichnet wird.
      POST
      Fügt eine neue (Sub-)Ressource unterhalb der angegebenen Ressource ein. Da die neue Ressource noch keinen URI besitzt, adressiert der URI die übergeordnete Ressource. Als Ergebnis wird der neue Ressourcenlink dem Client zurückgegeben. POST kann im weiteren Sinne auch dazu verwendet werden, Operationen abzubilden, die von keiner anderen Methode abgedeckt werden.
      PUT
      Die angegebene Ressource wird angelegt. Wenn die Ressource bereits existiert, wird sie geändert.
      PATCH
      Die angegebene Ressource wird angelegt. Wenn die Ressource bereits existiert, wird sie geändert.
      DELETE
      Löscht die angegebene Ressource.


      ACHTUNG: Richtige REST-APIs müssen das HATEOAS (Hypermedia As The Engine Of Application State) Prinzip implementieren. Das heißt das die Ressourcen, ähnlich wie im World Wide Web, untereinander verlinkt sein sollen.
      Einfaches Beispiel einer Kunden-Verwaltung:
      GET some/api/Customer/1 würde idealerweise eine solche Antwort zurückgeben:

      Quellcode

      1. {
      2. "name" : "Some_Customer",
      3. "orders" : [
      4. {
      5. "id" : xyz,
      6. "link" : "some/api/Orders/xyz"
      7. }
      8. {
      9. "id" : abc,
      10. "link" : "some/api/Orders/abc"
      11. }
      12. ]
      13. }

      Das hat zur Folge, dass man anhand des links auf alle Orders des Kunden sofort zugreifen kann ohne aus der OrderID und der API Dokumentation selbst eine URL zusammenbasteln muss.

      Für die Verlinkung verwendet das folgende Beispiel den SIREN Formatter (die notwendigen Klassen hänge ich unten an). Siren kapselt alle Entitäten mit zugehörigen Eigenschaften in generische Objekte. Diese Objekte erhalten sogenannte Links zueinander. Außerdem hat jede Entität zusätzlich sogenannte Actions, d.h. eine Auflistung aller Operationen, die die API auf dem angefragten Objekt anbietet.

      Erstellung einer REST-API
      1. Erstellung der Basisklassen, die der Service anbieten soll. In unserem Fall die Customer-Klasse:

        C#-Quellcode

        1. public class Customer
        2. {
        3. public int Id { get; set; }
        4. public string Name { get; set; }
        5. public DateTime LastContact { get; set; }
        6. public bool IsFavorite { get; set; }
        7. }

      2. Implementierung der Datenquelle, z.B. mit Hilfe des Repository-Patterns:

        C#-Quellcode

        1. public static class CustomerRepository
        2. {
        3. private static readonly Dictionary<int, Customer> s_Repository = new Dictionary<int, Customer>();
        4. static CustomerRepository()
        5. {
        6. var c1 = new Customer { Id = 1, Name = "Hugo", LastContact = new System.DateTime(2016, 3, 21, 12, 30, 23) };
        7. s_Repository.Add(c1.Id, c1);
        8. s_Repository.Add(2, new Customer { Id = 2, Name = "Gabi", LastContact = new System.DateTime(2016, 3, 22, 11, 40, 53) });
        9. s_Repository.Add(3, new Customer { Id = 3, Name = "Chelsea Manning", LastContact = new System.DateTime(2017, 10, 1, 12, 30, 23)});
        10. s_Repository.Add(4, new Customer { Id = 4, Name = "Lars Kaufmann" });
        11. }
        12. public static IEnumerable<Customer> GetAll()
        13. {
        14. return s_Repository.Values;
        15. }
        16. public static Customer Get(int id)
        17. {
        18. return s_Repository[id];
        19. }
        20. public static void Add(Customer customer)
        21. {
        22. s_Repository.Add(customer.Id, customer);
        23. }
        24. public static void Update(Customer customer)
        25. {
        26. s_Repository[customer.Id] = customer;
        27. }
        28. public static void Delete(int id)
        29. {
        30. s_Repository.Remove(id);
        31. }
        32. public static bool TryGetValue(int id, out Customer customer)
        33. {
        34. return s_Repository.TryGetValue(id, out customer);
        35. }
        36. }

        An dieser Stelle würde man hier eine Verbindung zu einer Datenbank aufbauen o.ä. aber um das ganze replizierbar zu machen verwendet das Beispiel statische Daten.
      3. Siren und SirenFormatter importieren, an den beiden Klassen muss nichts geändert werden. In der StartUp.cs muss allerdings der SirenFormatter noch ausgewählt werden:

        C#-Quellcode

        1. // This method gets called by the runtime. Use this method to add services to the container.
        2. public void ConfigureServices(IServiceCollection services) {
        3. services.AddMvc( options=>options.OutputFormatters.Add(new Hypermedia.SirenFormatter()) );
        4. }

      4. Erstellen des Controllers. Idealerweise hat jedes Objekt der Domäne einen eigenen Controller.
        GET api/Customer liefert eine Liste aller Kunden
        GET api/Customer/1 liefert den Kunden mit der ID 1
        POST api/Customer fügt einen neuen Kunden hinzu
        POST api/CustomerFavourites makiert einen Kunden als Favoriten

        C#-Quellcode

        1. //URI Route des Controllers, Wenn [Controller] verwendet wird dann ist es api/<Klassenname - Controller>
        2. [Route("api/[Controller]")]
        3. public class CustomersController : Controller
        4. {
        5. //Operationen Route unter Name angeben
        6. [HttpGet(Name = Routes.CUSTOMERS_GETAllCUSTOMER)]
        7. public IActionResult Get()
        8. {
        9. //Neues Siren Objekt erstellen
        10. var siren = new Siren();
        11. //Verwendete Klassen hinzufügen
        12. siren.Class.Add("Customers");
        13. siren.Class.Add("Collection");
        14. foreach (var customer in CustomerRepository.GetAll())
        15. {
        16. //Entities befüllen und in Siren geben, mit Self-Link
        17. var entity = new EmbeddedRepresentation();
        18. entity.Class.Add("CustomerOverview");
        19. entity.Properties.Add(new Property { Name = "Name", Value = customer.Name });
        20. entity.Properties.Add(new Property { Name = "LastContact", Value = customer.LastContact });
        21. entity.Properties.Add(new Property { Name = "IsFavorite", Value = customer.IsFavorite });
        22. //Self Link hinzufügen
        23. entity.Links.Add(new Link
        24. {
        25. Relation = new List<string> { "self" },
        26. Href = Url.Link(Routes.CUSTOMERS_GETCUSTOMER, new { id = customer.Id })
        27. });
        28. siren.Entities.Add(entity);
        29. }
        30. //Aktionen hinzufügen
        31. siren.Links.Add(new Link
        32. {
        33. Relation = new List<string> { "self" },
        34. //Nur Null wählen wenn keinerlei Pfad Parameter wie api/Customer/1 benötigt werden
        35. Href = Url.Link(Routes.CUSTOMERS_GETAllCUSTOMER, null)
        36. });
        37. return Ok(siren);
        38. }
        39. //Hier wird die ID des Kunden benötigt
        40. [HttpGet("{id}", Name = Routes.CUSTOMERS_GETCUSTOMER)]
        41. public IActionResult Get(int id)
        42. {
        43. try
        44. {
        45. //Objekt anhand von ID aus Repo holen
        46. var customer = CustomerRepository.Get(id);
        47. //Siren Objekt erstellen
        48. var siren = new Siren();
        49. // Verwendete Klaasen hinzufügen
        50. siren.Class.Add("Customer");
        51. //Properties Hinzufügen
        52. siren.Properties.Add(new Property { Name = "Name", Value = customer.Name });
        53. siren.Properties.Add(new Property { Name = "LastContact", Value = customer.LastContact });
        54. siren.Properties.Add(new Property { Name = "IsFavorite", Value = customer.IsFavorite });
        55. // Self-Link zur Methode hinzufügen
        56. siren.Links.Add(new Link
        57. {
        58. Relation = new List<string> { "self" },
        59. Href = Url.Link(Routes.CUSTOMERS_GETCUSTOMER, new { id = customer.Id })
        60. });
        61. //Alle Sonstigen Methoden hinzufügen
        62. if (!customer.IsFavorite)
        63. {
        64. siren.Actions.Add(new Action
        65. {
        66. Name = "MarkAsFavorite",
        67. Method = "POST",
        68. Href = Url.Link(Routes.CUSTOMERS_MARKASFAVORITE, null)
        69. });
        70. }
        71. siren.Actions.Add(new Action
        72. {
        73. Name = "Move",
        74. Method = "POST",
        75. Href = Url.Link(Routes.CUSTOMERS_MOVE, new { id = customer.Id })
        76. });
        77. return Ok(siren);
        78. }
        79. catch (KeyNotFoundException)
        80. {
        81. return NotFound();
        82. }
        83. }
        84. //Einfügen eines neuen Customers über POST
        85. [HttpPost]
        86. public void Post([FromBody]Customer customer)
        87. {
        88. CustomerRepository.Add(customer);
        89. }
        90. }
        91. public static class Routes
        92. {
        93. public const string CUSTOMERS_MARKASFAVORITE = "CUSTOMERS_MARKASFAVORITE";
        94. public const string CUSTOMERS_GETAllCUSTOMER = "CUSTOMERS_GETAllCUSTOMER";
        95. public const string CUSTOMERS_GETCUSTOMER = "CUSTOMERS_GETCUSTOMER";
        96. }

      5. Erstellen von Controllern für spezifische Operationen auf einen Objekt der Domäne:

      C#-Quellcode

      1. [Route("api/[Controller]")]
      2. public class CustomerFavouritesController : Controller
      3. {
      4. [HttpPost ( Name = Routes.CUSTOMERS_MARKASFAVORITE)]
      5. public IActionResult Post([FromBody] Customer customer)
      6. {
      7. CustomerRepository.Get(customer.Id).IsFavorite = true;
      8. return Ok();
      9. }
      10. }


      Das wars auch schon :), damit ist ein erstes sehr grundlegendes Beispiel einer REST-API fertig.
      Wie versprochen hänge ich unten noch die beiden Klassen an.
      Dateien
      • Siren.cs

        (2,12 kB, 207 mal heruntergeladen, zuletzt: )
      • SirenFormatter.cs

        (6,57 kB, 212 mal heruntergeladen, zuletzt: )

      8-) faxe1008 8-)

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „faxe1008“ ()