diff --git a/docs/basics/content.de.md b/docs/basics/content.de.md new file mode 100644 index 00000000..fc067b83 --- /dev/null +++ b/docs/basics/content.de.md @@ -0,0 +1,237 @@ +# Modelbindung + +Mit der Modelbindung können wir den Inhalt oder die Zeichenfolge einer Serveranfrage an einen vordefiniertes Datenobjekt binden. + +## Grundlagen + +Um das Binden besser zu verstehen, werfen wir einen kurzen Blick auf den Aufbau einer solchen Serveranfrage. + +```http +POST /greeting HTTP/1.1 +content-type: application/json +content-length: 18 + +{"hello": "world"} +``` + +Die Angabe _content-type_ in der Kopfzeile gibt Aufschluss über die Art des Inhaltes der Anfrage. Vapor nutzt die Angabe um den richtigen Kodierer zum Binden zu finden. + +Im Beispiel können wir erkennen, dass es sich bei dem Inhalt um JSON-Daten handelt. + +## Binden des Inhalts + +Zum Binden des Inhalts müssen wir zuerst eine Struktur vom Typ *Codable* anlegen. Indem wir das Objekt mit Vapor's Protokoll *Content* versehen, werden neben den eigentlichen Bindungsmethoden, der Typ mitvererbt. + +```swift +struct Greeting: Content { + var hello: String +} +``` + +Über die Eigenschaft *content* können wir anschließend die Methode *decode(_:)* verwenden. + +```swift +app.post("greeting") { req in + let greeting = try req.content.decode(Greeting.self) + print(greeting.hello) // "world" + return HTTPStatus.ok +} +``` + +Die Methode *decode(_:)* benutzt die entsprechende Angabe in der Serveranfrage um den passenden Kodierer aufzurufen. + +Sollte kein passender Kodierer gefunden werden oder die Anfrage keine Angaben zum Inhalt besitzen, wird der Fehler 415 (415 Unsupported Media Type) zurückgeliefert. + +### Unterstützte Medien + +Folgende Medien werden von Vapor standardmäßig unterstützt: + +|Bezeichnung |Feldwert |Typ | +|----------------|---------------------------------|-----------------| +|JSON |application/json |`.json` | +|Multipart |multipart/form-data |`.formData` | +|URL-Encoded Form|application/x-www-form-urlencoded|`.urlEncodedForm`| +|Plaintext |text/plain |`.plainText` | +|HTML |text/html |`.html` | + +_Codable_ unterstützt leider nicht alle Medien. + +## Binden der Zeichenfolge + +```http +GET /hello?name=Vapor HTTP/1.1 +content-length: 0 +``` + +Ähnlich wie beim Binden des Inhalts müssen wir für das Binden der Zeichenfolge eine Struktur anlegen und es mit dem Protokoll *Content* versehen. + +Zusätzlich müssen wir die Eigenschaft *name* als optional deklarieren, da Parameter in einer Zeichenfolge immer optional sind. + +```swift +struct Hello: Content { + var name: String? +} +``` + +```swift +app.get("hello") { req -> String in + let hello = try req.query.decode(Hello.self) + return "Hello, \(hello.name ?? "Anonymous")" +} +``` + +Zudem können wir auch Einzelwerte aus der Zeichenabfolge abrufen: + +```swift +app.get("hello") { req -> String in + let name: String? = req.query["name"] + ... +} +``` + +## Hooks + +Vapor ruft automatisch jeweils die beiden Methoden _beforeEncode_ und _afterDecode_ eines Objektes von Typ _Content_ auf. + +Die Methoden sind standardmäßig funktionslos, können aber im Bedarfsfall überschrieben werden. + +```swift +// Runs before this Content is encoded. `mutating` is only required for structs, not classes. +mutating func beforeEncode() throws { + + guard + let name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines), + !name.isEmpty + else { + throw Abort(.badRequest, reason: "Name must not be empty.") + } + self.name = name +} + +// Runs after this Content is decoded. `mutating` is only required for structs, not classes. +mutating func afterDecode() throws { + + self.name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines) + if let name = self.name, name.isEmpty { + throw Abort(.badRequest, reason: "Name must not be empty.") + } +} +``` + +## Standard überschreiben + +Vapor's Standardkodierer kann global oder situationsabhängig überschrieben werden. + +### Global + +Für eine globale Verwendung eines eigenen Kodierer müssen wir ihn der _ContentConfiguration.global_ mitgeben. + +```swift +// create a new JSON encoder that uses unix-timestamp dates +let encoder = JSONEncoder() +encoder.dateEncodingStrategy = .secondsSince1970 + +// override the global encoder used for the `.json` media type +ContentConfiguration.global.use(encoder: encoder, for: .json) +``` + +### Situationsabhängig + +Wir können aber auch den Bindungsmethoden abhängig von der Situation einen Kodierer mitgeben. + +```swift +// create a new JSON decoder that uses unix-timestamp dates +let decoder = JSONDecoder() +decoder.dateDecodingStrategy = .secondsSince1970 + +// decodes Hello struct using custom decoder +let hello = try req.content.decode(Hello.self, using: decoder) +``` + +## Benutzerdefinierte Kodierer + +### Kodierer für Inhalt + +Vapor hat die folgenden zwei Protokolle zum Binden von Inhalt vordefiniert. + +```swift +public protocol ContentEncoder { + func encode(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders) throws + where E: Encodable +} + +public protocol ContentDecoder { + func decode(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders) throws -> D + where D: Decodable +} +``` + +Indem wir einen unseren eigenen Kodierer mit diese beiden Protokolle versehen, kann er von _ContentConfiguration_ entgegengenommen werden. + +### Kodierer für Zeichenfolge + +Für das Binden einer Zeichenabfolge hat Vapor die folgenden zwei Protokolle vordefiniert. + +```swift +public protocol URLQueryDecoder { + func decode(_ decodable: D.Type, from url: URI) throws -> D + where D: Decodable +} + +public protocol URLQueryEncoder { + func encode(_ encodable: E, to url: inout URI) throws + where E: Encodable +} +``` + +### `ResponseEncodable` + +Another approach involves implementing `ResponseEncodable` on your types. Consider this trivial `HTML` wrapper type: + +```swift +struct HTML { + let value: String +} +``` + +Then its `ResponseEncodable` implementation would look like this: + +```swift +extension HTML: ResponseEncodable { + public func encodeResponse(for request: Request) -> EventLoopFuture { + var headers = HTTPHeaders() + headers.add(name: .contentType, value: "text/html") + return request.eventLoop.makeSucceededFuture(.init( + status: .ok, headers: headers, body: .init(string: value) + )) + } +} +``` + +If you're using `async`/`await` you can use `AsyncResponseEncodable`: + +```swift +extension HTML: AsyncResponseEncodable { + public func encodeResponse(for request: Request) async throws -> Response { + var headers = HTTPHeaders() + headers.add(name: .contentType, value: "text/html") + return .init(status: .ok, headers: headers, body: .init(string: value)) + } +} +``` + +Note that this allows customizing the `Content-Type` header. See [`HTTPHeaders` reference](https://api.vapor.codes/vapor/documentation/vapor/response/headers) for more details. + +You can then use `HTML` as a response type in your routes: + +```swift +app.get { _ in + HTML(value: """ + + +

Hello, World!

+ + + """) +} +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 39eb1665..0a1391ed 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -203,6 +203,7 @@ plugins: Deploy: Bereitstellung Contributing: Mitwirken Contributing Guide: Leitfaden für Beiträge + Content: Modelbindung zh: Welcome: 序言 Install: 安装