PDFTron webviewer - how to save the whole redacted pdf to server using ASP.net MVC Core - asp.net-mvc

I am currently a developing an application in MVC Core that is using a PDFTron webviewer. Is there anyway to save the edited pdf edited with pdftron webviewer to the server?
There is a feature of pdftron that saves annotations to the server, but I need to save the whole pdf with the edits to the server.
WebViewer({
path: '/lib/WebViewer',
initialDoc: '/StaticResource/Music.pdf', fullAPI: !0, enableRedaction: !0
}, document.getElementById('viewer')).then(
function(t) {
samplesSetup(t);
var n = t.docViewer;
n.on('documentLoaded', function() {
document.getElementById('apply-redactions').onclick = function() {
t.showWarningMessage({
title: 'Apply redaction?',
message: 'This action will permanently remove all items selected for redaction. It cannot be undone.',
onConfirm: function () {
alert( );
t.docViewer.getAnnotationManager().applyRedactions()
debugger
var options = {
xfdfString: n.getAnnotationManager().exportAnnotations()
};
var doc = n.getDocument();
const data = doc.getFileData(options);
const arr = new Uint8Array(data);
const blob = new Blob([arr], { type: 'application/pdf' });
const data = new FormData();
data.append('mydoc.pdf', blob, 'mydoc.pdf');
// depending on the server, 'FormData' might not be required and can just send the Blob directly
const req = new XMLHttpRequest();
req.open("POST", '/DocumentRedaction/SaveFileOnServer', true);
req.onload = function (oEvent) {
// Uploaded.
};
req.send(data);
return Promise.resolve();
},
});
};
}),
t.setToolbarGroup('toolbarGroup-Edit'),
t.setToolMode('AnnotationCreateRedaction');
}
);
When i send the request to the Controller i am not getting the file it is coming null
[HttpPost]
public IActionResult SaveFileOnServer(IFormFile file)
{
return Json(new { Result="ok"});
}
Can any one suggest me where i am going wrong
Thanks in adavance

For JavaScript async function, you need to wait for it completes before doing other things. For example, AnnotationManager#applyRedactions() returns a Promise, the same for AnnotationManager#exportAnnotations() and Document#getFileData().
For JS async functions, you can take a look at:
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
So here you may want to use await to wait for the Promise completes.

Related

Can I edit cached index.html before serving in service worker?

The webapp I am developing is opened inside a webview via post. The post body parameters(contextual user inputs) are inserted in to the index.html.
So the repeat loads are failing because the contextual input is absent.
The official docs say that nothing can be done about it. It says all you can do now is go network first and enable navigation preload.
(https://developers.google.com/web/tools/workbox/modules/workbox-navigation-preload ---------
"This feature is intended to reduce navigation latency for developers who can't precache their HTML......")
Hence I am looking for a way to edit my cached index.html before is gets used. I want to insert the post body parameters in to the index.html. I am not able to find any documentation on editing the cache. Hence any help/inputs from community would be appreciated.
Workbox !== service worker. Workbox is built on top of service worker, but raw service workers give you full control over the request and response, so you can do pretty much whatever you want.
Editing a response
Here's how you might change the text of a response:
addEventListener('fetch', event => {
event.respondWith(async function() {
// Get a cached response:
const cachedResponse = await caches.match('/');
// Get the text of the response:
const responseText = await cachedResponse.text();
// Change it:
const newText = responseText.replace(/Hello/g, 'Goodbye');
// Serve it:
return new Response(newText, cachedResponse);
}());
});
There's a potential performance issue here, that you end up loading the full response into memory, and doing the replacement work, before you serve the first byte. With a little more effort, you can do the replacement in a streaming manner:
function streamingReplace(find, replace) {
let buffer = '';
return new TransformStream({
transform(chunk, controller) {
buffer += chunk;
let outChunk = '';
while (true) {
const index = buffer.indexOf(find);
if (index === -1) break;
outChunk += buffer.slice(0, index) + replace;
buffer = buffer.slice(index + find.length);
}
outChunk += buffer.slice(0, -(find.length - 1));
buffer = buffer.slice(-(find.length - 1));
controller.enqueue(outChunk);
},
flush(controller) {
if (buffer) controller.enqueue(buffer);
}
})
}
addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (!(url.origin === location.origin && url.pathname === '/sw-content-change/')) return;
event.respondWith((async function() {
const response = await fetch(event.request);
const bodyStream = response.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(streamingReplace('Hello', 'Goodbye'))
.pipeThrough(new TextEncoderStream());
return new Response(bodyStream, response);
})());
});
Here's a live demo of the above.
Getting the POST params of a response
The other part you need, is getting the POST body of the response:
addEventListener('fetch', event => {
event.respondWith(async function() {
if (event.request.method !== 'POST') return;
const formData = await event.request.formData();
// Do whatever you want with the form data…
console.log(formData.get('foo'));
}());
});
See the MDN page for FormData for the API.

How to upload file in angular 2

This is the function I am using to upload file but is is giving me the error : Length is undefined. what I have to change in this code. where to give path of file to upload.
fileChange(event) {
let fileList: FileList = event.target.files;
if(fileList) {
let file: File = fileList[0];
let formData:FormData = new FormData();
formData.append('uploadFile', file, file.name);
let headers = new Headers();
/** No need to include Content-Type in Angular 4 */
headers.append('Content-Type', 'multipart/form-data');
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
this.http.post(`assets/Files/info.txt`, formData, options)
.map(res => res.json())
.catch(error => Observable.throw(error))
.subscribe(
data => console.log(fileList),
error => console.log(error)
)
}
}
you need to use xhr request to transfer files
fileChange(event: EventTarget) {
let eventObj: MSInputMethodContext = <MSInputMethodContext> event;
let target: HTMLInputElement = <HTMLInputElement> eventObj.target;
let files: FileList = target.files;
if(files) {
let file: File = files[0];
this.upload(file)
}
}
public upload(filedata: File) {
let url = 'your url'
if (typeof filedata != 'undefined') {
return new Promise((resolve, reject) => {
let formData: any = new FormData();
let xhr = new XMLHttpRequest();
formData.append('icondata', filedata, filedata.name);
xhr.open('POST', url, true);
xhr.setRequestHeader('Authorization', 'JWT ' + localStorage.getItem('id_token'));
xhr.send(formData);
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
resolve(JSON.parse(xhr.responseText));
}
}
});
}
}
I understand that this is not the functionality you want to have but with no backend you can not upload files to be persistent, they should be stored somewhere. If you just wanna manipulate file names for instance, skip the express part in my answer. I personally used this code which I altered to upload multiple files.
In your Component :
import {FormArray, FormBuilder, FormControl, FormGroup} from "#angular/forms";
declare FormBuilder in the constructor:
constructor (private http: Http, private fb: FormBuilder) {}
in ngOnInit() set a variable as follows :
this.myForm = this.fb.group({chosenfiles: this.fb.array([])});
this is the code for the upload method :
// invoke the upload to server method
// TODO
// Should be in a service (injectable)
upload() {
const formData: any = new FormData();
const files: Array<File> = this.filesToUpload;
//console.log(files);
const chosenf = <FormArray> this.myForm.controls["chosenfiles"];
// iterate over the number of files
for(let i =0; i < files.length; i++){
formData.append("uploads[]", files[i], files[i]['name']);
// store file name in an array
chosenf.push(new FormControl(files[i]['name']));
}
this.http.post('http://localhost:3003/api/upload', formData)
.map(files => files.json())
.subscribe(files => console.log('upload completed, files are : ', files));
}
the method responsible for the file change :
fileChangeEvent(fileInput: any) {
this.filesToUpload = <Array<File>>fileInput.target.files;
const formData: any = new FormData();
const files: Array<File> = this.filesToUpload;
console.log(files);
const chosenf = <FormArray> this.myForm.controls["chosenfiles"];
// iterate over the number of files
for(let i =0; i < files.length; i++){
formData.append("uploads[]", files[i], files[i]['name']);
// store file name in an array
chosenf.push(new FormControl(files[i]['name']));
}
}
Template is something like this
<input id="cin" name="cin" type="file" (change)="fileChangeEvent($event)" placeholder="Upload ..." multiple/>
Notice multiple responsible for allowing multiple selections
The express API which will handle the request uses multer after an npm install
var multer = require('multer');
var path = require('path');
specify a static directory which will hold the files
// specify the folder
app.use(express.static(path.join(__dirname, 'uploads')));
As specified by multer
PS: I did not investigate multer, as soon as i got it working, i moved to another task but feel free to remove unnecessary code.
var storage = multer.diskStorage({
// destination
destination: function (req, file, cb) {
cb(null, './uploads/')
},
filename: function (req, file, cb) {
cb(null, file.originalname);
}
});
var upload = multer({ storage: storage });
And finally the endpoint
app.post("/api/upload", upload.array("uploads[]", 12), function (req, res) {
console.log('files', req.files);
res.send(req.files);
});

How can i download a file from ajax call and force the results do be downloaded

I have post the similar question yesterday and i haven't get the results. I have loaded data on my kendo grid and when i
click download, i want to download the file but it is not returning results. The folder that i am downloading from is on the server not on project solution.
I created a controller to test the download without a button click and it works but i want to download from kendo button click. No error on console
Function for getting the selected Id from the grid
function showDetails(e) {
e.preventDefault();
var dataItem = this.dataItem($(e.currentTarget).closest("tr"));
DownloadIndexController(dataItem.possID);
}
Ajax call to the controller
function DownloadIndexController(possID) {
$.ajax({
url: '#Url.Action("DownloadIndex", "Poss")',
type: "GET",
data: { possID: possID },
dataType: "json",
success: function (data) {
window.location = '#Url.Action("DownloadIndex", "Poss")';
}
})
}
Controller
public ActionResult DownloadIndex(int possID)
{
string Filelocation = "myserverlocation"
FileContentResult ff = null;
try
{
OnePossModel md = new Models.OnePossModel();
JsonParamBuilder myBuilder = new JsonParamBuilder();
myBuilder.AddParam<Guid>("submittingUserID", System.Guid.Parse(User.Identity.GetUserId()));
myBuilder.AddParam<int>("possID", Convert.ToInt32(possID));
string jsonReq = Models.JsonWrapper.JsonPOST(ApiBaseUrl + ApiPOSSSubBaseUrl + "/WritePOSSFile", myBuilder.GetJSonParam());
string possFilename = Models.DeserialiseFromJson<string>.DeserialiseApiResponse(jsonReq);
possFilename = possFilename.Replace(",", ",");
string x = Filelocation + possFilename.ToString();
var type = System.Net.Mime.MediaTypeNames.Application.Octet;
byte[] fileBytes = System.IO.File.ReadAllBytes(x);
string myfileName = possFilename;
ff = File(fileBytes, type,myfileName);
Response.AppendHeader("Content-Disposition", "attachment; filename=" + myfileName);
Response.BinaryWrite(fileBytes);
return ff;
}
catch (Exception ex)
{
throw ex;
}
}
In your controller, just add this:
public FileResult DownloadIndex(int possID)
{
. . . .
return File(fileBytes, type,myfileName);
}
I don't think it can be done the way you are trying to. Take a look here for a workaround to simulate "ajax file download".
In your code you are making 2 requests, the first creates the file and stream it in the response (and cant be downloaded with ajax), and then the second request by setting the window.location - But the file is not "alive" any more since it was allocated to the first response.
If using the FileResult Action then give away the ajax call (depends on your requirements) and just use a plain link: /poss/downloadindex/123

Can't access KML features

I am loading a KML file locally and I have been able to add it to the map successfully. However, I want to interate over the features and can't seem to get anything to work. My code currently:
var myLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: '/kml/sample.kml',
format: new ol.format.KML()
})
});
// Iterate over features *NOT WORKING*
myLayer.getSource().forEachFeature(function(e) {
console.log(e);
})
Any pointers on how I can get the forEachFeature to function, or any alternative method, would be great.
The code in your question works fine, except that the features are loaded asynchronously. Most of the time it will first execute forEachFeature, which finds 0 features to loop through and afterwards the features are loaded.
You may find out that a single feature is loaded by listening for the addfeature event of the source and maybe you can make your desired changes there for each feature separately:
var id = 1;
myLayer.getSource().on('addfeature', function (ev_add) {
console.log(ev_add.feature);
ev_add.feature.once('change', function (ev_change) {
console.log(ev_change.target.getId());
});
ev_add.feature.setId(x);
x += 1;
});
If you must wait until all features are loaded, the change event of the layer can help:
myLayer.once('change', function () {
myLayer.getSource().forEachFeature(function (feature) {
console.log(feature);
});
});
Edit: You are right, the addfeature event handler has the event object as parameter. To your question about setting the ID while adding features, I think that this is again a problem of waiting until the changes are done. I made the amendments in the first snippet.
I found a way to get this to work. Not sure if it's the most efficient however:
var featProj = map.getView().getProjection();
var kmlFormat = new ol.format.KML();
var myLayer = new ol.layer.Vector();
var vectorSource = new ol.source.Vector({
loader: function() {
$.ajax( {
url: '/kml/my.kml',
success: function( data ) {
var features = kmlFormat.readFeatures( data, { featureProjection: featProj } );
vectorSource.addFeatures( features );
// iterate over features
vectorSource.forEachFeature( function( feature ) {
//do something
console.log( feature );
});
}
});
},
strategy: ol.loadingstrategy.bbox
});
myLayer.setSource( vectorSource );

Using plupload with MVC3

So, I've implemented plupload using flash runtime in MVC3.
It works perfectly, in the sense that it uploads using the correction Action and runs it all. However, I'd really like to be able to control the response, and handle it in plupload, but I can't seem to get any response through.
I've tried overriding fileUploaded, but I can't seem to get anything out of the arguments. I've tried return simple strings, json and what have you. I can't seem to get anything out on the client side. And of course being sent through flash, I can't even debug the requests with firebug :/
The same with the Error event, and throwing exceptions. It correctly interprets the exception as an error, but it's always that #IO ERROR with some code like 2038 or something coming out the other end. I can't show my exception string or anything at all. Can anyone help?
Bonus question: How would I send session/cookie data along with the plupload, so I can access the session in my action?
The following has worked for me:
[HttpPost]
public ActionResult Upload(int? chunk, string name)
{
var fileUpload = Request.Files[0];
var uploadPath = Server.MapPath("~/App_Data");
chunk = chunk ?? 0;
using (var fs = new FileStream(Path.Combine(uploadPath, name), chunk == 0 ? FileMode.Create : FileMode.Append))
{
var buffer = new byte[fileUpload.InputStream.Length];
fileUpload.InputStream.Read(buffer, 0, buffer.Length);
fs.Write(buffer, 0, buffer.Length);
}
return Json(new { message = "chunk uploaded", name = name });
}
and on the client:
$('#uploader').pluploadQueue({
runtimes: 'html5,flash',
url: '#Url.Action("Upload")',
max_file_size: '5mb',
chunk_size: '1mb',
unique_names: true,
multiple_queues: false,
preinit: function (uploader) {
uploader.bind('FileUploaded', function (up, file, data) {
// here file will contain interesting properties like
// id, loaded, name, percent, size, status, target_name, ...
// data.response will contain the server response
});
}
});
As far as the bonus question is concerned I am willing to answer it by don't use sessions, as they don't scale well, but because I know that you probably won't like this answer you have the possibility to pass a session id in the request using the multipart_params:
multipart_params: {
ASPSESSID: '#Session.SessionID'
},
and then on the server perform some hacks to create the proper session.
Look here:
$("#uploader").pluploadQueue({
// General settings
runtimes: 'silverlight',
url: '/Home/Upload',
max_file_size: '10mb',
chunk_size: '1mb',
unique_names: true,
multiple_queues: false,
// Resize images on clientside if we can
resize: { width: 320, height: 240, quality: 90 },
// Specify what files to browse for
filters: [
{ title: "Image files", extensions: "jpg,gif,png" },
{ title: "Zip files", extensions: "zip" }
],
// Silverlight settings
silverlight_xap_url: '../../../Scripts/upload/plupload.silverlight.xap'
});
// Client side form validation
$('form').submit(function (e) {
var uploader = $('#uploader').pluploadQueue();
// Files in queue upload them first
if (uploader.files.length > 0) {
// When all files are uploaded submit form
uploader.bind('StateChanged', function () {
if (uploader.files.length === (uploader.total.uploaded + uploader.total.failed)) {
$('form')[0].submit();
}
});
uploader.start();
} else {
alert('You must queue at least one file.');
}
return false;
});
And in Controller:
[HttpPost]
public string Upload( ) {
HttpPostedFileBase FileData = Request.Files[0];
if ( FileData.ContentLength > 0 ) {
var fileName = Path.GetFileName( FileData.FileName );
var path = Path.Combine( Server.MapPath( "~/Content" ), fileName );
FileData.SaveAs( path );
}
return "Files was uploaded successfully!";
}
That's all...No chunk is needed in Controller...

Resources