dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.37k stars 9.99k forks source link

Have to disable StreamRendering for Blazor Web App to run on browser with JS disabled #51389

Open mrpmorris opened 1 year ago

mrpmorris commented 1 year ago

Is there an existing issue for this?

Describe the bug

UK Gov only allows javascript as an experience enhancement. All sites must work on browsers with JS disabled.

If I disable JS and run the new Blazor web app "Weather" page I just get an endless Loading... message.

To fix this I have to remove @attribute [StreamRendering(true)], but then I cannot make use of StreamRendering in the majority of browsers that have JS enabled.

Could this please be fixed to have a fallback, so StreamRendering is only active if true and JS is enabled?

Expected Behavior

Running a StreamRendering(true) decorated page should not stop the page working in a browser with JS disabled.

Steps To Reproduce

1: New Blazor web app 2: Include the sample pages 3: Disable JS in the browser 4: Run the app

Exceptions (if any)

No response

.NET Version

8.0.100-rc.2.23502.2

Anything else?

No response

SteveSandersonMS commented 1 year ago

It's definitely something we've discussed on the team. For .NET 8 there isn't a way to trigger it dynamically so you will need to have it disabled if you can't rely on JS.

My personal preference would be to have a conservative mode that only enables streaming rendering if the request arrives with a certain cookie, and that cookie would be set by blazor.web.js. So, the very first visit from any given user would not have streaming SSR, but if they have JS enabled, then all their subsequent requests would get it.

This could still go wrong if that user later disables JS, but there's no way for the server to know if that has happened so this is unavoidable.

@mkArtakMSFT I recommend we consider this as an enhancement for .NET 9.

mrpmorris commented 1 year ago

Hi Steve

Thanks for your quick response.

I was under the (mis)understanding that this is how it worked. It's a shame, because I thought it would act as a kind of alternative to htmx without having to introduce multiple new endpoints on the servrer. A full progressive-enhancement approach by default, it would have been quite brilliant!

Do you think there might be a way to achieve this in .Net 8 with some kind of middleware to modify the requests and responses, or does adding this attribute cause a difference in code generation?

SteveSandersonMS commented 1 year ago

The server-side logic looks for StreamRenderingAttribute, and that's the only thing that controls it. If streaming is determined to be enabled, then the response HTML comes out in a different shape that requires blazor.web.js.

Theoretically you could put some middleware on the server that buffers the response and then somehow parses and rewrites the HTML to convert it back to a nonstreaming shape, but that sounds very difficult and fragile.

ghost commented 1 year ago

Thanks for contacting us.

We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

mrpmorris commented 1 year ago

I almost had a solution but, unfortunately, the result of looking up StreamRendering is cached. https://source.dot.net/#Microsoft.AspNetCore.Components.Endpoints/Rendering/EndpointComponentState.cs,22

public class MyStreamRenderingAttribute : StreamRenderingAttribute
{
    public MyStreamRenderingAttribute(bool enabled = true) : base(GetEnabled(enabled))
    {
    }

    private static bool GetEnabled(bool enabled)
    {
        if (!enabled) return false;

        var httpContext = new HttpContextAccessor().HttpContext;

        bool result =
            httpContext is not null
            && httpContext.Request.Cookies.TryGetValue("x-JSEnabled", out string? jsEnabled)
            && jsEnabled?.ToLowerInvariant() == "true";

        return result;
    }
}

Would it be acceptable to add an overload to the StreamRenderingAttribute constructor to have the name of a static method to execute to get the value, or some other way of registering a way of programmatically determining it from Component+StreamRenderingAttribute, such as a global hook?

SomeGlobalThing.StreamRenderingHookOfSomeKind = static (IComponent component, StreamRenderingAttribute streamRendering) => streamRendering.Enabled && {SomeCodeOfMyOwn}

Then this approach would work, and we'd have progressive enhancement, which would be amazing!

SteveSandersonMS commented 1 year ago

@mrpmorris It's too late to add features like this to .NET 8, and we can make a better API for it in .NET 9. Sorry that's not ideal for you.

mrpmorris commented 1 year ago

It's a shame, but it's my fault for not looking at .Net 8 earlier.

Thanks!

cobrien2 commented 10 months ago

Using .NET 8 the best solution I can see at the moment is, have a cookie set by some JavaScript, then have two endpoints, one with StreamRenderingAttribute and one without for non JavaScript. The endpoint with StreamRenderingAttribute will check the presence of the cookie and if it doesn't exists it redirects to the non JavaScript endpoint.

You could put the JavaScript to add the cookie on a previous page. But if you don't have a previous page then the initial request will go to the non JavaScript endpoint which will contain the JavaScript to add the cookie for subsequent requests to use stream rendering.

ghost commented 10 months ago

Thanks for contacting us.

We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

mrpmorris commented 7 months ago

Is this something I could contribute?

If so, could someone give me some guidance?