From 5e9aad859fe70762affb884f4751858cc208c2e7 Mon Sep 17 00:00:00 2001 From: Vladimir Shchur Date: Sat, 21 Dec 2024 11:25:13 -0800 Subject: [PATCH] Fix for rendering views with for non-ascii charactes --- src/Oxpecker.Solid.FablePlugin/Library.fs | 18 ++++-------- .../Oxpecker.ViewEngine.fsproj | 6 ++-- src/Oxpecker.ViewEngine/Render.fs | 12 +++++--- src/Oxpecker/HttpContextExtensions.fs | 28 +++++++++---------- src/Oxpecker/Oxpecker.fsproj | 6 ++-- .../Oxpecker.ViewEngine.Tests/Tools.Tests.fs | 6 ++-- 6 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/Oxpecker.Solid.FablePlugin/Library.fs b/src/Oxpecker.Solid.FablePlugin/Library.fs index 42b7144..795e678 100644 --- a/src/Oxpecker.Solid.FablePlugin/Library.fs +++ b/src/Oxpecker.Solid.FablePlugin/Library.fs @@ -426,14 +426,10 @@ module internal rec AST = let transformException (pluginHelper: PluginHelper) (range: SourceLocation option) = let childrenExpression = Value( - NewList ( - Some ( - Value ( - StringConstant $"Fable compilation error in {pluginHelper.CurrentFile}", None - ), - Value ( - NewList(None, Type.Tuple([ Type.String; Type.Any ], false)), None - ) + NewList( + Some( + Value(StringConstant $"Fable compilation error in {pluginHelper.CurrentFile}", None), + Value(NewList(None, Type.Tuple([ Type.String; Type.Any ], false)), None) ), Type.Tuple([ Type.String; Type.Any ], false) ), @@ -468,10 +464,8 @@ type SolidComponentAttribute() = // Console.WriteLine("!End! MemberDecl") let newBody = match memberDecl.Body with - | Extended (Throw _, range) -> - AST.transformException pluginHelper range - | _ -> - AST.transform memberDecl.Body + | Extended(Throw _, range) -> AST.transformException pluginHelper range + | _ -> AST.transform memberDecl.Body { memberDecl with Body = newBody } override _.TransformCall(_: PluginHelper, _: MemberFunctionOrValue, expr: Expr) : Expr = expr diff --git a/src/Oxpecker.ViewEngine/Oxpecker.ViewEngine.fsproj b/src/Oxpecker.ViewEngine/Oxpecker.ViewEngine.fsproj index edd3ee5..a983b83 100644 --- a/src/Oxpecker.ViewEngine/Oxpecker.ViewEngine.fsproj +++ b/src/Oxpecker.ViewEngine/Oxpecker.ViewEngine.fsproj @@ -20,9 +20,9 @@ README.md true snupkg - 1.0.0 - 1.0.0 - Major release + 1.0.1 + 1.0.1 + Added explicit flush when rendering to Stream and TextWriter diff --git a/src/Oxpecker.ViewEngine/Render.fs b/src/Oxpecker.ViewEngine/Render.fs index b1d3b37..98959ec 100644 --- a/src/Oxpecker.ViewEngine/Render.fs +++ b/src/Oxpecker.ViewEngine/Render.fs @@ -59,7 +59,8 @@ let toStreamAsync (stream: Stream) (view: #HtmlElement) = use _ = streamWriter :> IAsyncDisposable try view.Render sb - return! streamWriter.WriteAsync(sb) + do! streamWriter.WriteAsync(sb) + return! streamWriter.FlushAsync() finally StringBuilderPool.Return(sb) } @@ -73,7 +74,8 @@ let toHtmlDocStreamAsync (stream: Stream) (view: #HtmlElement) = use _ = streamWriter :> IAsyncDisposable try view.Render sb - return! streamWriter.WriteAsync(sb) + do! streamWriter.WriteAsync(sb) + return! streamWriter.FlushAsync() finally StringBuilderPool.Return(sb) } @@ -84,7 +86,8 @@ let toTextWriterAsync (textWriter: TextWriter) (view: #HtmlElement) = task { try view.Render sb - return! textWriter.WriteAsync(sb) + do! textWriter.WriteAsync(sb) + return! textWriter.FlushAsync() finally StringBuilderPool.Return(sb) } @@ -96,7 +99,8 @@ let toHtmlDocTextWriterAsync (textWriter: TextWriter) (view: #HtmlElement) = task { try view.Render sb - return! textWriter.WriteAsync(sb) + do! textWriter.WriteAsync(sb) + return! textWriter.FlushAsync() finally StringBuilderPool.Return(sb) } diff --git a/src/Oxpecker/HttpContextExtensions.fs b/src/Oxpecker/HttpContextExtensions.fs index b2a2d34..45a8e54 100644 --- a/src/Oxpecker/HttpContextExtensions.fs +++ b/src/Oxpecker/HttpContextExtensions.fs @@ -2,6 +2,7 @@ namespace Oxpecker open System open System.Collections.Generic +open System.IO open System.Runtime.CompilerServices open System.Text open System.Threading.Tasks @@ -285,27 +286,27 @@ type HttpContextExtensions() = /// Task of writing to the body of the response. [] static member WriteHtmlView(ctx: HttpContext, htmlView: #HtmlElement) = - let sb = Tools.StringBuilderPool.Get().AppendLine("") + let memoryStream = recyclableMemoryStreamManager.Value.GetStream() ctx.Response.ContentType <- "text/html; charset=utf-8" if ctx.Request.Method <> HttpMethods.Head then - let textWriter = new HttpResponseStreamWriter(ctx.Response.Body, Encoding.UTF8) task { - use _ = textWriter :> IAsyncDisposable try - htmlView.Render(sb) - ctx.Response.ContentLength <- sb.Length - return! textWriter.WriteAsync(sb) + do! Render.toHtmlDocStreamAsync memoryStream htmlView + ctx.Response.ContentLength <- memoryStream.Length + memoryStream.Seek(0, SeekOrigin.Begin) |> ignore + return! memoryStream.CopyToAsync ctx.Response.Body finally - Tools.StringBuilderPool.Return(sb) + memoryStream.Dispose() } :> Task else - try - htmlView.Render(sb) - ctx.Response.ContentLength <- sb.Length - Task.CompletedTask - finally - Tools.StringBuilderPool.Return(sb) + task { + try + do! Render.toHtmlDocStreamAsync memoryStream htmlView + ctx.Response.ContentLength <- memoryStream.Length + finally + memoryStream.Dispose() + } /// /// Serializes a stream of HTML elements and writes the output to the body of the HTTP response using chunked transfer encoding. @@ -323,7 +324,6 @@ type HttpContextExtensions() = use _ = textWriter :> IAsyncDisposable while! enumerator.MoveNextAsync() do do! Render.toTextWriterAsync textWriter enumerator.Current - do! textWriter.FlushAsync() } /// diff --git a/src/Oxpecker/Oxpecker.fsproj b/src/Oxpecker/Oxpecker.fsproj index 93c7566..b62e7fd 100644 --- a/src/Oxpecker/Oxpecker.fsproj +++ b/src/Oxpecker/Oxpecker.fsproj @@ -20,9 +20,9 @@ README.md true snupkg - 1.1.1 - 1.1.1 - Oxpecker.ModelValidation module added + 1.1.2 + 1.1.2 + Fixed WriteHtmlView for non-ascii charactes diff --git a/tests/Oxpecker.ViewEngine.Tests/Tools.Tests.fs b/tests/Oxpecker.ViewEngine.Tests/Tools.Tests.fs index 608f002..7df7da3 100644 --- a/tests/Oxpecker.ViewEngine.Tests/Tools.Tests.fs +++ b/tests/Oxpecker.ViewEngine.Tests/Tools.Tests.fs @@ -54,6 +54,6 @@ let ``HTMLEncoding.encodeCharsInto and WebUtility.HtmlEncode are exactly the sam [] let ``indexOfHtmlEncodingChar works correctly`` () = - CustomWebUtility.indexOfHtmlEncodingChar ("test".AsSpan()) |> shouldEqual -1 - CustomWebUtility.indexOfHtmlEncodingChar ("test shouldEqual 4 - CustomWebUtility.indexOfHtmlEncodingChar ("test😀sd".AsSpan()) |> shouldEqual 4 + CustomWebUtility.indexOfHtmlEncodingChar("test".AsSpan()) |> shouldEqual -1 + CustomWebUtility.indexOfHtmlEncodingChar("test shouldEqual 4 + CustomWebUtility.indexOfHtmlEncodingChar("test😀sd".AsSpan()) |> shouldEqual 4