Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions Rotativa.AspNetCore/AsImageResultBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Microsoft.AspNetCore.Mvc;
using Rotativa.AspNetCore.Options;

namespace Rotativa.AspNetCore
{
public abstract class AsImageResultBase : AsResultBase
{
/// <summary>
/// Set height for cropping
/// </summary>
[OptionFlag("-f")]
public ImageFormat? Format { get; set; }

/// <summary>
/// Output image quality (between 0 and 100) (default 94)
/// </summary>
[OptionFlag("--quality")]
public int? Quality { get; set; }

/// <summary>
/// Set height for cropping
/// </summary>
[OptionFlag("--crop-h")]
public int? CropHeight { get; set; }

/// <summary>
/// Set width for cropping
/// </summary>
[OptionFlag("--crop-w")]
public int? CropWidth { get; set; }

/// <summary>
/// Set x coordinate for cropping
/// </summary>
[OptionFlag("--crop-x")]
public int? CropX { get; set; }

/// <summary>
/// Set y coordinate for cropping
/// </summary>
[OptionFlag("--crop-y")]
public int? CropY { get; set; }

/// <summary>
/// Sets the page width.
/// </summary>
/// <remarks>Set screen width, note that this is used only as a guide line.</remarks>
[OptionFlag("--width")]
public int? PageWidth { get; set; }

/// <summary>
/// Sets the page height in mm.
/// </summary>
/// <remarks>Has priority over <see cref="PageSize"/> but <see cref="PageWidth"/> has to be also specified.</remarks>
[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}";
}
}
}
151 changes: 151 additions & 0 deletions Rotativa.AspNetCore/ViewAsImage.cs
Original file line number Diff line number Diff line change
@@ -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<byte[]> 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(), "<head>", $"<head><base href=\"{baseUrl}\" />", RegexOptions.IgnoreCase);

byte[] fileContent = WkhtmltoimageDriver.ConvertHtml(this.WkhtmlPath, this.GetConvertOptions(), htmlForWkhtml);
return fileContent;
}
}
}
36 changes: 36 additions & 0 deletions Rotativa.AspNetCore/WkhtmltoimageDriver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Runtime.InteropServices;

namespace Rotativa.AspNetCore
{
public class WkhtmltoimageDriver : WkhtmlDriver
{
/// <summary>
/// wkhtmltoimage only has a .exe extension in Windows.
/// </summary>
private static readonly string wkhtmlExe =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "wkhtmltoimage.exe" : "wkhtmltoimage";

/// <summary>
/// Converts given HTML string to Image.
/// </summary>
/// <param name="wkhtmltoimagePath">Path to wkthmltoimage.</param>
/// <param name="switches">Switches that will be passed to wkhtmltoimage binary.</param>
/// <param name="html">String containing HTML code that should be converted to Image.</param>
/// <returns>PDF as byte array.</returns>
public static byte[] ConvertHtml(string wkhtmltoimagePath, string switches, string html)
{
return Convert(wkhtmltoimagePath, switches, html, wkhtmlExe);
}

/// <summary>
/// Converts given URL to Image.
/// </summary>
/// <param name="wkhtmltoimagePath">Path to wkthmltoimage.</param>
/// <param name="switches">Switches that will be passed to wkhtmltoimage binary.</param>
/// <returns>PDF as byte array.</returns>
public static byte[] Convert(string wkhtmltoimagePath, string switches)
{
return Convert(wkhtmltoimagePath, switches, null, wkhtmlExe);
}
}
}