diff --git a/README.md b/README.md
index a8fbeb3..10b8ae8 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,39 @@ Extremely easy way to create Pdf files from ASP.NET Core
New Features
------
+### 4.0.0
+
+* Property `SaveOnServerPath` is discontinued.
+* Support new delegate. `TryCustomizeAsync()`
+ * If you want to customize the generated binary file before `OnBuildFileSuccess()`, use this.
+ * Please return true to continue processing, false to drop with error.
+
+```csharp
+ public ActionResult TestInlie()
+ {
+ return new ActionAsPdf("Index")
+ {
+ ContentDisposition = ContentDisposition.Inline,
+ TryCustomizeAsync = async (stream, context, fineName) =>
+ {
+ // some code done.
+ return true;
+
+ // e.g.
+ var customizeStream = new MemoryStream();
+ await stream.CopyToAsync(customizeStream);
+
+ // ...
+ stream.SetLength(0);
+ await customizeStream.CopyToAsync(stream);
+
+ return true;
+ },
+ };
+ }
+```
+
+
### 3.0.0
* Support ASP.NET Core 3.0. Thank you [@vertonghenb](https://github.com/vertonghenb)
diff --git a/RotativaCore.sln b/RotativaCore.sln
index 441e122..3bce3f1 100644
--- a/RotativaCore.sln
+++ b/RotativaCore.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28803.202
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32407.343
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{053EF92D-D564-4D29-8554-9422F330640C}"
EndProject
@@ -17,6 +17,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RotativaCore.UnitTests", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RotativaCore.SampleWebApp.3.0", "src\RotativaCore.SampleWebApp.3.0\RotativaCore.SampleWebApp.3.0.csproj", "{1AAF8B2C-F313-46FB-87C6-7B6DF53A4727}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{86815082-22AE-43B1-8816-187A9BC49B2C}"
+ ProjectSection(SolutionItems) = preProject
+ .gitignore = .gitignore
+ LICENSE = LICENSE
+ README.md = README.md
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/src/RotativaCore.SampleWebApp.3.0/Controllers/HomeController.cs b/src/RotativaCore.SampleWebApp.3.0/Controllers/HomeController.cs
index 62961d6..5e9636b 100644
--- a/src/RotativaCore.SampleWebApp.3.0/Controllers/HomeController.cs
+++ b/src/RotativaCore.SampleWebApp.3.0/Controllers/HomeController.cs
@@ -1,249 +1,265 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Routing;
-using Microsoft.WindowsAzure.Storage;
-using RotativaCore.Options;
-using RotativaCore.SampleWebApp._3._0.Models;
-
-namespace RotativaCore.SampleWebApp._3._0.Controllers
-{
- public class HomeController : Controller
- {
- public IActionResult Index()
- {
- return View();
- }
-
- public IActionResult Privacy()
- {
- return View();
- }
-
- [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
- public IActionResult Error()
- {
- return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
- }
-
-
- public IActionResult TestInlie()
- {
- return new ActionAsPdf("Index")
- {
- //FileName = "Test.pdf",
- ContentDisposition = ContentDisposition.Inline,
- OnBuildFileSuccess = async (bytes, context, fileName) =>
- {
- // some code done.
- return true;
-
- // example.
- if (string.IsNullOrEmpty(fileName))
- fileName = $"{Guid.NewGuid()}.pdf";
-
- var container = CloudStorageAccount
- .Parse(connectionString: null) // Please set your value.If it's null, it will result in an ArgumentNullException().
- .CreateCloudBlobClient()
- .GetContainerReference(containerName: null); // Please set your value.If it's null, it will result in an ArgumentNullException().
-
- try
- {
- var blockBlob = container.GetBlockBlobReference(fileName);
- blockBlob.Properties.ContentType = "application/pdf";
- await blockBlob.UploadFromByteArrayAsync(bytes, 0, bytes.Length);
- }
- catch (Exception e)
- {
- // logging.
- return false; // fire InvalidOperationException()
- }
-
- return true;
- },
- };
- }
-
-
- public ActionResult TestAttachmentWithA4LandscapeDisableSmartShrinkingViewPortSize1024()
- {
- return new ActionAsPdf("Index")
- {
- FileName = "TestWithA4LandscapeDisableSmartShrinkingViewPortSize1024.pdf",
- ContentDisposition = ContentDisposition.Attachment,
- PageSize = Size.A4,
- PageOrientation = Orientation.Landscape,
- DisableSmartShrinking = true,
- ViewportSize = 1024,
- };
- }
-
- public ActionResult TestImage()
- {
- return new ActionAsImage("Index")
- {
- FileName = "TestImage.jpg"
- };
- }
-
- public ActionResult TestImagePng()
- {
- return new ActionAsImage("Index")
- {
- FileName = "TestImagePng.png",
- Format = ImageFormat.png
- };
- }
-
- public ActionResult TestUrl()
- {
- // Now I realize that this isn't very expressive example of why this can be useful.
- // However imagine that you have your own UrlHelper extensions like UrlHelper.User(...)
- // where you create correct URL according to passed conditions, prepare some complex model, etc.
-
- var urlHelper = new UrlHelper(ControllerContext);
- var url = urlHelper.Action("Index", new { name = "Great Friends" });
-
- return new UrlAsPdf(url)
- {
- FileName = "TestUrl.pdf"
- };
- }
-
- public ActionResult TestExternalUrl()
- {
- // In some cases you might want to pull completely different URL that is not related to your application.
- // You can do that by specifying full URL.
-
- return new UrlAsPdf("http://www.github.com")
- {
- FileName = "TestExternalUrl.pdf",
- PageMargins = new Margins(0, 0, 0, 0)
- };
- }
-
- public ActionResult TestView()
- {
- // The more usual way of using this would be to have a Model object that you would pass into ViewAsPdf
- // and work with that Model inside your View.
- // Good example could be an Order Summary page on some fictional E-shop.
-
- // Probably the biggest advantage of this approach is that you have Session object available.
-
- ViewBag.Message = string.Format("Hello {0} to ASP.NET Core!", "Super Great Friends");
- return new ViewAsPdf("Index")
- {
- FileName = "TestView.pdf",
- PageSize = Size.A3,
- PageOrientation = Orientation.Landscape,
- PageMargins = { Left = 0, Right = 0 },
- ContentDisposition = ContentDisposition.Inline
- };
- }
-
- public ActionResult TestViewImage()
- {
- // The more usual way of using this would be to have a Model object that you would pass into ViewAsImage
- // and work with that Model inside your View.
- // Good example could be an Order Summary page on some fictional E-shop.
-
- // Probably the biggest advantage of this approach is that you have Session object available.
-
- ViewBag.Message = string.Format("Hello {0} to ASP.NET Core!", "Super Great Friends");
- return new ViewAsImage("Index")
- {
- FileName = "TestViewImage.png",
- };
- }
-
- public ActionResult TestViewWithModel(string id)
- {
- var model = new TestViewModel
- {
- DocTitle = id,
- DocContent = "This is a test"
- };
-
- return new ViewAsPdf("TestViewWithModel", model)
- {
- FileName = "TestViewWithModel.pdf"
- };
- }
-
- public ActionResult TestImageViewWithModel(string id)
- {
- var model = new TestViewModel
- {
- DocTitle = id,
- DocContent = "This is a test"
- };
-
- return new ViewAsImage("TestViewWithModel", model)
- {
- FileName = "TestImageViewWithModel.pdf"
- };
- }
-
- public ActionResult TestPartialViewWithModel(string id)
- {
- var model = new TestViewModel
- {
- DocTitle = id,
- DocContent = "This is a test with a partial view"
- };
-
- return new PartialViewAsPdf("TestPartialViewWithModel", model)
- {
- FileName = "TestPartialViewWithModel.pdf"
- };
- }
-
- public ActionResult TestImagePartialViewWithModel(string id)
- {
- var model = new TestViewModel
- {
- DocTitle = id,
- DocContent = "This is a test with a partial view"
- };
-
- return new PartialViewAsImage("TestPartialViewWithModel", model);
- }
-
- public ActionResult ErrorTest()
- {
- return new ActionAsPdf("SomethingBad")
- {
- FileName = "ErrorTest.pdf"
- };
- }
-
- public ActionResult SomethingBad()
- {
- return Redirect("http://thisdoesntexists");
- }
-
- public ActionResult RouteTest()
- {
- return new RouteAsPdf("TestRoute")
- {
- FileName = "RouteTest.pdf"
- };
- }
-
- [Obsolete]
- public ActionResult BinaryTest()
- {
- var pdfResult = new ActionAsPdf("Index")
- {
- FileName = "BinaryTest.pdf"
- };
-
- var binary = pdfResult.BuildPdf(this.ControllerContext);
-
- return File(binary, "application/pdf");
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Routing;
+using Microsoft.WindowsAzure.Storage;
+using RotativaCore.Options;
+using RotativaCore.SampleWebApp._3._0.Models;
+
+namespace RotativaCore.SampleWebApp._3._0.Controllers
+{
+ public class HomeController : Controller
+ {
+ public IActionResult Index()
+ {
+ return View();
+ }
+
+ public IActionResult Privacy()
+ {
+ return View();
+ }
+
+ [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
+ public IActionResult Error()
+ {
+ return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
+ }
+
+
+ public IActionResult TestInlie()
+ {
+ return new ActionAsPdf("Index")
+ {
+ //FileName = "Test.pdf",
+ ContentDisposition = ContentDisposition.Inline,
+ TryCustomizeAsync = async (stream, context, fineName) =>
+ {
+ // some code done.
+ return true;
+
+ // e.g.
+ var customizeStream = new MemoryStream();
+ await stream.CopyToAsync(customizeStream);
+
+ // ...
+ stream.SetLength(0);
+ await customizeStream.CopyToAsync(stream);
+
+ return true;
+ },
+ OnBuildFileSuccess = async (bytes, context, fileName) =>
+ {
+ // some code done.
+ return true;
+
+ // example.
+ if (string.IsNullOrEmpty(fileName))
+ fileName = $"{Guid.NewGuid()}.pdf";
+
+ var container = CloudStorageAccount
+ .Parse(connectionString: null) // Please set your value.If it's null, it will result in an ArgumentNullException().
+ .CreateCloudBlobClient()
+ .GetContainerReference(containerName: null); // Please set your value.If it's null, it will result in an ArgumentNullException().
+
+ try
+ {
+ var blockBlob = container.GetBlockBlobReference(fileName);
+ blockBlob.Properties.ContentType = "application/pdf";
+ await blockBlob.UploadFromByteArrayAsync(bytes, 0, bytes.Length);
+ }
+ catch (Exception e)
+ {
+ // logging.
+ return false; // fire InvalidOperationException()
+ }
+
+ return true;
+ },
+ };
+ }
+
+
+ public ActionResult TestAttachmentWithA4LandscapeDisableSmartShrinkingViewPortSize1024()
+ {
+ return new ActionAsPdf("Index")
+ {
+ FileName = "TestWithA4LandscapeDisableSmartShrinkingViewPortSize1024.pdf",
+ ContentDisposition = ContentDisposition.Attachment,
+ PageSize = Size.A4,
+ PageOrientation = Orientation.Landscape,
+ DisableSmartShrinking = true,
+ ViewportSize = 1024,
+ };
+ }
+
+ public ActionResult TestImage()
+ {
+ return new ActionAsImage("Index")
+ {
+ FileName = "TestImage.jpg"
+ };
+ }
+
+ public ActionResult TestImagePng()
+ {
+ return new ActionAsImage("Index")
+ {
+ FileName = "TestImagePng.png",
+ Format = ImageFormat.png
+ };
+ }
+
+ public ActionResult TestUrl()
+ {
+ // Now I realize that this isn't very expressive example of why this can be useful.
+ // However imagine that you have your own UrlHelper extensions like UrlHelper.User(...)
+ // where you create correct URL according to passed conditions, prepare some complex model, etc.
+
+ var urlHelper = new UrlHelper(ControllerContext);
+ var url = urlHelper.Action("Index", new { name = "Great Friends" });
+
+ return new UrlAsPdf(url)
+ {
+ FileName = "TestUrl.pdf"
+ };
+ }
+
+ public ActionResult TestExternalUrl()
+ {
+ // In some cases you might want to pull completely different URL that is not related to your application.
+ // You can do that by specifying full URL.
+
+ return new UrlAsPdf("http://www.github.com")
+ {
+ FileName = "TestExternalUrl.pdf",
+ PageMargins = new Margins(0, 0, 0, 0)
+ };
+ }
+
+ public ActionResult TestView()
+ {
+ // The more usual way of using this would be to have a Model object that you would pass into ViewAsPdf
+ // and work with that Model inside your View.
+ // Good example could be an Order Summary page on some fictional E-shop.
+
+ // Probably the biggest advantage of this approach is that you have Session object available.
+
+ ViewBag.Message = string.Format("Hello {0} to ASP.NET Core!", "Super Great Friends");
+ return new ViewAsPdf("Index")
+ {
+ FileName = "TestView.pdf",
+ PageSize = Size.A3,
+ PageOrientation = Orientation.Landscape,
+ PageMargins = { Left = 0, Right = 0 },
+ ContentDisposition = ContentDisposition.Inline
+ };
+ }
+
+ public ActionResult TestViewImage()
+ {
+ // The more usual way of using this would be to have a Model object that you would pass into ViewAsImage
+ // and work with that Model inside your View.
+ // Good example could be an Order Summary page on some fictional E-shop.
+
+ // Probably the biggest advantage of this approach is that you have Session object available.
+
+ ViewBag.Message = string.Format("Hello {0} to ASP.NET Core!", "Super Great Friends");
+ return new ViewAsImage("Index")
+ {
+ FileName = "TestViewImage.png",
+ };
+ }
+
+ public ActionResult TestViewWithModel(string id)
+ {
+ var model = new TestViewModel
+ {
+ DocTitle = id,
+ DocContent = "This is a test"
+ };
+
+ return new ViewAsPdf("TestViewWithModel", model)
+ {
+ FileName = "TestViewWithModel.pdf"
+ };
+ }
+
+ public ActionResult TestImageViewWithModel(string id)
+ {
+ var model = new TestViewModel
+ {
+ DocTitle = id,
+ DocContent = "This is a test"
+ };
+
+ return new ViewAsImage("TestViewWithModel", model)
+ {
+ FileName = "TestImageViewWithModel.pdf"
+ };
+ }
+
+ public ActionResult TestPartialViewWithModel(string id)
+ {
+ var model = new TestViewModel
+ {
+ DocTitle = id,
+ DocContent = "This is a test with a partial view"
+ };
+
+ return new PartialViewAsPdf("TestPartialViewWithModel", model)
+ {
+ FileName = "TestPartialViewWithModel.pdf"
+ };
+ }
+
+ public ActionResult TestImagePartialViewWithModel(string id)
+ {
+ var model = new TestViewModel
+ {
+ DocTitle = id,
+ DocContent = "This is a test with a partial view"
+ };
+
+ return new PartialViewAsImage("TestPartialViewWithModel", model);
+ }
+
+ public ActionResult ErrorTest()
+ {
+ return new ActionAsPdf("SomethingBad")
+ {
+ FileName = "ErrorTest.pdf"
+ };
+ }
+
+ public ActionResult SomethingBad()
+ {
+ return Redirect("http://thisdoesntexists");
+ }
+
+ public ActionResult RouteTest()
+ {
+ return new RouteAsPdf("TestRoute")
+ {
+ FileName = "RouteTest.pdf"
+ };
+ }
+
+ [Obsolete]
+ public ActionResult BinaryTest()
+ {
+ var pdfResult = new ActionAsPdf("Index")
+ {
+ FileName = "BinaryTest.pdf"
+ };
+
+ var binary = pdfResult.BuildPdf(this.ControllerContext);
+
+ return File(binary, "application/pdf");
+ }
+ }
+}
diff --git a/src/RotativaCore/AsResultBase.cs b/src/RotativaCore/AsResultBase.cs
index 99c90fc..0ce759f 100644
--- a/src/RotativaCore/AsResultBase.cs
+++ b/src/RotativaCore/AsResultBase.cs
@@ -116,21 +116,28 @@ public string CookieName
public string CustomSwitches { get; set; }
- [Obsolete(@"Use BuildFile(ActionContext) method instead and use the resulting binary data to do what needed.")]
- public string SaveOnServerPath { get; set; }
-
///
/// Set 'Content-Disposition' Response Header. If you set 'FileName', this value is ignored and "attachment" is forced.
///
public ContentDisposition ContentDisposition { get; set; }
+
+#pragma warning disable CS1998
+ ///
+ /// If you want to customize the generated binary file before `OnBuildFileSuccess()`, use this.
+ /// Please return true to continue processing, false to drop with error.
+ ///
+ public Func> TryCustomizeAsync { get; set; } = async (stream, applicationContext, fileName) => true;
+#pragma warning restore CS1998
+
+
#pragma warning disable CS1998
///
/// If you want to save the generated binary file to an external source such as Azure BLOB, please use this.
/// Please return true to continue processing, false to drop with error.
///
- public Func> OnBuildFileSuccess { get; set; } = async (byteArray, applicationContext, fileName) => true;
+ public Func> OnBuildFileSuccess { get; set; } = async (stream, applicationContext, fileName) => true;
#pragma warning restore CS1998
@@ -221,25 +228,56 @@ public byte[] BuildFile(ActionContext context)
if (context == null)
throw new ArgumentNullException("context");
- if (WkhtmlPath == string.Empty)
- {
- var location = Assembly.GetEntryAssembly().Location;
- var directory = Path.GetDirectoryName(location);
- WkhtmlPath = Path.Combine(directory, "WkHtmlToPdf");
- }
+ SetupWkhtmlPath();
var fileContent = CallTheDriver(context);
- if (string.IsNullOrEmpty(SaveOnServerPath) == false)
- File.WriteAllBytes(SaveOnServerPath, fileContent);
+ var pdfStream = new MemoryStream();
+
+ pdfStream.Write(fileContent, 0, fileContent.Length);
+ if (!TryCustomizeAsync?.Invoke(pdfStream, context, FileName).Result ?? true)
+ throw new InvalidOperationException($"{nameof(TryCustomizeAsync)} returned false.");
- if (!OnBuildFileSuccess?.Invoke(fileContent, context, FileName).Result ?? true)
+ if (!OnBuildFileSuccess?.Invoke(pdfStream.ToArray(), context, FileName).Result ?? true)
throw new InvalidOperationException($"{nameof(OnBuildFileSuccess)} returned false.");
- return fileContent;
+ return pdfStream.ToArray();
}
+ public async Task BuildFileAsync(ActionContext context)
+ {
+ if (context == null)
+ throw new ArgumentNullException("context");
+
+ SetupWkhtmlPath();
+
+ var fileContent = CallTheDriver(context);
+
+ var pdfStream = new MemoryStream();
+
+ await pdfStream.WriteAsync(fileContent, 0, fileContent.Length);
+
+ if (TryCustomizeAsync != null && !await TryCustomizeAsync.Invoke(pdfStream, context, FileName))
+ throw new InvalidOperationException($"{nameof(TryCustomizeAsync)} returned false.");
+
+ if (OnBuildFileSuccess != null && !await OnBuildFileSuccess.Invoke(pdfStream.ToArray(), context, FileName))
+ throw new InvalidOperationException($"{nameof(OnBuildFileSuccess)} returned false.");
+
+ return pdfStream;
+ }
+
+ private void SetupWkhtmlPath()
+ {
+ if (WkhtmlPath == string.Empty)
+ {
+ var location = Assembly.GetEntryAssembly()?.Location;
+ var directory = Path.GetDirectoryName(location);
+ WkhtmlPath = Path.Combine(directory, "WkHtmlToPdf");
+ }
+ }
+
+
public override void ExecuteResult(ActionContext context)
{
var fileContent = BuildFile(context);
@@ -249,6 +287,18 @@ public override void ExecuteResult(ActionContext context)
response.Body.WriteAsync(fileContent, 0, fileContent.Length);
}
+ public override async Task ExecuteResultAsync(ActionContext context)
+ {
+ var fileContentStream = await BuildFileAsync(context);
+
+ var response = PrepareResponse(context.HttpContext.Response);
+
+ fileContentStream.Position = 0;
+
+ await fileContentStream.CopyToAsync(response.Body);
+ }
+
+
private static string SanitizeFileName(string name)
{
var invalidChars = Regex.Escape(new string(Path.GetInvalidPathChars()) + new string(Path.GetInvalidFileNameChars()));
diff --git a/src/RotativaCore/RotativaCore.csproj b/src/RotativaCore/RotativaCore.csproj
index 0f41a15..1c57344 100644
--- a/src/RotativaCore/RotativaCore.csproj
+++ b/src/RotativaCore/RotativaCore.csproj
@@ -19,15 +19,14 @@
false
false
- 3.0.0
+ 4.0.0
@arichika (arichika.taniguchi)
RotativaCore
Convert HTML to PDF by WkHtmlToPdf for ASP.NET Core on Azure Web Apps (Windows). Forked from Rotativa. Target for .NETStandard 2.0.
MIT
netcore;aspnetcore;pdf;wkhtmltopdf;
- This is the initial version of the port and "Preview Release". It is not fully tested.
-Support new event. OnBuildFileSuccess Func<T>. For example, use this event to save the PDF to BLOB.
+ This is beta software always. It is not fully tested.
https://github.com/arichika/RotativaCore
https://github.com/arichika/RotativaCore