最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Rendering blazor component to a string, render mode not supported - Stack Overflow

programmeradmin8浏览0评论

I'm trying to get html from RadarChart(chartjs.blazor) component

var html = await blazorRenderer.RenderComponent<RadarChart>();

but I keep getting exception:

Cannot supply a component of type 'x.Components.Dashboard.RadarChart'
 because the current platform does not support the render mode 
 'Microsoft.AspNetCore.Components.Web.InteractiveServerRenderMode'.

I need html string so I could use for DinkToPdf export.

Here is brief look at key parts of my RadarChart component.

@rendermode InteractiveServer

@using ApexCharts
@using x.DTOs
@using x.Services
@using ChartJs.Blazor
@using ChartJs.Blazor.Common
@using ChartJs.Blazor.Common.Axes
@using ChartJs.Blazor.Common.Enums
@using ChartJs.Blazor.Common.Time
@using ChartJs.Blazor.Util
@using ChartJs.Blazor.LineChart
@using Microsoft.EntityFrameworkCore


@inject IUserService userService
@inject IAnswerService answerService
@inject ILogService logService
@inject IJSRuntime JSRuntime
<canvas id="radarChart"></canvas>

@code {
 private async Task RenderChart()
 {
...
     await JSRuntime.InvokeVoidAsync("renderRadarChart", "radarChart", data, options);
 }

BlazorRenderers function looks like this:

private Task<string> RenderComponent<T>(ParameterView parameters) where T : IComponent
       {
           return _htmlRenderer.Dispatcher.InvokeAsync(async () =>
           {
               var output = await _htmlRenderer.RenderComponentAsync<T>(parameters);
               return output.ToHtmlString();
           });
       }

I'm trying to get html from RadarChart(chartjs.blazor) component

var html = await blazorRenderer.RenderComponent<RadarChart>();

but I keep getting exception:

Cannot supply a component of type 'x.Components.Dashboard.RadarChart'
 because the current platform does not support the render mode 
 'Microsoft.AspNetCore.Components.Web.InteractiveServerRenderMode'.

I need html string so I could use for DinkToPdf export.

Here is brief look at key parts of my RadarChart component.

@rendermode InteractiveServer

@using ApexCharts
@using x.DTOs
@using x.Services
@using ChartJs.Blazor
@using ChartJs.Blazor.Common
@using ChartJs.Blazor.Common.Axes
@using ChartJs.Blazor.Common.Enums
@using ChartJs.Blazor.Common.Time
@using ChartJs.Blazor.Util
@using ChartJs.Blazor.LineChart
@using Microsoft.EntityFrameworkCore


@inject IUserService userService
@inject IAnswerService answerService
@inject ILogService logService
@inject IJSRuntime JSRuntime
<canvas id="radarChart"></canvas>

@code {
 private async Task RenderChart()
 {
...
     await JSRuntime.InvokeVoidAsync("renderRadarChart", "radarChart", data, options);
 }

BlazorRenderers function looks like this:

private Task<string> RenderComponent<T>(ParameterView parameters) where T : IComponent
       {
           return _htmlRenderer.Dispatcher.InvokeAsync(async () =>
           {
               var output = await _htmlRenderer.RenderComponentAsync<T>(parameters);
               return output.ToHtmlString();
           });
       }
Share Improve this question edited Mar 27 at 13:10 gildraus asked Mar 27 at 11:41 gildraus gildraus 214 bronze badges 6
  • 1 In your RadarChart component we can see that it relies on js method to render html content so that we are not able to get the html content in server side directly, we have to get the html content in client side and then pass it to server side. I think this is the reason for the exception. – Tiny Wang Commented Mar 27 at 13:09
  • May I know whether you followed this document and is trying to render component into html string outside of asp core or is trying to get html content within the blazor app? If you are trying to get html content of the razor component within blazor app, I'm afraid you could use js like window.getElementHtml = (elementId) => {var element = document.getElementById(elementId); return element.outerHTML;}; to get your desired HTML content. – Tiny Wang Commented Mar 27 at 13:49
  • I actually used a wrapper around the HTML renderer that I found here: andrewlock/… . I'm trying to get this string inside PdfHelper.cs, so I guess inside ASP.NET. –  gildraus Commented Mar 27 at 14:33
  • I also referred this bolg, it's based on another project instead of inside the blazor app as it mentioned to create a new project via dotnet new web so that the razor component it used is a static component. However, in your scenaior, the razor component requires to call js method to generate html content for <canvas id="radarChart"></canvas>. Therefore, what the blog showed can't help you resolve your issue. Could you pls try my js code above? – Tiny Wang Commented Mar 27 at 14:45
  • I just shared my test result below. I'm afraid the RenderComponentAsync can be used out of the blazor context seperately. – Tiny Wang Commented Mar 28 at 8:10
 |  Show 1 more comment

1 Answer 1

Reset to default 0

According to this document, blazor razor components can be rendered outside of the context of an HTTP request. OP also followed this blog which demonstrates that this is feasible with a seperate razor component out of a blazor application. So that I'm curious that whether we could render razor components in side the blazor application. I had a test with codes below.

This is the service I registered for render html string.


using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components;

namespace BlazorApp3.Services
{
    public interface IComponentRendererService
    {
        Task<string> RenderComponentToHtmlAsync<TComponent>(ParameterView parameters) where TComponent : IComponent;
    }

    public class ComponentRendererService : IComponentRendererService, IAsyncDisposable
    {
        private readonly IServiceProvider _serviceProvider;
        private readonly ILoggerFactory _loggerFactory;
        private readonly HtmlRenderer _htmlRenderer;

        public ComponentRendererService(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
        {
            _serviceProvider = serviceProvider;
            _loggerFactory = loggerFactory;
            _htmlRenderer = new HtmlRenderer(_serviceProvider, _loggerFactory);
        }

        public async Task<string> RenderComponentToHtmlAsync<TComponent>(ParameterView parameters) where TComponent : IComponent
        {
            var output = await _htmlRenderer.RenderComponentAsync<TComponent>(parameters);
            return output.ToHtmlString();
        }

        public async ValueTask DisposeAsync()
        {
            await _htmlRenderer.DisposeAsync();
        }
    }
}

And in my Counter.razor, I hope to render html content.

@page "/counter"
@using BlazorApp3.Components.Pages
@using BlazorApp3.Services
@rendermode InteractiveServer
@inject IComponentRendererService ComponentRendererService
@inject IJSRuntime JSRuntime

<div id="counter-component">
<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</div>

<button class="btn btn-secondary" @onclick="RenderToHtml">Render to HTML</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    private async Task RenderToHtml()
    {
        // Pass the current state (currentCount) as a parameter
        var parameters = ParameterView.FromDictionary(new Dictionary<string, object?>
        {
            { "currentCount", currentCount }
        });
        // this gave me the same error as what OP got
        var renderedHtml = await ComponentRendererService.RenderComponentToHtmlAsync<Counter>(parameters);
        // it gave me error "System.InvalidOperationException: The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state." 
        //var renderedHtml = await ComponentRendererService.RenderComponentToHtmlAsync<Home>(parameters);
        // var renderedHtml = await JSRuntime.InvokeAsync<string>("getElementHtml", "counter-component");// this worked well
    }
}

I found that no matter I tried to render a static render mode component or an interactive render mode component, I failed finally. Therefore I tried to use fronent js method to get the html content. I added script below into App.razor which just behind <script src="_framework/blazor.web.js"></script>.

<script>
    window.getElementHtml = (elementId) => {
        alert(elementId);
        var element = document.getElementById(elementId);
        return element ? element.outerHTML : '';
    };
</script>

=========================

In above codes, we have got the html content successfully in the client side via js. Just like you know, the content for <canvas id="radarChart"></canvas> requires to be rendered in the client side too as it's generated by calling js method. Therefore we are not able to get the rendered html content in the backend server directly. We have to get the content in the client and pass it to your PdfHelper.cs.

We can send http request to dispatch the data. Here's what I did. In Program.cs, I inject http service and Controller API service.

using BlazorApp3.Components;
using BlazorApp3.Services;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddControllers();
builder.Services.AddHttpClient();

builder.Services.AddScoped<IComponentRendererService, ComponentRendererService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAntifery();
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();
    
app.MapControllers();

app.Run();

Next, I created Controllers folder and create my Controller to receive html content.

[ApiController]
[Microsoft.AspNetCore.Mvc.Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpPost]
    public string GetAsync([FromBody] HtmlContentRequest request)
    {
        var html = request.HtmlContent;
        return "success";
    }
}

public class HtmlContentRequest
{
    public string HtmlContent { get; set; }
}

In my counter.razor component, I @inject HttpClient HttpClient and call the API endpoint.

private async Task RenderToHtml()
{
    // Pass the current state (currentCount) as a parameter
    var parameters = ParameterView.FromDictionary(new Dictionary<string, object?>
    {
        { "currentCount", currentCount }
    });

    // var renderedHtml = await ComponentRendererService.RenderComponentToHtmlAsync<Home>(parameters);
    var renderedHtml = await JSRuntime.InvokeAsync<string>("getElementHtml", "counter-component");

    var request = new { HtmlContent = renderedHtml };
    var response = await HttpClient.PostAsJsonAsync("https://localhost:7210/api/WeatherForecast", request);
    if (response.IsSuccessStatusCode)
    {
        var res = await response.Content.ReadAsStringAsync();
    }
}
发布评论

评论列表(0)

  1. 暂无评论