7
votes

I'm trying to get the generate the pdf in server-side Blazor. I use DinkToPdf as an external library to convert HTML string to pdf. But I'm having trouble of converting the blazor component to HTML string.

There is a way to render Razor templates to a string by using the Razor ViewEngine. From this web http://fizzylogic.nl/2017/08/03/how-to-generate-pdf-documents-in-asp-net-core/

[HttpGet]
public async Task<IActionResult> CreatePDF()
{
    var globalSettings = new GlobalSettings
    {
        ColorMode = ColorMode.Color,
        Orientation = Orientation.Portrait,
        PaperSize = PaperKind.A4,
        Margins = new MarginSettings { Top = 10 },
        DocumentTitle = "PDF Report",
    };

    var objectSettings = new ObjectSettings
    {
        PagesCount = true,
        HtmlContent = "<h>Hello World</h>",
        WebSettings = { DefaultEncoding = "utf-8"},
        HeaderSettings = { FontName = "Arial", FontSize = 9, Right = "Page [page] of [toPage]", Line = true },
        FooterSettings = { FontName = "Arial", FontSize = 9, Line = true, Center = "Report Footer" }
    };

    var pdf = new HtmlToPdfDocument()
    {
        GlobalSettings = globalSettings,
        Objects = { objectSettings }
    };

    var file = _converter.Convert(pdf);
    return File(file,"application/pdf");
}

I need to modify the ObjectSettings.HtmlContent to be my blazor component html string.

3
So to get this straight, what are you using server side blazor for? Are you using it just to render your pdfs? Are you trying to convert your web pages to pdfs? I can't see where your importing any view information into the html to render here, I'm a bit confusedMark Davies
Just for fun to use the server side blazor, since it is easy to convert it back by changing a couple line of codes. And that's correct, I'm trying to "print" my web page. But not just print, I want to customize it with my other blazor component or blazor template.user489566
The goal is to generate a pdf by my blazor component (or view in Razor). The problem I have now is there are no such kinds of view Engine in blazor such that I can easily convert blazor view to html string. Workflow: blazor view =(?)=> html string =(by DinkToPdf library)=> pdfuser489566

3 Answers

2
votes

Are you trying to convert content the user can see? If so, you could add @ref='ContentToRender' to the component you want to render, Blazor will then assign that reference after the component is rendered:

@inject IJSRuntime JSRuntime

<div @ref=ContentToRender>
  ... Your content here...
</div>

@code {
  ElementReference ContentToRender;

  protected async override Task OnAfterRenderAsync(bool firstRender) 
  {
    if (firstRender) 
    {
      string html = await JSRuntime.InvokeAsync<string>("BlazorUniversity.getInnerHTML", ContentToRender);
    }
  };
}

Where the JS would look something like this

var BlazorUniversity = BlazorUniversity || {};
BlazorUniversity.getInnerHTML = function(element) {
  return element.innerHTML;
};

Don't forget to include the JS in your main index.html or _Host.cshtml page.

For more info see Passing HTML Element References on Blazor University.

Note that you will not be able to access the content until at least after the first render event (firstRender == true).

0
votes

This works for me:

...... HtmlContent = TemplateGenerator.GetHTMLString(), ......

    public static string GetHTMLString()
    {
        IRaumNodeProvider RaumNodeProvider = new 
              RaumNodeProvider(Globals.Connectionstring);

        var reservierungen = RaumNodeProvider.GetReservierungen();

        var sb = new StringBuilder();
        sb.Append(@"
                    <html>
                        <head>
                        </head>
                        <body>
                            <div class='header'><h1>Reservierungsliste</h1></div>
                            <table align='center'>
                                <tr>
                                    <th>Id      </th>
                                    <th>Raum    </th>
                                    <th>Datum   </th>
                                    <th>Zeit    </th>
                                    <th>Beleger </th>
                                    <th>Belegung</th>
                                    <th>EmailMsg</th>
                                </tr>");

        foreach (var res in reservierungen)
        {
            sb.AppendFormat(@"<tr>
                                <td>{0}</td>
                                <td>{1}</td>
                                <td>{2}</td>
                                <td>{3}</td>
                                <td>{4}</td>
                                <td>{5}</td>
                                <td>{6}</td>
                              </tr>", 
                                res.Res_Id              
                              , res.RaumName                                  
                              , res.BelegungsDatumTxt   
                              , res.Zeit                
                              , res.Belegung_durch      
                              , res.Belegung            
                              , res.EmailMsg
                              );
        }

        sb.Append(@"
                            </table>
                        </body>
                    </html>");

        return sb.ToString();
    }
0
votes

I had to dig through some of the source code and this is what I found that works for static content (RenderMode.Static) (I haven't tested this with script tags or layout hierarchies or nested components).

Depending on where you are trying to do this (i.e. within a request vs background task), you will either need to supply the current HttpContext or create your own.

public static async Task<string> RenderAsync<TComponent>(IHtmlHelper helper, HttpContext httpContext, object parameters)
{
  if (helper is IViewContextAware viewContextAware)
  {
    viewContextAware.Contextualize(new ViewContext()
    {
      HttpContext = httpContext
    }
  }

  var content = await helper.RenderComponentAsync<TComponent>(RenderMode.Static, parameters);
  var writer = new StringWriter();
  content.WriteTo(writer, HtmlEncoder.Default);
  return writer.ToString();
}

public static HttpContext CreateDefaultContext(IServiceProvider serviceProvider)
{
  return new DefaultHttpContext
  {
    RequestServices = serviceProvider,
    Request =
    {
      Scheme = "http",
      Host = new HostString("localhost"),
      PathBase = "/base",
      Path = "/path",
      QueryString = QueryString.FromUriComponent("?query=value")
    }
  }
}

CreateDefaultContext is loosely based off of this sample found in Microsoft's tests

Sample.razor:

<h3>@Data.Title</h3>

Hello, @Data.FirstName @Data.LastName!

@code {

  [Parameter]
  public Test Data { get; set; }

  public class Test
  {
    public string Title { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }
  }

}

Demo:

var helper = ServiceProvider.GetService<IHtmlHelper>();
var context = CreateDefaultContext(ServiceProvider);
var html = await RenderAsync<Sample>(helper, context, new
{
  Data = new Sample.Test()
  {
    Title = "Stack Overflow Test",
    FirstName = "user",
    LastName = "489566"
  }
});

html value:

<h3>Stack Overflow Test</h3>

Hello, user 489566!