Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.

Link zu der Vergleichsansicht

Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung
Nächste Überarbeitung
Vorherige Überarbeitung
ef_informatik:voci_rest [2022-05-24 06:28] – [Ein Web-Server mit C#] hofef_informatik:voci_rest [2022-05-31 13:16] (aktuell) sca
Zeile 78: Zeile 78:
  
 Dann starten wir einen Server, der Dateien im Ordner `website` als Dateien überträgt: Dann starten wir einen Server, der Dateien im Ordner `website` als Dateien überträgt:
-<code c# server.cs>+<code c# VociServer.cs>
 using Grapevine; using Grapevine;
  
-// Start the standalone REST server+namespace server;
-using (var server = RestServerBuilder.UseDefaults().Build()) { +
-    // We want to serve static content (HTML, CSS, JS that is not modified +
-    // but transferred to the client as is from the "website" folder. +
-    string staticPath = Path.Combine(Directory.GetCurrentDirectory(), "website"); +
-    server.ContentFolders.Add(staticPath); +
-    server.UseContentFolders();+
  
-    // Start the server. +class VociServer { 
-    // The server will scan the entire assembly for RestResource annotations +    static void Main(string[] args) { 
-    // and add them to the served prefixes. +        // Start the standalone REST server. 
-    server.Start();+        using (var server = RestServerBuilder.UseDefaults().Build()) 
 +        { 
 +            // The server will scan the entire assembly for RestResource annotations 
 +            // and add them to the served prefixes.
  
-    // Block the thread to keep the process from terminating+            // In addition, we want to serve static content (HTML, CSS, JS that is not modified 
-    Console.WriteLine("Press enter to stop the server"); +            // but transferred to the client as is from the "website" folder
-    Console.ReadLine();+            string staticPath = Path.Combine(Directory.GetCurrentDirectory(), "website"); 
 +            server.ContentFolders.Add(staticPath); 
 +            server.UseContentFolders(); 
 + 
 +            // Start the server. 
 +            server.Start(); 
 + 
 +            Console.WriteLine("Press enter to stop the server"); 
 +            Console.ReadLine(); 
 +        } 
 +    }
 } }
 </code> </code>
  
-Wenn wir ihn starten mit `dotnet run`, können wir alle Dateien im `website` Ordner übertragen.+Wenn wir ihn starten mit `dotnet run`, können wir alle Dateien im `website` Ordner übertragen. Wenn du jetzt mehrere `Main` Funktionen hast, kannst du die auszuführende angeben mit  
 + 
 +``` 
 +dotnet build -p:StartupObject=server.VociServer 
 +dotnet run  
 +```
  
 Eine einfache Website könnte beispielsweise mit folgendem HTML dargestellt werden: Eine einfache Website könnte beispielsweise mit folgendem HTML dargestellt werden:
Zeile 118: Zeile 130:
  
   * Binde `grapevine` in deinen Code ein.   * Binde `grapevine` in deinen Code ein.
-  * Füge einen Ordern `website` in den Projekt ein und füge eine leere Website als Datei `website/index.html` hinzu.+  * Füge einen Ordner `website` in den Projekt ein und füge obige HTML-Seite als Datei `website/index.html` hinzu.
   * Starte den Server mit `dotnet run` und rufe http://localhost:1234/index.html auf - die Datei sollte im Browser angezeigt werden.   * Starte den Server mit `dotnet run` und rufe http://localhost:1234/index.html auf - die Datei sollte im Browser angezeigt werden.
  
-#### REST+### Dynamische Ressourcen
  
-Um dynamische Daten zu übertragen, müssen wir noch Server-Code schreiben, der weiss, wie er auf bestimmte URLs reagieren sollDazu verwenden wir //Annotations//, die vom Server automatisch erkannt werden:+Für eine Webapp wollen wir nicht nur ganze Dateien übertragen, sondern *dynamisch* auf eine Anfrage (also eine bestimmte URL) reagieren.
  
-<code c# rest.cs> +Wir müssen also Server-Code schreiben, der weiss, wie er auf bestimmte URLs reagieren soll. Dazu verwenden wir //Annotations//, die vom Server automatisch erkannt werden. Das zugrundeliegende Muster heisst *Representational State Transfer* ([[wpde>Representational_State_Transfer|REST]]), deshalb heissen die Annotations `RestResource` und `RestRoute`. 
-using voci;+ 
 +Der folgende Code wird ausgeführt, wenn die Adresse http://localhost:1234/hello/world aufgerufen wird: 
 + 
 +<code c# Hello.cs> 
 +using System.Net; 
 +using Grapevine; 
 + 
 +[RestResource] 
 +public class Hello { 
 +    // RestRoute tells the server to reply to requests of the given pattern. 
 +    // "Get": the HTTP-method (normally either "Get" or "Post"
 +    // "/hello/world": the path to match. 
 +    [RestRoute("Get", "/hello/world")] 
 +    public async Task World(IHttpContext context) { 
 +        await context.Response.SendResponseAsync("Hello Client"); 
 +    } 
 +
 +</code> 
 + 
 +Es ist auch möglich, statt eines exakten Pfads ein Muster anzugeben und die passenden Segmente auszulesen. Achtung: in URLs werden Sonderzeichen und Leerschläge speziell codiert, wir müssen die Codierung wieder rückgängig machen: 
 + 
 +<code c#> 
 +    // Matches any path of the pattern "/greetings/<anything>" 
 +    [RestRoute("Get", "/greetings/{name}")] 
 +    public async Task Greetings(IHttpContext context) { 
 +        // Extract the name from the URL path. 
 +        // UrlDecode ensures that spaces and special characters are in the right format. 
 +        string name = WebUtility.UrlDecode(context.Request.PathParameters["name"]); 
 +        await context.Response.SendResponseAsync($"Greetings, {name}!"); 
 +    } 
 +</code> 
 + 
 +#### Aufgabe: Dynamischer Webserver 
 + 
 +  * Füge eine neue Klasse wie die obige zu deinem Code hinzu. 
 +  * Teste, ob du den Webserver auf `http://localhost:1234/hello/world` erreichst, indem du die Adresse mit dem Browser öffnest. 
 +    * Was passiert, wenn du `http://localhost:1234/hello/mars` aufrufst? 
 +  * Füge eine Route mit Muster und Parametern ein, z.B. `http://localhost:1234/greetings/Mr.%20Bond`. 
 +  * Sicherheit: überlege dir, wie ein Angreifer Unfug treiben könnte mit unserem Code. 
 + 
 +### Web-Applikation 
 + 
 +Nun kennen wir die Grundbausteine einer Webapp. Wir möchten nun unseren Voci-Trainer als Web-Schnittstelle zur Verfügung stellen. Verwende zum Start den untenstehenden Code und verbinde ihn mit deinem Voci-Code, um ein zufälliges Wort zurückzugeben. 
 + 
 +<code c# VociRoutes.cs>
 using System.Net; using System.Net;
 using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
Zeile 132: Zeile 188:
  
 /** /**
- * A resource is something that matches a web address (URL).  
- * The UnitResource contains routes for serving words. 
- * 
  * ServiceLifetime.Singleton tells the server to keep the same  * ServiceLifetime.Singleton tells the server to keep the same
  * resource around for the entire time the application runs, instead  * resource around for the entire time the application runs, instead
Zeile 141: Zeile 194:
 [RestResource] [RestResource]
 [ResourceLifetime(ServiceLifetime.Singleton)] [ResourceLifetime(ServiceLifetime.Singleton)]
-public class UnitResource +public class VociRoutes { 
-+    // TODO: load the unit from CSV 
-    private Unit unit = UnitReader.readFromCsv("data/de_en.csv");+    private Unit unit;
  
-    // RestRoute tells the server to connect incoming requests of the given 
-    // pattern to this method. 
     [RestRoute("Get", "/random")]     [RestRoute("Get", "/random")]
-    public async Task Random(IHttpContext context) +    public async Task Random(IHttpContext context) { 
-    +        // TODO: Choose a random word from the unit and return in the request.
-        Word entry = unit.PickOne(); +
-        await context.Response.SendResponseAsync($"{entry.Origin}");+
     }     }
 } }
 </code> </code>
  
-Wir können auch auf dynamische Parameter im URL-Pfad zugreifen:+#### Aufgabe: Web-Schnittstelle für Voci-App 
 +  * Starte mit dem obigen Code und gib ein zufälliges Wort zurück. 
 +  * Teste, ob du den Webserver auf `http://localhost:1234/random` erreichst. Was passiert beim Reload (Ctrl+R)? 
 +  * Nächster Schritt: baue eine Route ein, um vom Client einen Übersetzungsversuch einzulesen, z.B. auf den Pfad `/submit/{word}/{translation}`. 
 +    * Die Logik solltest du eigentlich direkt vom bisherigen Konsolenprogramm übernehmen können.
  
-<code crest.cs> +#### Aufgabe: Statische Webseite 
-    [RestRoute("Get", "/submit/{word}/{translation}")] +  * Baue eine einfache Webseite mit HTML & CSS für die Voci-App (more details coming)
-    public async Task Word(IHttpContext context+  * Lerninhalte zu HTML & CSS im [[gf_informatik:web:start|Grundlagenfach-Wiki]
-    { + 
-        // URL parameters are available from the context+#### Aufgabe: Web-App 
-        // Since URLs are encoded, we need to decode them in order to properly +  * Verknüpfen von Website und dynamischen Inhalten mit Javascript (TBD)
-        // handle umlauts and spaces and accents. + 
-        string word WebUtility.UrlDecode(context.Request.PathParameters["word"]); + 
-        string translation = WebUtility.UrlDecode(context.Request.PathParameters["translation"]); +## JavaScript 
-        Word entry unit.Find(word); + 
-        bool correct translation.Equals(entry.Translation); +JavaScript einbinden: 
-        unit.Record(entry, correct); +<code html index.html> 
-        string message correct ? $"Correct, the answer is {entry.Translation}!: $"Incorrect! The correct answer is {entry.Translation}"; +<html> 
-        await context.Response.SendResponseAsync(message); +    <head> 
-    }+        <title>Voci Trainer</title> 
 +        <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
 +        <link rel="stylesheet" href="voci.css" /
 +    </head> 
 +    <body> 
 +        <h1>Voci Trainer</h1> 
 +        <div class="words"> 
 +            <span id="original"></span> 
 +            <input type="text" id="translation" name="translation" onkeyup="keyup(this, event);"> 
 +            <div class="buttons"> 
 +                <button type="button" id="submit" onclick="submit();">Eingabe</button> 
 +            </div> 
 +        </div> 
 +        <div class="status"
 +            <p id="reply"></p> 
 +            <p id="stats"></p> 
 +        </div> 
 +    </body> 
 +    <script src="voci.js" async></script> 
 +</html>
 </code> </code>
  
-## AufgabeWebserver Hello World +JavaScript Code
-  Baue Grapevine in dein Programm ein und starte einen Webserver+<code javascript voci.js> 
-    Teste, ob du den Webserver auf `http://localhost:1234` erreichtsindem du die Adresse mit dem Browser öffnest+/*
-    * Baue eine Resource ein, ähnlich wie der Beispielcode oben+ * Function called when the user clicks the submit button or presses Enter
-    * Gib einen String im HTTP-Request zurück, z.B`"Hello World"` + */ 
-    * Teste sie mit einem Browser aus. +async function submit() { 
-  * Verwende die Entwicklertools (Alt+Ctrl+I / Cmd+Alt+I), um die Requests und Responses anzusehen.+    // Get the original and translated wordssend them to the server
 +    original = document.getElementById("original").innerHTML; 
 +    translation = document.getElementById("translation").value; 
 +    uri = `/submit/${original}/${translation}`; 
 +    let response = await fetch(uri);
  
-## Aufgabe: Webserver bedient dynamische Anfragen +    let response_text =  await response.text(); 
-  * Baue eine Route ein (mit der `RestRouteAnnotation), die ein zufälliges Wort zur Übersetzung zurückgibt.+    if (!response.ok) { 
 +        // something unexpected happened... 
 +        console.log(`Error: ${response_text} in response to '${uri}'`)
 +        return; 
 +    } 
 +    // Update the status text. 
 +    document.getElementById("reply").innerText = response_text; 
 +     
 +    // And fetch the next word. 
 +    next(); 
 +}
  
-## Aufgabe: Statische Webseite +/** 
-  * Baue eine einfache Webseite mit HTML & CSS (more details coming)+ * Update the stats. 
 + */ 
 +async function updateStats() { 
 +    uri = '/stats'; 
 +    let response = await fetch(uri); 
 +    let response_text =  await response.text(); 
 +    document.getElementById("stats").innerText = response_text; 
 +
 + 
 +/*
 + * Fetches a new word to translate. Called automatically after submit(), and initially for the first 
 + * word. 
 + */ 
 +async function next() { 
 +    // Ensure the stats are correctly displayed. 
 +    updateStats(); 
 + 
 +    uri = `/random`; 
 +    let response = await fetch(uri); 
 +    let response_text =  await response.text(); 
 +    if (!response.ok) { 
 +        console.log(`Error: ${response_text} in response to '${uri}'`); 
 +        return; 
 +    } 
 +    document.getElementById("original").innerText = response_text; 
 +    document.getElementById("translation").value = ""; 
 +
 + 
 +/** Called when a key is released within input. */ 
 +async function keyup(input, event) { 
 +    var code = event.code; 
 +    if (code == "Enter") { 
 +        submit(); 
 +    } 
 +
 + 
 +// Initial call to fetch the first word. 
 +next(); 
 +</code>
  
-## Aufgabe: Web-App 
-  * Verknüpfen von Website und dynamischen Inhalten mit Javascript (TBD) 
  • ef_informatik/voci_rest.1653373695.txt.gz
  • Zuletzt geändert: 2022-05-24 06:28
  • von hof