From 0ba7e75e7afdf486dd83cf8f219473bf4486911c Mon Sep 17 00:00:00 2001 From: Meruzhan Hovhannisyan Date: Sun, 1 Mar 2020 20:15:23 +0400 Subject: [PATCH 1/2] Add Wkhtmltoimage support --- Rotativa.AspNetCore/AsImageResultBase.cs | 70 ++++++++++ Rotativa.AspNetCore/ViewAsImage.cs | 151 +++++++++++++++++++++ Rotativa.AspNetCore/WkhtmltoimageDriver.cs | 30 ++++ 3 files changed, 251 insertions(+) create mode 100644 Rotativa.AspNetCore/AsImageResultBase.cs create mode 100644 Rotativa.AspNetCore/ViewAsImage.cs create mode 100644 Rotativa.AspNetCore/WkhtmltoimageDriver.cs diff --git a/Rotativa.AspNetCore/AsImageResultBase.cs b/Rotativa.AspNetCore/AsImageResultBase.cs new file mode 100644 index 0000000..332f09c --- /dev/null +++ b/Rotativa.AspNetCore/AsImageResultBase.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Mvc; +using Rotativa.AspNetCore.Options; + +namespace Rotativa.AspNetCore +{ + public abstract class AsImageResultBase : AsResultBase + { + /// + /// Set height for cropping + /// + [OptionFlag("-f")] + public ImageFormat? Format { get; set; } + + /// + /// Output image quality (between 0 and 100) (default 94) + /// + [OptionFlag("--quality")] + public int? Quality { get; set; } + + /// + /// Set height for cropping + /// + [OptionFlag("--crop-h")] + public int? CropHeight { get; set; } + + /// + /// Set width for cropping + /// + [OptionFlag("--crop-w")] + public int? CropWidth { get; set; } + + /// + /// Set x coordinate for cropping + /// + [OptionFlag("--crop-x")] + public int? CropX { get; set; } + + /// + /// Set y coordinate for cropping + /// + [OptionFlag("--crop-y")] + public int? CropY { get; set; } + + /// + /// Sets the page width. + /// + /// Set screen width, note that this is used only as a guide line. + [OptionFlag("--width")] + public int? PageWidth { get; set; } + + /// + /// Sets the page height in mm. + /// + /// Has priority over but has to be also specified. + [OptionFlag("--height")] + public int? PageHeight { get; set; } + + protected override byte[] WkhtmlConvert(string switches) + { + return WkhtmltoimageDriver.Convert(this.WkhtmlPath, switches); + } + + protected override string GetContentType() + { + var imageFormat = this.Format ?? ImageFormat.jpeg; + + return $"image/{imageFormat}"; + } + } +} \ No newline at end of file diff --git a/Rotativa.AspNetCore/ViewAsImage.cs b/Rotativa.AspNetCore/ViewAsImage.cs new file mode 100644 index 0000000..8e00943 --- /dev/null +++ b/Rotativa.AspNetCore/ViewAsImage.cs @@ -0,0 +1,151 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Rotativa.AspNetCore +{ + public class ViewAsImage : AsImageResultBase + { + private string _viewName; + + public string ViewName + { + get => _viewName ?? string.Empty; + set => _viewName = value; + } + + private string _masterName; + + public string MasterName + { + get => _masterName ?? string.Empty; + set => _masterName = value; + } + + public object Model { get; set; } + + public ViewDataDictionary ViewData { get; set; } + + public ViewAsImage(ViewDataDictionary viewData = null) + { + this.WkhtmlPath = string.Empty; + MasterName = string.Empty; + ViewName = string.Empty; + Model = null; + ViewData = viewData; + } + + public ViewAsImage(string viewName, ViewDataDictionary viewData = null) + : this(viewData) + { + ViewName = viewName; + } + + public ViewAsImage(object model, ViewDataDictionary viewData = null) + : this(viewData) + { + Model = model; + } + + public ViewAsImage(string viewName, object model, ViewDataDictionary viewData = null) + : this(viewData) + { + ViewName = viewName; + Model = model; + } + + public ViewAsImage(string viewName, string masterName, object model) + : this(viewName, model) + { + MasterName = masterName; + } + + protected override string GetUrl(ActionContext context) + { + return string.Empty; + } + + protected virtual ViewEngineResult GetView(ActionContext context, string viewName, string masterName) + { + var engine = context.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine; + var getViewResult = engine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true); + if (getViewResult.Success) + { + return getViewResult; + } + + var findViewResult = engine.FindView(context, viewName, isMainPage: true); + if (findViewResult.Success) + { + return findViewResult; + } + + var searchedLocations = getViewResult.SearchedLocations.Concat(findViewResult.SearchedLocations); + var errorMessage = string.Join( + Environment.NewLine, + new[] { $"Unable to find view '{viewName}'. The following locations were searched:" }.Concat(searchedLocations)); + + throw new InvalidOperationException(errorMessage); + } + + protected override async Task CallTheDriver(ActionContext context) + { + // use action name if the view name was not provided + string viewName = ViewName; + if (string.IsNullOrEmpty(ViewName)) + { + viewName = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor).ActionName; + } + + ViewEngineResult viewResult = GetView(context, viewName, MasterName); + var html = new StringBuilder(); + + //string html = context.GetHtmlFromView(viewResult, viewName, Model); + ITempDataProvider tempDataProvider = context.HttpContext.RequestServices.GetService(typeof(ITempDataProvider)) as ITempDataProvider; + + var viewDataDictionary = new ViewDataDictionary( + metadataProvider: new EmptyModelMetadataProvider(), + modelState: new ModelStateDictionary()) + { + Model = this.Model + }; + if (this.ViewData != null) + { + foreach (var item in this.ViewData) + { + viewDataDictionary.Add(item); + } + } + using (var output = new StringWriter()) + { + var view = viewResult.View; + var tempDataDictionary = new TempDataDictionary(context.HttpContext, tempDataProvider); + var viewContext = new ViewContext( + context, + viewResult.View, + viewDataDictionary, + tempDataDictionary, + output, + new HtmlHelperOptions()); + + await view.RenderAsync(viewContext); + + html = output.GetStringBuilder(); + } + + string baseUrl = $"{context.HttpContext.Request.Scheme}://{context.HttpContext.Request.Host}"; + var htmlForWkhtml = Regex.Replace(html.ToString(), "", $"", RegexOptions.IgnoreCase); + + byte[] fileContent = WkhtmltoimageDriver.ConvertHtml(this.WkhtmlPath, this.GetConvertOptions(), htmlForWkhtml); + return fileContent; + } + } +} \ No newline at end of file diff --git a/Rotativa.AspNetCore/WkhtmltoimageDriver.cs b/Rotativa.AspNetCore/WkhtmltoimageDriver.cs new file mode 100644 index 0000000..409a0d7 --- /dev/null +++ b/Rotativa.AspNetCore/WkhtmltoimageDriver.cs @@ -0,0 +1,30 @@ +namespace Rotativa.AspNetCore +{ + public class WkhtmltoimageDriver : WkhtmlDriver + { + private const string wkhtmlExe = "wkhtmltoimage.exe"; + + /// + /// Converts given HTML string to Image. + /// + /// Path to wkthmltoimage. + /// Switches that will be passed to wkhtmltoimage binary. + /// String containing HTML code that should be converted to Image. + /// PDF as byte array. + public static byte[] ConvertHtml(string wkhtmltoimagePath, string switches, string html) + { + return Convert(wkhtmltoimagePath, switches, html, wkhtmlExe); + } + + /// + /// Converts given URL to Image. + /// + /// Path to wkthmltoimage. + /// Switches that will be passed to wkhtmltoimage binary. + /// PDF as byte array. + public static byte[] Convert(string wkhtmltoimagePath, string switches) + { + return Convert(wkhtmltoimagePath, switches, null, wkhtmlExe); + } + } +} \ No newline at end of file From b0a0752275e8d53f467271bb018f653a81629abe Mon Sep 17 00:00:00 2001 From: Meruzhan Hovhannisyan Date: Sun, 1 Mar 2020 21:29:59 +0400 Subject: [PATCH 2/2] fixed the need to rename binaries for Linux or macOS --- Rotativa.AspNetCore/WkhtmltoimageDriver.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Rotativa.AspNetCore/WkhtmltoimageDriver.cs b/Rotativa.AspNetCore/WkhtmltoimageDriver.cs index 409a0d7..e48c737 100644 --- a/Rotativa.AspNetCore/WkhtmltoimageDriver.cs +++ b/Rotativa.AspNetCore/WkhtmltoimageDriver.cs @@ -1,8 +1,14 @@ +using System.Runtime.InteropServices; + namespace Rotativa.AspNetCore { public class WkhtmltoimageDriver : WkhtmlDriver { - private const string wkhtmlExe = "wkhtmltoimage.exe"; + /// + /// wkhtmltoimage only has a .exe extension in Windows. + /// + private static readonly string wkhtmlExe = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "wkhtmltoimage.exe" : "wkhtmltoimage"; /// /// Converts given HTML string to Image.