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
|
Show 1 more comment
1 Answer
Reset to default 0According 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();
}
}
window.getElementHtml = (elementId) => {var element = document.getElementById(elementId); return element.outerHTML;};
to get your desired HTML content. – Tiny Wang Commented Mar 27 at 13:49dotnet 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:45RenderComponentAsync
can be used out of the blazor context seperately. – Tiny Wang Commented Mar 28 at 8:10