problem showing PDF in Blazor page from byte array - pdf

I have gone through all the suggestions for how to take a byte array stored in SQL Server db as varbinary and display it as PDF in a Blazor website. I'm successful in ASP.Net with the aspx pages and code behind but I can't seem to find the right combination for Blazor (ShowPDF.razor and code behind ShowPDF.razor.cs)
Here is what I have as variants in the code behind:
The aReport.ReportDocument is returning a byte array of aReport.DocumentSize from the DB
FileStreamResult GetPDF()
{
var pdfStream = new System.IO.MemoryStream();
this.rdb = new ReportData();
aReport = rdb.GetWithReport(1);
pdfStream.Write(aReport.ReportDocument, 0, aReport.DocumentSize);
pdfStream.Position = 0;
return new FileStreamResult(pdfStream, new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/pdf"));
}
OR 2. direct binary array to base 64 encoding:
return Convert.ToBase64String(aReport.ReportDocument);
While those two processes return the data, I'm unable to find how to set up the razor page to show the result. I have tried:
<object src="#Url.Action("GetPDF")"/>
and other variants without success.
Thanks!

Ok, I finally found the resolution for this.
The ShowPDF.razor.cs code behind page is:
public partial class ShowPDF: ComponentBase
{
private IReportData rdb; // the database
private ReportModel aReport; // report model
/*
aReport.ReportDocument holds the byte[]
*/
string GetPDF(int ReportId)
{
this.rdb = new ReportData();
aReport = rdb.GetWithReport(ReportId);
return "data:application/pdf;base64," + Convert.ToBase64String(aReport.ReportDocument);
}
}
and the ShowPDF.razor page is:
#page "/ShowPDF"
#page "/ShowPDF/{Report:int}"
#code {
[Parameter]
public int Report { get; set; }
}
<embed src="#GetPDF(Report)" visible="false" width="1500" height="2000" />

I'm afraid this solution is not optimal for medium size or large PDF files. Nobody sets systematically image source as base64 string. It should be the same for PDF files. Browsers will appreciate downloading PDF in a separate thread not in the HTML code rendering.
In Blazor, this can be easy achieved using a custom middleware.
namespace MyMiddlewares
{
public class ShowPdf
{
public ShowPdf(RequestDelegate next)
{
//_next = next; no call to _next.Invoke(context) because the handler is at the end of the request pipeline, so there will be no next middleware to invoke.
}
public async Task Invoke(HttpContext context)
{
byte[] pdfBytes = getPdfFromDb(context.Request.Query["pdfid"]);
context.Response.ContentType = "application/pdf";
context.Response.Headers.Add("Content-Disposition",
"attachment; " +
"filename=\"mypdf.pdf\"; " +
"size=" + pdfBytes.Length + "; " +
"creation-date=" + DateTime.Now.ToString("R").Replace(",", "") + "; " +
"modification-date=" + DateTime.Now.ToString("R").Replace(",", "") + "; " +
"read-date=" + DateTime.Now.ToString("R").Replace(",", ""));
await context.Response.Body.WriteAsync(pdfBytes);
}
}
public static class ShowPdfExtensions
{
public static IApplicationBuilder UseShowPdf(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ShowPdf>();
}
}
}
In the Configure method of Startup.cs, you add (before app.UseStaticFiles();)
app.MapWhen(
context => context.Request.Path.ToString().Contains("ShowPdf.mdwr", StringComparison.InvariantCultureIgnoreCase),
appBranch => {
appBranch.UseShowPdf();
});
So, this URL will download a PDF file: /ShowPdf.mdwr?pdfid=idOfMyPdf
If embedding is required, this URL may be used in a PDF viewer.

Related

How do I use IViewLocationExtender with Razor Pages to render device specific pages

Currently we are building a web application, desktop first, that needs device specific Razor Pages for specific pages. Those pages are really different from their Desktop version and it makes no sense to use responsiveness here.
We have tried to implement our own IViewLocationExpander and also tried to use the MvcDeviceDetector library (which is basically doing the same). Detection of the device type is no problem but for some reason the device specific page is not picked up and it is constantly falling back to the default Index.cshtml.
(edit: We're thinking about implementing something based on IPageConvention, IPageApplicationModelProvider or something ... ;-))
Index.mobile.cshtml
Index.cshtml
We have added the following code using the example of MvcDeviceDetector:
public static IMvcBuilder AddDeviceDetection(this IMvcBuilder builder)
{
builder.Services.AddDeviceSwitcher<UrlSwitcher>(
o => { },
d => {
d.Format = DeviceLocationExpanderFormat.Suffix;
d.MobileCode = "mobile";
d.TabletCode = "tablet";
}
);
return builder;
}
and are adding some route mapping
routes.MapDeviceSwitcher();
We expected to see Index.mobile.cshtml to be picked up when selecting a Phone Emulation in Chrome but that didnt happen.
edit Note:
we're using a combination of Razor Views/MVC (older sections) and Razor Pages (newer sections).
also not every page will have a mobile implementation. That's what would have a IViewLocationExpander solution so great.
edit 2
I think the solution would be the same as how you'd implement Culture specific Razor Pages (which is also unknown to us ;-)). Basic MVC supports Index.en-US.cshtml
Final Solution Below
If this is a Razor Pages application (as opposed to an MVC application) I don't think that the IViewLocationExpander interface is much use to you. As far as I know, it only works for partials, not routeable pages (i.e. those with an #page directive).
What you can do instead is to use Middleware to determine whether the request comes from a mobile device, and then change the file to be executed to one that ends with .mobile. Here's a very rough and ready implementation:
public class MobileDetectionMiddleware
{
private readonly RequestDelegate _next;
public async Task Invoke(HttpContext context)
{
if(context.Request.IsFromAMobileDevice())
{
context.Request.Path = $"{context.Request.Path}.mobile";
}
await _next.Invoke(context);
}
}
It's up to you how you want to implement the IsFromAMobileDevice method to determine the nature of the user agent. There's nothing stopping you using a third party library that can do the check reliably for you. Also, you will probably only want to change the path under certain conditions - such as where there is a device specific version of the requested page.
Register this in your Configure method early:
app.UseMiddleware<MobileDetectionMiddleware>();
I've finally found the way to do it convention based. I have implemented a IViewLocationExpander in order to tackle the device handling for basic Razor Views (including Layouts) and I've implemented IPageRouteModelConvention + IActionConstraint to handle devices for Razor Pages.
Note: this solution only seems to be working on ASP.NET Core 2.2 and up though. For some reason 2.1.x and below is clearing the constraints (tested with a breakpoint in a destructor) after they've been added (can probably be fixed).
Now I can have /Index.mobile.cshtml /Index.desktop.cshtml etc. in both MVC and Razor Pages.
Note: This solution can also be used to implement a language/culture specific Razor Pages (eg. /Index.en-US.cshtml /Index.nl-NL.cshtml)
public class PageDeviceConvention : IPageRouteModelConvention
{
private readonly IDeviceResolver _deviceResolver;
public PageDeviceConvention(IDeviceResolver deviceResolver)
{
_deviceResolver = deviceResolver;
}
public void Apply(PageRouteModel model)
{
var path = model.ViewEnginePath; // contains /Index.mobile
var lastSeparator = path.LastIndexOf('/');
var lastDot = path.LastIndexOf('.', path.Length - 1, path.Length - lastSeparator);
if (lastDot != -1)
{
var name = path.Substring(lastDot + 1);
if (Enum.TryParse<DeviceType>(name, true, out var deviceType))
{
var constraint = new DeviceConstraint(deviceType, _deviceResolver);
for (var i = model.Selectors.Count - 1; i >= 0; --i)
{
var selector = model.Selectors[i];
selector.ActionConstraints.Add(constraint);
var template = selector.AttributeRouteModel.Template;
var tplLastSeparator = template.LastIndexOf('/');
var tplLastDot = template.LastIndexOf('.', template.Length - 1, template.Length - Math.Max(tplLastSeparator, 0));
template = template.Substring(0, tplLastDot); // eg Index.mobile -> Index
selector.AttributeRouteModel.Template = template;
var fileName = template.Substring(tplLastSeparator + 1);
if ("Index".Equals(fileName, StringComparison.OrdinalIgnoreCase))
{
selector.AttributeRouteModel.SuppressLinkGeneration = true;
template = selector.AttributeRouteModel.Template.Substring(0, Math.Max(tplLastSeparator, 0));
model.Selectors.Add(new SelectorModel(selector) { AttributeRouteModel = { Template = template } });
}
}
}
}
}
protected class DeviceConstraint : IActionConstraint
{
private readonly DeviceType _deviceType;
private readonly IDeviceResolver _deviceResolver;
public DeviceConstraint(DeviceType deviceType, IDeviceResolver deviceResolver)
{
_deviceType = deviceType;
_deviceResolver = deviceResolver;
}
public int Order => 0;
public bool Accept(ActionConstraintContext context)
{
return _deviceResolver.GetDeviceType() == _deviceType;
}
}
}
public class DeviceViewLocationExpander : IViewLocationExpander
{
private readonly IDeviceResolver _deviceResolver;
private const string ValueKey = "DeviceType";
public DeviceViewLocationExpander(IDeviceResolver deviceResolver)
{
_deviceResolver = deviceResolver;
}
public void PopulateValues(ViewLocationExpanderContext context)
{
var deviceType = _deviceResolver.GetDeviceType();
if (deviceType != DeviceType.Other)
context.Values[ValueKey] = deviceType.ToString();
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
var deviceType = context.Values[ValueKey];
if (!string.IsNullOrEmpty(deviceType))
{
return ExpandHierarchy();
}
return viewLocations;
IEnumerable<string> ExpandHierarchy()
{
var replacement = $"{{0}}.{deviceType}";
foreach (var location in viewLocations)
{
if (location.Contains("{0}"))
yield return location.Replace("{0}", replacement);
yield return location;
}
}
}
}
public interface IDeviceResolver
{
DeviceType GetDeviceType();
}
public class DefaultDeviceResolver : IDeviceResolver
{
public DeviceType GetDeviceType() => DeviceType.Mobile;
}
public enum DeviceType
{
Other,
Mobile,
Tablet,
Normal
}
Startup
services.AddMvc(o => { })
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorOptions(o =>
{
o.ViewLocationExpanders.Add(new DeviceViewLocationExpander(new DefaultDeviceResolver()));
})
.AddRazorPagesOptions(o =>
{
o.Conventions.Add(new PageDeviceConvention(new DefaultDeviceResolver()));
});

How to make my firebase dynamic link redirect to my website on desktop and to my instant app on mobile

I have an instant app and a Firebase dynamic link which redirects to this instant app.
But when I click the dynamic link on a computer, the link leads to a non existant page of my website.
According to Google doc : https://firebase.google.com/docs/dynamic-links/android/create
When users open a Dynamic Link on a desktop web browser, they will load this URL (unless the ofl parameter is specified). If you don't have a web equivalent to the linked content, the URL doesn't need to point to a valid web resource. In this situation, you should set up a redirect from this URL to, for example, your home page.
So I created a redirection for my dynamic link which redirects
/share/** to /
And it works, when I click the link on a computer I land on the homepage of my website.
But my Dynamic links also leads on my homepage and do not open my instant app anymore.
So my question is : how to configure a redirection which redirects desktop users from /share/** to / without breaking my instant app ?
#Simon,
it is possible to achieve it without manual link construction, just use the same builder
private static final String OTHER_PLATFORM_LINK_KEY = "ofl";
public static Task<ShortDynamicLink> createShortDynamicLink(Uri deepLink, Uri imageUrl, String title, String description) {
DynamicLink dynamicLink = createDynamicLink(deepLink, imageUrl, title, description);
return FirebaseDynamicLinks.getInstance().createDynamicLink()
.setLongLink(dynamicLink.getUri())
.buildShortDynamicLink();
}
public static DynamicLink createDynamicLink(Uri deepLink, Uri imageUrl, String title, String description) {
DynamicLink dynamicLink = getDynamicLinkBuilder(deepLink, imageUrl, title, description)
.buildDynamicLink();
String longDynamicLink = String.valueOf(dynamicLink.getUri());
longDynamicLink += '&' + OTHER_PLATFORM_LINK_KEY + '=' + DomainConstants.OTHER_PLATFORM_LINK;
return FirebaseDynamicLinks.getInstance().createDynamicLink()
.setLongLink(Uri.parse(longDynamicLink))
.buildDynamicLink();
}
public static DynamicLink.Builder getDynamicLinkBuilder(Uri deepLink, Uri imageUrl, String title, String description) {
return FirebaseDynamicLinks.getInstance().createDynamicLink()
.setLink(deepLink)
.setDomainUriPrefix(DomainConstants.DYNAMIC_LINK_DOMAIN_URI_PREFIX)
.setAndroidParameters(new DynamicLink.AndroidParameters.Builder().build())
.setIosParameters(new DynamicLink.IosParameters.Builder(DomainConstants.IOS_BUNDLE_ID)
.setAppStoreId(DomainConstants.APP_STORE_ID)
.setMinimumVersion(DomainConstants.IOS_MINIMUM_VERSION)
.build())
.setSocialMetaTagParameters(
new DynamicLink.SocialMetaTagParameters.Builder()
.setTitle(title)
.setDescription(description)
.setImageUrl(imageUrl)
.build())
.setNavigationInfoParameters(new DynamicLink.NavigationInfoParameters.Builder()
.setForcedRedirectEnabled(true)
.build());
}
Add the "ofl" parameter to the url to make the FDL redirects another URL on desktop.
Unfortunately, it is not possible to add this parameter with the Android builder.
So you have to manually create the link, and use the "setLongLink" method as you can see here at the bottom of the page : https://firebase.google.com/docs/dynamic-links/android/create#shorten-a-long-dynamic-link
Task<ShortDynamicLink> shortLinkTask = FirebaseDynamicLinks.getInstance().createDynamicLink()
.setLongLink(Uri.parse("https://example.page.link/?link=https://www.example.com/&apn=com.example.android&ibn=com.example.ios"))
.buildShortDynamicLink()
.addOnCompleteListener(this, new OnCompleteListener<ShortDynamicLink>() {
#Override
public void onComplete(#NonNull Task<ShortDynamicLink> task) {
if (task.isSuccessful()) {
// Short link created
Uri shortLink = task.getResult().getShortLink();
Uri flowchartLink = task.getResult().getPreviewLink();
} else {
// Error
// ...
}
}
});
I created my own builder to include the ofl parameter. If it can helps :
public class DynamicLinkBuilder {
private String dynamicLink = "https://example.com/foo"
public DynamicLinkBuilder(String link) {
this.dynamicLink += "?link=" + link;
}
public DynamicLinkBuilder addMinVersion(int minVersion){
dynamicLink += "&amv=" + minVersion;
return this;
}
public DynamicLinkBuilder addIosUrl(String iosUrl){
dynamicLink += "&ifl=" + iosUrl;
return this;
}
public DynamicLinkBuilder addDesktopUrl(String desktopUrl){
dynamicLink += "&ofl=" + desktopUrl;
return this;
}
public DynamicLinkBuilder addFallbackUrl(String fallbackUrl){
dynamicLink += "&afl=" + fallbackUrl;
return this;
}
public DynamicLinkBuilder addPackageName(String packageName){
dynamicLink += "&apn=" + packageName;
return this;
}
public DynamicLinkBuilder addSocialMediaLogo(String logoUrl){
dynamicLink += "&si=" + logoUrl;
return this;
}
public DynamicLinkBuilder addSocialMediaTitle(String title){
dynamicLink += "&st=" + Uri.encode(title);
return this;
}
public DynamicLinkBuilder addSocialMediaDescription(String description){
dynamicLink += "&sd=" + Uri.encode(description);
return this;
}
public String build(){
return dynamicLink;
}
}
I solved this in a similar way but with shorter code:
As other have mentioned, you must manually add an 'ofl' parameter to the link. My method was:
// Grab link from Firebase builder
guard var longDynamicLink = shareLink.url else { return }
// Parse URL to string
var urlStr = longDynamicLink.absoluteString
// Append the ofl fallback (ofl param specifies a device other than ios or android)
urlStr = urlStr + "&ofl=https://www.meetmaro.com/"
// Convert back to a URL
var urlFinal = URL(string: urlStr)!
// Shorten the url & check for errors
DynamicLinkComponents.shortenURL(urlFinal, options: nil, completion:{ [weak self] url,warnings,error in
if let _ = error{
return
}
if let warnings = warnings{
for warning in warnings{
print("Shorten URL warnings: ", warning)
}
}
guard let shortUrl = url else {return}
// prompt the user with UIActivityViewController
self?.showShareSheet(url: shortUrl)
})
The final URL can then be used to present the shareable panel with another function like:
self.showShareSheet(url: finalUrl) which triggers the UIActivityViewController
Credit to http://ostack.cn/?qa=168161/ for the original idea
More about ofl: https://firebase.google.com/docs/dynamic-links/create-manually?authuser=3#general-params
Based on #Ivan Karpiuk answer:
Documentation:
ofl The link to open on platforms beside Android and iOS. This is useful to specify a different behavior on desktop, like displaying a full web page of the app content/payload (as specified by param link) with another dynamic link to install the app.
Add this DynamicLink.Builder extension:
private fun DynamicLink.Builder.otherPlatformParameters(): DynamicLink.Builder {
var longDynamicLink = this.buildDynamicLink().uri.toString()
longDynamicLink += "&ofl=" + YOUR_URL
longLink = Uri.parse(longDynamicLink)
return this
}
Replace YOUR_URL with yours and then:
FirebaseDynamicLinks.getInstance().createDynamicLink()
.setLink(...)
.setDomainUriPrefix(...)
.setAndroidParameters(...)
.setIosParameters(...)
.setSocialMetaTagParameters(...)
.otherPlatformParameters()
.buildShortDynamicLink()
So we create a long link via createDynamicLink(), add ofl with otherPlatformParameters() and then convert it to a short link buildShortDynamicLink()

How to replace / remove text from a PDF file

How would I go about replacing / removing text from a PDF file?
I have a PDF file that I obtained somewhere, and I want to be able to replace some text within it.
Or, I have a PDF file that I want to obscure (redact) some of the text within it so that it's no longer visible [and so that it looks cool, like the CIA files].
Or, I have a PDF that contains global Javascript that I want to stop from interrupting my use of the PDF.
This is possible in a limited fashion with the use of iText / iTextSharp.
It will only work with Tj/TJ opcodes (i.e. standard text, not text embedded in images, or drawn with shapes).
You need to override the default PdfContentStreamProcessor to act on the page content streams, as presented by Mkl here Removing Watermark from PDF iTextSharp. Inherit from this class, and in your new class look for the Tj/TJ opcodes, the operand(s) will generally be the text element(s) (for a TJ this may not be straightforward text, and may require further parsing of all the operands).
A pretty basic example of some of the flexibility around iTextSharp is available from this github repository https://github.com/bevanweiss/PdfEditor (code excerpts below also)
NOTE: This utilises the AGPL version of iTextSharp (and is hence also AGPL), so if you will be distributing executables derived from this code or allowing others to interact with those executables in any way then you must also provide your modified source code. There is also no warranty, implied or expressed, related to this code. Use at your own peril.
PdfContentStreamEditor
using System.Collections.Generic;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
namespace PDFCleaner
{
public class PdfContentStreamEditor : PdfContentStreamProcessor
{
/**
* This method edits the immediate contents of a page, i.e. its content stream.
* It explicitly does not descent into form xobjects, patterns, or annotations.
*/
public void EditPage(PdfStamper pdfStamper, int pageNum)
{
var pdfReader = pdfStamper.Reader;
var page = pdfReader.GetPageN(pageNum);
var pageContentInput = ContentByteUtils.GetContentBytesForPage(pdfReader, pageNum);
page.Remove(PdfName.CONTENTS);
EditContent(pageContentInput, page.GetAsDict(PdfName.RESOURCES), pdfStamper.GetUnderContent(pageNum));
}
/**
* This method processes the content bytes and outputs to the given canvas.
* It explicitly does not descent into form xobjects, patterns, or annotations.
*/
public virtual void EditContent(byte[] contentBytes, PdfDictionary resources, PdfContentByte canvas)
{
this.Canvas = canvas;
ProcessContent(contentBytes, resources);
this.Canvas = null;
}
/**
* This method writes content stream operations to the target canvas. The default
* implementation writes them as they come, so it essentially generates identical
* copies of the original instructions the {#link ContentOperatorWrapper} instances
* forward to it.
*
* Override this method to achieve some fancy editing effect.
*/
protected virtual void Write(PdfContentStreamProcessor processor, PdfLiteral operatorLit, List<PdfObject> operands)
{
var index = 0;
foreach (var pdfObject in operands)
{
pdfObject.ToPdf(null, Canvas.InternalBuffer);
Canvas.InternalBuffer.Append(operands.Count > ++index ? (byte) ' ' : (byte) '\n');
}
}
//
// constructor giving the parent a dummy listener to talk to
//
public PdfContentStreamEditor() : base(new DummyRenderListener())
{
}
//
// constructor giving the parent a dummy listener to talk to
//
public PdfContentStreamEditor(IRenderListener renderListener) : base(renderListener)
{
}
//
// Overrides of PdfContentStreamProcessor methods
//
public override IContentOperator RegisterContentOperator(string operatorString, IContentOperator newOperator)
{
var wrapper = new ContentOperatorWrapper();
wrapper.SetOriginalOperator(newOperator);
var formerOperator = base.RegisterContentOperator(operatorString, wrapper);
return (formerOperator is ContentOperatorWrapper operatorWrapper ? operatorWrapper.GetOriginalOperator() : formerOperator);
}
public override void ProcessContent(byte[] contentBytes, PdfDictionary resources)
{
this.Resources = resources;
base.ProcessContent(contentBytes, resources);
this.Resources = null;
}
//
// members holding the output canvas and the resources
//
protected PdfContentByte Canvas = null;
protected PdfDictionary Resources = null;
//
// A content operator class to wrap all content operators to forward the invocation to the editor
//
class ContentOperatorWrapper : IContentOperator
{
public IContentOperator GetOriginalOperator()
{
return _originalOperator;
}
public void SetOriginalOperator(IContentOperator op)
{
this._originalOperator = op;
}
public void Invoke(PdfContentStreamProcessor processor, PdfLiteral oper, List<PdfObject> operands)
{
if (_originalOperator != null && !"Do".Equals(oper.ToString()))
{
_originalOperator.Invoke(processor, oper, operands);
}
((PdfContentStreamEditor)processor).Write(processor, oper, operands);
}
private IContentOperator _originalOperator = null;
}
//
// A dummy render listener to give to the underlying content stream processor to feed events to
//
class DummyRenderListener : IRenderListener
{
public void BeginTextBlock() { }
public void RenderText(TextRenderInfo renderInfo) { }
public void EndTextBlock() { }
public void RenderImage(ImageRenderInfo renderInfo) { }
}
}
}
TextReplaceStreamEditor
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
namespace PDFCleaner
{
public class TextReplaceStreamEditor : PdfContentStreamEditor
{
public TextReplaceStreamEditor(string MatchPattern, string ReplacePattern)
{
_matchPattern = MatchPattern;
_replacePattern = ReplacePattern;
}
private string _matchPattern;
private string _replacePattern;
protected override void Write(PdfContentStreamProcessor processor, PdfLiteral oper, List<PdfObject> operands)
{
var operatorString = oper.ToString();
if ("Tj".Equals(operatorString) || "TJ".Equals(operatorString))
{
for(var i = 0; i < operands.Count; i++)
{
if(!operands[i].IsString())
continue;
var text = operands[i].ToString();
if(Regex.IsMatch(text, _matchPattern))
{
operands[i] = new PdfString(Regex.Replace(text, _matchPattern, _replacePattern));
}
}
}
base.Write(processor, oper, operands);
}
}
}
TextRedactStreamEditor
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
namespace PDFCleaner
{
public class TextRedactStreamEditor : PdfContentStreamEditor
{
public TextRedactStreamEditor(string MatchPattern) : base(new RedactRenderListener(MatchPattern))
{
_matchPattern = MatchPattern;
}
private string _matchPattern;
protected override void Write(PdfContentStreamProcessor processor, PdfLiteral oper, List<PdfObject> operands)
{
base.Write(processor, oper, operands);
}
public override void EditContent(byte[] contentBytes, PdfDictionary resources, PdfContentByte canvas)
{
((RedactRenderListener)base.RenderListener).SetCanvas(canvas);
base.EditContent(contentBytes, resources, canvas);
}
}
//
// A pretty simple render listener, all we care about it text stuff.
// We listen out for text blocks, look for our text, and then put a
// black box over it.. text 'redacted'
//
class RedactRenderListener : IRenderListener
{
private PdfContentByte _canvas;
private string _matchPattern;
public RedactRenderListener(string MatchPattern)
{
_matchPattern = MatchPattern;
}
public RedactRenderListener(PdfContentByte Canvas, string MatchPattern)
{
_canvas = Canvas;
_matchPattern = MatchPattern;
}
public void SetCanvas(PdfContentByte Canvas)
{
_canvas = Canvas;
}
public void BeginTextBlock() { }
public void RenderText(TextRenderInfo renderInfo)
{
var text = renderInfo.GetText();
var match = Regex.Match(text, _matchPattern);
if(match.Success)
{
var p1 = renderInfo.GetCharacterRenderInfos()[match.Index].GetAscentLine().GetStartPoint();
var p2 = renderInfo.GetCharacterRenderInfos()[match.Index+match.Length].GetAscentLine().GetEndPoint();
var p3 = renderInfo.GetCharacterRenderInfos()[match.Index+match.Length].GetDescentLine().GetEndPoint();
var p4 = renderInfo.GetCharacterRenderInfos()[match.Index].GetDescentLine().GetStartPoint();
_canvas.SaveState();
_canvas.SetColorStroke(BaseColor.BLACK);
_canvas.SetColorFill(BaseColor.BLACK);
_canvas.MoveTo(p1[Vector.I1], p1[Vector.I2]);
_canvas.LineTo(p2[Vector.I1], p2[Vector.I2]);
_canvas.LineTo(p3[Vector.I1], p3[Vector.I2]);
_canvas.LineTo(p4[Vector.I1], p4[Vector.I2]);
_canvas.ClosePathFillStroke();
_canvas.RestoreState();
}
}
public void EndTextBlock() { }
public void RenderImage(ImageRenderInfo renderInfo) { }
}
}
Using them with iTextSharp
var reader = new PdfReader("SRC FILE PATH GOES HERE");
var dstFile = File.Open("DST FILE PATH GOES HERE", FileMode.Create);
pdfStamper = new PdfStamper(reader, output, reader.PdfVersion, false);
// We don't need to auto-rotate, as the PdfContentStreamEditor will already deal with pre-rotated space..
// if we enable this we will inadvertently rotate the content.
pdfStamper.RotateContents = false;
// This is for the Text Replace
var replaceTextProcessor = new TextReplaceStreamEditor(
"TEXT TO REPLACE HERE",
"TEXT TO SUBSTITUTE IN HERE");
for(int i=1; i <= reader.NumberOfPages; i++)
replaceTextProcessor.EditPage(pdfStamper, i);
// This is for the Text Redact
var redactTextProcessor = new TextRedactStreamEditor(
"TEXT TO REDACT HERE");
for(int i=1; i <= reader.NumberOfPages; i++)
redactTextProcessor.EditPage(pdfStamper, i);
// Since our redacting just puts a box over the top, we should secure the document a bit... just to prevent people copying/pasting the text behind the box.. we also prevent text to speech processing of the file, otherwise the 'hidden' text will be spoken
pdfStamper.Writer.SetEncryption(null,
Encoding.UTF8.GetBytes("ownerPassword"),
PdfWriter.AllowDegradedPrinting | PdfWriter.AllowPrinting,
PdfWriter.ENCRYPTION_AES_256);
// hey, lets get rid of Javascript too, because it's annoying
pdfStamper.Javascript = "";
// and then finally we close our files (saving it in the process)
pdfStamper.Close();
reader.Close();
You can use GroupDocs.Redaction (available for .NET) for replacing or removing the text from PDF documents. You can perform the exact phrase, case-sensitive and regular expression redaction (removal) of the text. The following code snippet replaces the word "candy" with "[redacted]" in the loaded PDF document.
C#:
using (Document doc = Redactor.Load("D:\\candy.pdf"))
{
doc.RedactWith(new ExactPhraseRedaction("candy", new ReplacementOptions("[redacted]")));
// Save the document to "*_Redacted.*" file.
doc.Save(new SaveOptions() { AddSuffix = true, RasterizeToPDF = false });
}
Disclosure: I work as Developer Evangelist at GroupDocs.

ASP.NET Bundling and minification include dynamic files from database

I'm developing a multi-tenancy MVC 4 application on which the user has some theming possibilities.
He can override every single resource (css, js, jpg, png, ect...) by adding a relative path to a theming table e.g. /Scripts/booking.js
Which tenant to use is figured out by the URL e.g. http://myapp/tenant/Booking/New this is simply the name of the connection string which should be used.
Therefore if a request is made for a specific resource I first need to check if there is an overridden version of this resource in the database and use it if found.
Now I'd like to implement the new bundling and minification features which microsoft provides in the System.Web.Optimization namespace. But I couldn't figure out how to achieve this with the files in the database.
I've prototyped my own JsMinify implementation to achieve this
public class MyJsMinify : JsMinify
{
private static byte[] GetContentFile(FileInfo filePath)
{
string fullName = filePath.FullName;
int indexOf = fullName.IndexOf("content", StringComparison.OrdinalIgnoreCase);
string substring = fullName.Substring(indexOf + 8).Replace(#"\\", "/").Replace(#"\", "/");
ThemingService themingService = ObjectFactory.GetInstance<ThemingService>();
Theming myTheming = themingService.Find(new ThemingFilter { FilePathLike = substring });
if (myTheming == null)
{
return themingService.GetContentFile(fullName);
}
return myTheming.FileData;
}
public override void Process(BundleContext context, BundleResponse response)
{
StringBuilder newContent = new StringBuilder();
foreach (FileInfo fileInfo in response.Files)
{
using (MemoryStream memoryStream = new MemoryStream(GetContentFile(fileInfo)))
{
using (StreamReader myStreamReader = new StreamReader(memoryStream, true))
{
newContent.AppendLine(myStreamReader.ReadToEnd());
}
}
}
response.Content = newContent.ToString();
base.Process(context, response);
}
}
This seems to work if I'm in Release mode but while developing I'd like to get each single script referenced independently. This is automatically done throughout the bundling and minification framework. The Resource URL's generated by the framework looks like the following
<script src="/myapp/Content/Scripts/jquery-1.9.0.js"></script>
but should look like this
<script src="/myapp/tenant/Content/Scripts/jquery-1.9.0.js"></script>
I've configured the following Routes:
routeCollection.MapRoute("Content1", "{mandator}/Content/{*filePath}", new { mandator = defaultMandator, controller = "Environment", action = "ContentFile" }, new { mandator = mandatorConstraints });
routeCollection.MapRoute("Content2", "Content/{*filePath}", new { mandator = defaultMandator, controller = "Environment", action = "ContentFile" }, new { mandator = mandatorConstraints });
The ContentFile Method looks like this
[AcceptVerbs(HttpVerbs.Get)]
[AcceptType(HttpTypes.All)]
[OutputCache(CacheProfile = "ContentFile")]
public ActionResult ContentFile(string filePath)
{
if (string.Compare(filePath, "Stylesheets/Import.css", StringComparison.OrdinalIgnoreCase) == 0)
{
return GetContentImport(CssFileArray, "Stylesheets/");
}
if (string.Compare(filePath, "Stylesheets/ImportOutlook.css", StringComparison.OrdinalIgnoreCase) == 0)
{
return GetContentImport(OutlookCssFileArray, "Stylesheets/");
}
if (string.Compare(filePath, "Scripts/OutlookAddin/Import.js", StringComparison.OrdinalIgnoreCase) == 0)
{
return GetContentImport(OutlookJsFileArray, "Scripts/");
}
return new FileContentResult(GetContentFile(filePath), MimeType(filePath));
}
Does anybody have an idea how I could achieve this?
Is there a multi-tenancy pattern to follow?
So I'm not sure I completely understand your scenario, but I believe this is what VirtualPathProviders could be used for.
We added support in the 1.1-alpha1 release, so bundles will automatically use the VirtualPathProvider registered with ASP.NET to fetch the contents of the file.
If you were to write a custom VPP that is able to always return the correct version of ~/Scripts/booking.js, everything should just work.

MVC 4 How to get json response from a repository into a view using Ajax?

I am a newbie when it comes to MVC4 Web Development and there's something I am struggling with.
Basically, I have the following :
public class maincontroller: Controller
{
private MyRepository myRepository;
public mainController()
{
myRepository= new MyRepository();
}
public ActionResult Index()
{
var mystuff = myRepository.GetPrograms();
return View(mystuff);
}
public ActionResult MyStuff()
{
var mystuff = myRepository.GetStuff(1);
return Json(mystuff , JsonRequestBehavior.AllowGet);
}
}
Assuming that in my `MyRepository' class I have two functions:
One that is setting up `mystuff':
public MyRepository()
{
for (int i = 0; i < 8; i++)
{
programs.Add(new MyStuff
{
Title = "Hello" + i,
content = "Hi"
});
}
}
and second function that gets Stuff:
public List<MyStuff> GetStuff(int pageNumber = 0)
{
return stuff
.Skip(pageNumber * pageCount)
.Take(pageCount).ToList();
}
All works well. I mean I am able to iterate through `stuff' and display on a view...
The problem is that I want to display MyStuff() ( which returns Json ) using AJAX and then append all stuff to a view. How do I do that?
I have been beating my head against the wall for about 4 hours now, and can't get this working.
Please any help will be much appreciated.
Thank you.
At the most straightforward level, you can simply append HTML to your document using something like this (assuming you're using JQuery, because it's so much easier):
<div id="container"></div>
// make AJAX call to "MyStuff" action in the current controller
$.get(#Url.Action("MyStuff", function(data) {
// cycle through each item in the response
$.each(data, function(index, item) {
// construct some HTML from the JSON representation of MyStuff
var html = "<div>" + item.StuffProperty + "</div>";
// append the HTML to a container in the current document
$("#container").append(html);
});
});
This adds some HTML for each item in the collection to a container element, using (eg) StuffProperty from the MyStuff class.
Appending HTML manually like this can be a hassle once it gets too complicated -- at that point you should consider using either:
Partial views (return HTML directly from the controller, instead of JSON)
A client-side templating engine like Mustache.js, Underscore.js, etc, to convert JSON into HTML.