Adobe embedded PDF save to Server giving corrupt file - asp.net-mvc

I am using Adobe Document Services PDF Embed API to display PDF files in my website and Iam allowing users to add sticky notes in the pdf and then save it
My problem is that i need to save the file back to the Server. But I cant find the PDF Buffer. I need to send the pdf buffer or the updated pdf to my Asp.net Conbtroller
adobeDCView.previewFile({
/* Pass information on how to access the file */
content: {
/* Location of file where it is hosted */
location: {
url: myurl,
},
},
/* Pass meta data of file */
metaData: {
/* file name */
fileName: "-VER - 0.pdf"
}
}, viewerConfig);
/* Define Save API Handler */
var saveApiHandler = function (metaData, content, options) {
console.log(metaData, content, options);
return new Promise(function (resolve, reject) {
/* Dummy implementation of Save API, replace with your business logic */
/
var formData = new FormData();
formData.append('metaData', metaData);
formData.append('content', content); // is this the pdf buffer ???
formData.append('options', options);
setTimeout(function () {
//Want to do ajax call to controller here
var response = {
code: AdobeDC.View.Enum.ApiResponseCode.SUCCESS,
data: {
metaData: Object.assign(metaData, { updatedAt: new Date().getTime() })
},
};
resolve(response);
}, 2000);
});
};
And in my controller I have
[HttpPost ]
public ActionResult GetData(object Metadata, Object content,object option)
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream()) {
bf.Serialize(ms, content);
System.IO. File.WriteAllBytes(#"C:\1.pdf", ms.ToArray());
}
return View();
}

As #joelgeraci suggested Need to convert the content to base64 and add it to form data and send to Controller action( credits to the codepen provided by #joelgeraci
var base64PDF = arrayBufferToBase64(content);
var formData = new FormData();
formData.append('content', base64PDF);
Function to convert arraybuffer
function arrayBufferToBase64(buffer) {
var binary = "";
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
And in the controller
[HttpPost]
public ActionResult GetData(object Metadata, Object content, object option, IEnumerable<HttpPostedFileBase> productImg, String DocumentID, string filename)
{
byte[] sPDFDecoded = Convert.FromBase64String(((string[])content)[0]);
string filepath = "C:\1.pdf";
System.IO.File.WriteAllBytes(filepath, sPDFDecoded);
return View();
}

Related

Summernote image upload with .NET Core

Im really struggling to get SummerNote to upload an iamge in .NET core. The trouble is the IFormFile file parameter is null when a new image is uploaded.
I initialise Summernote using the following -
$('#MessageBody').summernote({
height: ($(window).height() - 300),
callbacks: {
onImageUpload: function(image) {
uploadImage(image[0]);
}
}
});
Here is the uploadImage function -
function uploadImage(image) {
var data = new FormData();
data.append("image", image);
$.ajax({
url: '/EmailTemplate/UploadImage',
cache: false,
contentType: false,
processData: false,
data: data,
type: "post",
success: function(url) {
var image = $('<img>').attr('src', 'http://' + url);
$('#MessageBody').summernote("insertNode", image[0]);
alert(url);
var imgNode = document.createElement('img');
imgNode.src = url;
$('#MessageBody').summernote('insertNode', imgNode);
},
error: function(data) {
console.log(data);
}
});
And finally, here is the controller -
[HttpPost]
public async Task<IActionResult> UploadImage(IFormFile file)
{
string message;
var saveimg = Path.Combine(_hostingEnvironment.WebRootPath, "Images", file.FileName);
string imgext = Path.GetExtension(file.FileName);
if (imgext == ".jpg" || imgext == ".png")
{
using (var uploadimg = new FileStream(saveimg, FileMode.Create))
{
await file.CopyToAsync(uploadimg);
message = "The selected file" + file.FileName + " is saved";
}
}
else
{
message = "only JPG and PNG extensions are supported";
}
// return "filename : " + saveimg + " message :" + message;
return Content(Url.Content(saveimg));
}
The parameter is called file while the field name is image. To fix this use the same name, either file or image.
The IFormFile type represents the value of an input type=file field. IFormFile parameters are bound to fields based on their name. There may be many file fields in the same form so the type isn't enough to determine the field.
Field binding is explained in the Sources section of the Model Binding document.

Free text editor with image gallery

Hi i want a free text editor with image or file gallery and i want to insert image or file in the middle of the text.
Please give me your suggestions.
ckeditor(text)+ckfinder(image)
Or
you can use Summernote with server side image upload setup
$('#Editor').summernote({
lang: 'fa-IR',
callbacks: {
onImageUpload: function (files) {
var $editor = $(this);
var data = new FormData();
data.append('imageFile', files[0]);
$.ajax({
url: '/Server/UploadImage',
method: 'POST',
data: data,
processData: false,
contentType: false,
success: function (url) {
$editor.summernote('insertImage', url);
}
});
}
}
});
and MVC5 server side sample action code:
public string UploadImage()
{
HttpPostedFileBase file = null;
string RenameFile = "";
for (int i = 0; i < Request.Files.Count; i++)
{
file = Request.Files[i];
string fileExt = System.IO.Path.GetExtension(file.FileName);
Guid randomFileName = Guid.NewGuid();
RenameFile = randomFileName + fileExt;
var path = Path.Combine(Server.MapPath("~/Content/Uploads/"), RenameFile);
file.SaveAs(path);
}
return #"/Content/Uploads/" + RenameFile;
}

How to insert HTML into Response body using .NET Core Middleware

I've been trying to cobble up some middleware that will allow me to measure the processing time on a request. This example gave me a good starting point, but I've run into trouble.
In the code below, I am able to measure the process time and insert it in a div (using HTML Agility Pack). However, the original contents of the page get duplicated. I think I'm doing something incorrectly with the context.Response.Body property in UpdateHtml(), but cannot figure out what it is. (I made some comments in the code.) If you see anything that looks incorrect, could you please let me know?
Thanks.
public class ResponseMeasurementMiddleware
{
private readonly RequestDelegate _next;
public ResponseMeasurementMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
watch.Start();
context.Response.OnStarting(async () =>
{
var responseTime = watch.ElapsedMilliseconds;
var newContent = string.Empty;
var existingBody = context.Response.Body;
string updatedHtml = await UpdateHtml(responseTime, context);
await context.Response.WriteAsync(updatedHtml);
});
await _next.Invoke(context);
}
private async Task<string> UpdateHtml(long responseTime, HttpContext context)
{
var newContent = string.Empty;
var existingBody = context.Response.Body;
string updatedHtml = "";
//I think I'm doing something incorrectly in this using...
using (var newBody = new MemoryStream())
{
context.Response.Body = newBody;
await _next(context);
context.Response.Body = existingBody;
newBody.Position = 0;
newContent = await new StreamReader(newBody).ReadToEndAsync();
updatedHtml = CreateDataNode(newContent, responseTime);
}
return updatedHtml;
}
private string CreateDataNode(string originalHtml, long responseTime)
{
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(originalHtml);
HtmlNode testNode = HtmlNode.CreateNode($"<div><h2>Inserted using Html Agility Pack: Response Time: {responseTime.ToString()} ms.</h2><div>");
var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body");
htmlBody.InsertBefore(testNode, htmlBody.FirstChild);
string rawHtml = htmlDoc.DocumentNode.OuterHtml; //using this results in a page that displays my inserted HTML correctly, but duplicates the original page content.
//rawHtml = "some text"; uncommenting this results in a page with the correct format: this text, followed by the original contents of the page
return rawHtml;
}
}
For duplicated html, it is caused by await _next(context); in UpdateHtml which will invoke the rest middlware like MVC to handle the requests and response.
Withtout await _next(context);, you should not modify the Reponse body in context.Response.OnStarting.
For a workaround, I would suggest you place the ResponseMeasurementMiddleware as the first middleware and then calculate the time like
public class ResponseMeasurementMiddleware
{
private readonly RequestDelegate _next;
public ResponseMeasurementMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originalBody = context.Response.Body;
var newBody = new MemoryStream();
context.Response.Body = newBody;
var watch = new Stopwatch();
long responseTime = 0;
watch.Start();
await _next(context);
//// read the new body
// read the new body
responseTime = watch.ElapsedMilliseconds;
newBody.Position = 0;
var newContent = await new StreamReader(newBody).ReadToEndAsync();
// calculate the updated html
var updatedHtml = CreateDataNode(newContent, responseTime);
// set the body = updated html
var updatedStream = GenerateStreamFromString(updatedHtml);
await updatedStream.CopyToAsync(originalBody);
context.Response.Body = originalBody;
}
public static Stream GenerateStreamFromString(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
private string CreateDataNode(string originalHtml, long responseTime)
{
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(originalHtml);
HtmlNode testNode = HtmlNode.CreateNode($"<div><h2>Inserted using Html Agility Pack: Response Time: {responseTime.ToString()} ms.</h2><div>");
var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body");
htmlBody.InsertBefore(testNode, htmlBody.FirstChild);
string rawHtml = htmlDoc.DocumentNode.OuterHtml; //using this results in a page that displays my inserted HTML correctly, but duplicates the original page content.
//rawHtml = "some text"; uncommenting this results in a page with the correct format: this text, followed by the original contents of the page
return rawHtml;
}
}
And register ResponseMeasurementMiddleware like
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<ResponseMeasurementMiddleware>();
//rest middlwares
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
For this way app.UseMiddleware<ResponseMeasurementMiddleware>();, the action will be last opertion before sending the response, and then processing time would be suitable for processing time.

Image displaying error from database

After the recent changes in my application still i get this issue while displaying the image using the relative path in the database. Error: 404 NOT FOUND http://localhost:1256/Empdet/%22/Photos/jobs.jpg%22
Controller.js:
$scope.UploadFile = function () {
console.log('UploadFile');
console.log($scope.Empdet.PhotoFile);
var file = $scope.Empdet.PhotoFile;
console.log('file is ' + JSON.stringify(file));
var uploadUrl = "../Photos";
console.log('before file upload');
EmployeeFactory.UploadFile(file, uploadUrl).success(function (response) {
$scope.Empdet.PhotoText = response;
console.log('$scope.Empdet.PhotoText');
console.log(response);
}).error(function () {
console.log('error');
});
console.log('after file upload');
};
service.js:
service.UploadFile = function (file, uploadUrl) {
var fd = new FormData();
fd.append('file', file);
return $http.post('/Empdet/UploadFile', fd, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
});
}
EmpdetController.cs:
[HttpPost]
public ActionResult UploadFile()
{
var file = Request.Files[0];
var path = Path.Combine(Server.MapPath("~/Photos/"), file.FileName);
file.SaveAs(path);
// prepare a relative path to be stored in the database and used to display later on.
var filename = Url.Content("~/Photos/" + file.FileName);
// save to db
return Json(filename.ToString(), JsonRequestBehavior.AllowGet);
}
Remove the .toString() from your function, the FileName property already returns a string.
[HttpPost]
public ActionResult UploadFile()
{
var file = Request.Files[0];
var path = Path.Combine(Server.MapPath("~/Photos/") + file.FileName);
file.SaveAs(path);
// prepare a relative path to be stored in the database and used to display later on.
string filename = Url.Content("~/Photos/" + file.FileName);
// save to db
return Json(filename, JsonRequestBehavior.AllowGet);
}
Parse the return in your controller. This should get rid of the extra quotes(") in your URL.
controller.js:
$scope.UploadFile = function () {
console.log('UploadFile');
console.log($scope.Empdet.PhotoFile);
var file = $scope.Empdet.PhotoFile;
console.log('file is ' + JSON.stringify(file));
var uploadUrl = '/Empdet/UploadFile';
console.log('before file upload');
EmployeeFactory.UploadFile(file, uploadUrl).success(function (response) {
console.log(JSON.parse(response));
console.log('$scope.Empdet.PhotoText');
$scope.Empdet.PhotoText = JSON.parse(response);
}).error(function () {
console.log('error');
});
console.log('after file upload');
};
EmpdetController.cs:
[HttpPost]
public ActionResult UploadFile()
{
var file = Request.Files[0];
var path = Path.Combine(Server.MapPath("~/Photos/") + file.FileName);
file.SaveAs(path);
// prepare a relative path to be stored in the database and used to display later on.
string filename = Url.Content("~/Photos/" + file.FileName);
// save to db
return Json(filename, JsonRequestBehavior.AllowGet);
}

Using CSVHelper to output stream to browser

I'm trying to use CSVHelper to generate a CSV file and send it back to a browser, so the user can select a save location and filename and save the data.
The website is MVC based. Here' the jQuery button code I'm using to make the call (data is some serialised Json representation of a DTO list):
$.ajax({
type: "POST",
url: unity.baseUrl + "common/ExportPayments",
data: data
});
Here's the controller code:
[HttpPost]
public FileStreamResult ExportPayments()
{
MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms);
CsvWriter writer = new CsvWriter(sw);
List<Payment_dto> pd = _commonService.GetPayments();
foreach (var record in pd)
{
writer.WriteRecord(record);
}
sw.Flush();
return new FileStreamResult(ms, "text/csv");
}
Which seems to achieve precisely nothing - invoking the method steps into the correct bit of code but the response is empty, let alone offering the user a file dialog to save the data. I've stepped through this code, and it brings back data from the service, writes it, and throws no errors. So what am I doing wrong?
EDIT: Returning this ...
return File(ms.GetBuffer(), "text/csv", "export.csv");
... gives me a response, consisting of the csv-formatted data that I'm expecting. But the browser still doesn't seem to know what to do with it - no download option is offered to the user.
Try below code:
public FileStreamResult ExportPayments()
{
var result = WriteCsvToMemory(_commonService.GetPayments()());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
public byte[] WriteCsvToMemory(IEnumerable<Payment_dto> records)
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(records);
streamWriter.Flush();
return memoryStream.ToArray();
}
}
Update
Below is how to pass a complex type model to an action method which is using GET HTTP method. I don't prefer this approach, it just gives you an idea there is an approach to achieve this.
Model
public class Data
{
public int Id { get; set; }
public string Value { get; set; }
public static string Serialize(Data data)
{
var serializer = new JavaScriptSerializer();
return serializer.Serialize(data);
}
public static Data Deserialize(string data)
{
var serializer = new JavaScriptSerializer();
return serializer.Deserialize<Data>(data);
}
}
Action:
[HttpGet]
public FileStreamResult ExportPayments(string model)
{
//Deserialize model here
var result = WriteCsvToMemory(GetPayments());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
View:
#{
    var data = new Data()
    {
        Id = 1,
        Value = "This is test"
    };
}
#Html.ActionLink("Export", "ExportPayments", new { model = Data.Serialize(data) })
ASP.NET Core solution:
var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8); // No 'using' around this as it closes the underlying stream. StreamWriter.Dispose() is only really important when you're dealing with actual files anyhow.
using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture, true)) // Note the last argument being set to 'true'
csvWriter.WriteRecords(...);
streamWriter.Flush(); // Perhaps not necessary, but CsvWriter's documentation does not mention whether the underlying stream gets flushed or not
memoryStream.Position = 0;
Response.Headers["Content-Disposition"] = "attachment; filename=somename.csv";
return File(memoryStream, "text/csv");
Try in the controller:
HttpContext.Response.AddHeader("content-disposition", "attachment; filename=payments.csv");
Could also user dynamic keyword for converting any data
Code from #Lin
public FileStreamResult ExportPayments()
{
var result = WriteCsvToMemory(_commonService.GetPayments()());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
public byte[] WriteCsvToMemory(dynamic records)
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(records);
streamWriter.Flush();
return memoryStream.ToArray();
}
}

Resources