Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
| Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung Nächste Überarbeitung | Vorherige Überarbeitung | ||
| ef_informatik:voci_rest [2022-05-24 06:24] – [Ein Web-Server mit C#] hof | ef_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 |
| - | 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 " | + | |
| - | string staticPath = Path.Combine(Directory.GetCurrentDirectory(), | + | |
| - | server.ContentFolders.Add(staticPath); | + | |
| - | server.UseContentFolders(); | + | |
| - | | + | class VociServer { |
| - | // The server will scan the entire assembly for RestResource annotations | + | static void Main(string[] args) { |
| - | // and add them to the served prefixes. | + | |
| - | server.Start(); | + | using (var server = RestServerBuilder.UseDefaults().Build()) |
| + | { | ||
| + | | ||
| + | // and add them to the served prefixes. | ||
| - | | + | |
| - | Console.WriteLine(" | + | // but transferred |
| - | Console.ReadLine(); | + | |
| + | server.ContentFolders.Add(staticPath); | ||
| + | server.UseContentFolders(); | ||
| + | |||
| + | // Start the server. | ||
| + | server.Start(); | ||
| + | |||
| + | | ||
| + | Console.ReadLine(); | ||
| + | } | ||
| + | } | ||
| } | } | ||
| </ | </ | ||
| - | 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. |
| + | |||
| + | ``` | ||
| + | dotnet build -p: | ||
| + | dotnet run | ||
| + | ``` | ||
| + | |||
| + | Eine einfache Website könnte beispielsweise mit folgendem HTML dargestellt werden: | ||
| + | <code html index.html> | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | </ | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| #### Aufgabe: Statische Webseite | #### Aufgabe: Statische Webseite | ||
| * Binde `grapevine` in deinen Code ein. | * Binde `grapevine` in deinen Code ein. | ||
| - | * Füge einen Ordern | + | * Füge einen Ordner |
| * Starte den Server mit `dotnet run` und rufe http:// | * Starte den Server mit `dotnet run` und rufe http:// | ||
| - | #### REST | + | ### Dynamische Ressourcen |
| - | Um dynamische Daten zu übertragen, | + | Für eine Webapp wollen wir nicht nur ganze Dateien |
| - | <code c# rest.cs> | + | Wir müssen also Server-Code schreiben, der weiss, wie er auf bestimmte URLs reagieren soll. Dazu verwenden wir // |
| - | using voci; | + | |
| + | Der folgende Code wird ausgeführt, | ||
| + | |||
| + | <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. | ||
| + | // " | ||
| + | // "/ | ||
| + | [RestRoute(" | ||
| + | public async Task World(IHttpContext context) { | ||
| + | await context.Response.SendResponseAsync(" | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | 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 "/ | ||
| + | [RestRoute(" | ||
| + | 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[" | ||
| + | await context.Response.SendResponseAsync($" | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | #### Aufgabe: Dynamischer Webserver | ||
| + | |||
| + | * Füge eine neue Klasse wie die obige zu deinem Code hinzu. | ||
| + | * Teste, ob du den Webserver auf `http:// | ||
| + | * Was passiert, wenn du `http:// | ||
| + | * Füge eine Route mit Muster und Parametern ein, z.B. `http:// | ||
| + | * 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 119: | 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 128: | 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(" | + | private Unit unit; |
| - | // RestRoute tells the server to connect incoming requests of the given | ||
| - | // pattern to this method. | ||
| [RestRoute(" | [RestRoute(" | ||
| - | public async Task Random(IHttpContext context) | + | public async Task Random(IHttpContext context) { |
| - | | + | |
| - | | + | |
| - | await context.Response.SendResponseAsync($" | + | |
| } | } | ||
| } | } | ||
| </ | </ | ||
| - | 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:// | ||
| + | * Nächster Schritt: baue eine Route ein, um vom Client einen Übersetzungsversuch einzulesen, z.B. auf den Pfad `/ | ||
| + | * Die Logik solltest du eigentlich direkt vom bisherigen Konsolenprogramm übernehmen können. | ||
| - | <code c# rest.cs> | + | #### Aufgabe: Statische Webseite |
| - | | + | * Baue eine einfache Webseite mit HTML & CSS für die Voci-App |
| - | | + | * Lerninhalte zu HTML & CSS im [[gf_informatik: |
| - | | + | |
| - | // 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 |
| - | // handle umlauts and spaces and accents. | + | |
| - | | + | |
| - | | + | ## JavaScript |
| - | Word entry = unit.Find(word); | + | |
| - | bool correct | + | JavaScript einbinden: |
| - | | + | <code html index.html> |
| - | | + | < |
| - | | + | |
| - | | + | |
| + | <meta name=" | ||
| + | | ||
| + | </head> | ||
| + | < | ||
| + | | ||
| + | | ||
| + | < | ||
| + | <input type=" | ||
| + | < | ||
| + | <button type=" | ||
| + | </ | ||
| + | | ||
| + | | ||
| + | <p id="reply"></ | ||
| + | <p id=" | ||
| + | | ||
| + | | ||
| + | <script src=" | ||
| + | </ | ||
| </ | </ | ||
| - | ## Aufgabe: Webserver 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: | + | /** |
| - | | + | * Function called when the user clicks the submit button or presses Enter. |
| - | | + | */ |
| - | | + | async function submit() { |
| - | * Verwende die Entwicklertools | + | |
| + | | ||
| + | | ||
| + | | ||
| + | let response = await fetch(uri); | ||
| - | ## Aufgabe: Webserver bedient dynamische Anfragen | + | let response_text = await response.text(); |
| - | * Baue eine Route ein (mit der `RestRoute` Annotation), die ein zufälliges Wort zur Übersetzung zurückgibt. | + | if (!response.ok) { |
| + | // something unexpected happened... | ||
| + | console.log(`Error: ${response_text} in response to ' | ||
| + | return; | ||
| + | } | ||
| + | // Update the status text. | ||
| + | document.getElementById(" | ||
| + | |||
| + | // 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 = '/ | ||
| + | let response = await fetch(uri); | ||
| + | let response_text = await response.text(); | ||
| + | document.getElementById(" | ||
| + | } | ||
| + | |||
| + | /** | ||
| + | * 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 = `/ | ||
| + | let response = await fetch(uri); | ||
| + | let response_text = await response.text(); | ||
| + | if (!response.ok) { | ||
| + | console.log(`Error: | ||
| + | return; | ||
| + | } | ||
| + | document.getElementById(" | ||
| + | document.getElementById(" | ||
| + | } | ||
| + | |||
| + | /** Called when a key is released within input. */ | ||
| + | async function keyup(input, | ||
| + | var code = event.code; | ||
| + | if (code == " | ||
| + | submit(); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Initial call to fetch the first word. | ||
| + | next(); | ||
| + | </ | ||
| - | ## Aufgabe: Web-App | ||
| - | * Verknüpfen von Website und dynamischen Inhalten mit Javascript (TBD) | ||