question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Bolero without Razor

See original GitHub issue

Currently building a proof of concept with Blazor Server Side and Bolero.

Everything works as expected - all I want to do now is get rid of the _Host.cshtml file and replace it with just code.

https://github.com/dotnet/aspnetcore/discussions/29120

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

5reactions
JaggerJocommented, Jan 17, 2021

Hey @Tarmil !

I have a non hacky version! It also uses the Bolero DSL to generate the root serving/host page which makes it nicely coherent.

Would be nice if this could be simplified to not need a container & context. (maybe we could provide a host base class that provides methods to enable that, …)

body [] [
    Html.rootComp<App>(RenderMode.ServerPrerendered, null, this.Container, this.HttpContext)
    ...
]

This code just works but still needs some polishing:

open System
open System.IO
open System.Text
open System.Collections.Generic
open System.Threading.Tasks

open Microsoft.AspNetCore
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Routing
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Hosting
open Microsoft.AspNetCore.Components
open Microsoft.AspNetCore.Mvc.Rendering
open Microsoft.AspNetCore.Mvc.ViewFeatures

open FSharp.Control.Tasks.V2

open Bolero
open Bolero.Html

[<AutoOpen>]
module Extensions =
    
    type Object with
        member this.Ignore : unit = ()
        

[<RequireQualifiedAccess>]
module Html =
    open System.Text.Encodings.Web
    
    let renderComp<'T when 'T :> IComponent>(mode: RenderMode, parameters: obj, container: IServiceProvider, httpContext: HttpContext) : Task<string> =
        task {  
            let htmlHelper = container.GetService<IHtmlHelper>()

            (htmlHelper :?> IViewContextAware).Contextualize(ViewContext(HttpContext = httpContext))

            // RenderComponentAsync is an extension from "Microsoft.AspNetCore.Mvc.Rendering"
            let! componentHtmlContent = htmlHelper.RenderComponentAsync<'T>(mode, parameters)
            
            let componentHtml: string =
                using (new StringWriter()) (fun writer ->
                    componentHtmlContent.WriteTo (writer, HtmlEncoder.Default)
                    writer.Flush()
                    writer.ToString()
                )
                
            return componentHtml
        }
        
    let rootComp<'T when 'T :> IComponent>(mode: RenderMode, parameters: obj, container: IServiceProvider, httpContext: HttpContext) : Node =
        renderComp<'T>(mode, parameters, container, httpContext)
        |> Async.AwaitTask
        |> Async.RunSynchronously
        |> Node.RawHtml

type App() =
    inherit Component()

    member this.ForceRerender() =
        base.StateHasChanged()
    
    override this.Render() =
        div [] [
            p [] [textf "Hello World! No Razor needed"]
        ]

type Host() =
    inherit Component()

    [<Parameter>]
    member val Container: IServiceProvider = null with get, set
    
    [<Parameter>]
    member val HttpContext: HttpContext = null with get, set
    
    member this.ForceRerender() =
        base.StateHasChanged()
    
    override this.Render() =
        html [
            attr.lang "en"
        ] [
            head [] [
                meta [ attr.charset "UTF-8" ]
                meta [ attr.name "viewport"; attr.content "width=device-width, initial-scale=1.0" ]
                title [] [ text "Test App" ]
                
                //Html.``base`` [ attr.href "~/" ]
                
                link [
                    attr.rel "stylesheet"
                    attr.href "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css"
                ]
                
                link [
                    attr.rel "stylesheet"
                    attr.href "css/index.css"
                ]
            ]
            body [] [
                Html.rootComp<App>(RenderMode.ServerPrerendered, null, this.Container, this.HttpContext)
                
                script [ attr.src "_framework/blazor.server.js" ] []
            ]            
        ]
           
    static member Index(container: IServiceProvider, httpContext: HttpContext) : Task =
        task {
            let parameters = Dictionary<string, obj>()
            parameters.Add ("Container", container :> obj)
            parameters.Add ("HttpContext", httpContext :> obj)
            
            let! componentHtml = Html.renderComp<Host>(RenderMode.Static, parameters, container, httpContext)
                
            let! _ =
                (componentHtml)
                |> Encoding.UTF8.GetBytes
                |> ReadOnlyMemory
                |> httpContext.Response.BodyWriter.WriteAsync
                
            return ()
        } :> _

type Startup () =

    member this.ConfigureServices(services: IServiceCollection) =
        services.AddMvc().AddRazorRuntimeCompilation().Ignore
        services.AddServerSideBlazor().Ignore

    member this.Configure(app: IApplicationBuilder, env: IWebHostEnvironment) =
        if env.IsDevelopment () || true then
            app.UseDeveloperExceptionPage().Ignore
        else
            app.UseExceptionHandler("/Error").Ignore
            app.UseHsts().Ignore
        
        let endpoints (endpoints: IEndpointRouteBuilder) : unit =
            endpoints.MapBlazorHub().Ignore
            endpoints.MapFallback(fun ctx -> Host.Index (app.ApplicationServices, ctx)).Ignore
            endpoints.MapGet("/", fun ctx -> Host.Index (app.ApplicationServices, ctx)).Ignore

        app.UseHttpsRedirection().Ignore
        app.UseStaticFiles().Ignore
        app.UseRouting().Ignore
        app.UseEndpoints(fun arg -> endpoints arg).Ignore

module Program =

    [<EntryPoint>]
    let main args =
        Host
            .CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(fun webBuilder -> webBuilder.UseStartup<Startup>().Ignore)
            .Build()
            .Run()
        0
3reactions
JaggerJocommented, Jan 17, 2021

Nice progress!

Yeah, rendering the bootstrapping document wasn’t that straightforward. Wasn’t sure if rendering it as a static component would just work - but really like how it turned out.

The container can be retrieved from the HttpContext with ctx.RequestServices. In fact I only managed to run your code using that instead of app.ApplicationServices, otherwise it couldn’t access scoped services such as IViewBufferScope. I doubt it’s possible to avoid passing the HttpContext, but that’s fine.

Ahh ok.

Looks like this also runs nicely in WebAssembly mode by using an App defined in a client project, changing the RenderMode, the js script URL, and adding app.UseBlazorFrameworkFiles(). This is great!

So I guess we can nuke the _Host.cshtml file finally 🥳

Read more comments on GitHub >

github_iconTop Results From Across the Web

Blazor without Razor?
It's not C#, but the F# Bolero project allows you to write views with either F# and/or HTML templates. Could be fun learning...
Read more >
Hosting models - Bolero: F# in WebAssembly
Note that prerendering is not possible in plain WebAssembly mode. ... It can also be used with an F# application with Razor runtime...
Read more >
Bolero Beverly Hills Charcoal + Bamboo Men's Shave & ...
Shop Amazon for Bolero Beverly Hills Charcoal + Bamboo Men's Shave & After Shave Lotion Cream 2 Pack Set ... Non-returnable due to...
Read more >
Using Blazor features - Bolero: F# in WebAssembly
This section documents how to use a Blazor Component, either referenced from a C# Razor project, or created in F# by inheriting from...
Read more >
BOLERO Beverly hills Mens Shave set 4 pcs(Cedar wood ...
Amazon.com: BOLERO Beverly hills Mens Shave set 4 pcs(Cedar wood + eucalyptus and Charcoal+ Bamboo) : Beauty ... Non-returnable due to hazmat safety...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found