user489566
user489566

Reputation: 141

Razor view Engine in Blazor (Convert blazor component to html string at run-time)

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.

Upvotes: 7

Views: 3742

Answers (3)

rdadkins
rdadkins

Reputation: 353

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!

Upvotes: 0

Peter Morris
Peter Morris

Reputation: 23264

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).

Upvotes: 2

Uwe Hein
Uwe Hein

Reputation: 1

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();
    }

Upvotes: 0

Related Questions