Serving file to angular js from web api controller - c#

So I have a pdf that I generated using PDF Sharp and saved within my App_Data folder. When the user clicks a button on my controller, I want the user be able to download that file.
I've managed to get it to work (sort of) in the sense that the user can download the file, however when they open up this pdf file the document is blank and appears to be nearly twice the size of the original file saved in my App_Data folder. I'm not sure what's going wrong, but can only guess it's something to do with how I'm reading the file and streaming that data.
Anyway this is the web api controller function:
[HttpGet]
public HttpResponseMessage DownloadPdf(int itemId)
{
var item = _myService.GetItem(itemId);
var result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new StreamContent(File.ReadAllBytes(HostingEnvironment.MapPath(item.ReportFilePath)));
result.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/pdf");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(HostingEnvironment.MapPath(item.ReportFilePath));
return result;
}
And this is the relevant function on my angular controller (note the businessResource call just routes the data to my web api function via an $http call):
$scope.downloadPdf = function() {
businessResource.downloadPdf($scope.itemId).then(function (response) {
var headers = response.headers;
var filename;
var disposition = headers('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
// Get the blob url creator
var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (urlCreator) {
var link = document.createElement('a');
if ('download' in link) {
// Prepare a blob URL
var blob = new Blob([response.data], { type: headers('content-type') });
var url = urlCreator.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute("download", filename);
// Simulate clicking the download link
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(event);
}
}
}, function (response) {
notificationsService.error("Error", "Could not generate report");
});
}
So just to clarify: The call is working in the sense I'm getting a pdf file downloaded, however this file seems to contain no content when I open it in the browser/pdf reader and is nearly twice the size of the original file I'm storing in the App_Data folder.
I don't know where the data is getting corrupted.. any ideas?

Figured it out:
The $http.get that was making the call to the web api needed to be changed from:
return $http.get('<end point url>);
to:
return $http.get('<end point url>', { responseType: 'arraybuffer' });
ie. the responseType needs to be set to 'arraybuffer'. Hope this helps someone!

Related

CKEditor and C# Web API, upload image with simple upload plugin

In my project I use CKEditor WYSWYG package to make HTML content for my website.
There is possible to insert image and send it directly from the package to the server.
Since 2 days I try to figure out how is it possible to catch the sent image from the Angular front-end to the Web API, but still no success.
I use .Net6 and Angular 12 with CKEditor 5.
public async Task<ActionResult<string>> AddPostPhoto(IFormFile photo)
{
try
{
System.Console.WriteLine(Request.ContentType);
var folderDirectory = $"\\Photos\\PostPhotos";
var path = Path.Combine("Photos/PostPhotos", "fileName.jpg");
var memoryStream = new MemoryStream();
await Request.Body.CopyToAsync(memoryStream);
System.Console.WriteLine(Request.HttpContext.Request.ContentLength);
System.Console.WriteLine(Request.Form.Keys);
if (!Directory.Exists(folderDirectory))
{
Directory.CreateDirectory(folderDirectory);
}
await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(fs);
}
return Ok(new { Url = path });
}
catch(Exception exception)
{
return BadRequest(exception.Message);
}
}
Finally I could find a working solution.
my-upload-adapter.ts
//ckeditorExComponent class Ends here and MyUploadAdapter class begins here in the same ckeditorEx.ts
export class MyUploadAdapter {
xhr: any;
loader: any;
serverUrl: string;
baseApiUrl: string;
constructor(loader: any, serverUrl: string, baseApiUrl: string) {
// The file loader instance to use during the upload.
this.loader = loader;
this.serverUrl = serverUrl;
this.baseApiUrl = baseApiUrl;
}
// Starts the upload process.
upload() {
return this.loader.file
.then((file: any) => new Promise((resolve, reject) => {
this._initRequest();
this._initListeners(resolve, reject, file);
this._sendRequest(file);
}));
}
// Aborts the upload process.
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
// Note that your request may look different. It is up to you and your editor
// integration to choose the right communication channel. This example uses
// a POST request with JSON as a data structure but your configuration
// could be different.
//Replace below url with your API url
xhr.open('POST', this.baseApiUrl + 'Tutorial/add-post-photo', true);
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners(resolve: any, reject: any, file: any) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${file.name}.`;
xhr.addEventListener('error', () => reject(genericErrorText));
xhr.addEventListener('abort', () => reject());
xhr.addEventListener('load', () => {
const response = xhr.response;
// This example assumes the XHR server's "response" object will come with
// an "error" which has its own "message" that can be passed to reject()
// in the upload promise.
//
// Your integration may handle upload errors in a different way so make sure
// it is done properly. The reject() function must be called when the upload fails.
if (!response || response.error) {
return reject(response && response.error ? response.error.message : genericErrorText);
}
// If the upload is successful, resolve the upload promise with an object containing
// at least the "default" URL, pointing to the image on the server.
// This URL will be used to display the image in the content. Learn more in the
// UploadAdapter#upload documentation.
resolve({
default: this.serverUrl + response.url
});
});
// Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
// properties which are used e.g. to display the upload progress bar in the editor
// user interface.
if (xhr.upload) {
xhr.upload.addEventListener('progress', (evt: any) => {
if (evt.lengthComputable) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
});
}
}
// Prepares the data and sends the request.
_sendRequest(file: any) {
// Prepare the form data.
const data = new FormData();
data.append('upload', file);
// Important note: This is the right place to implement security mechanisms
// like authentication and CSRF protection. For instance, you can use
// XMLHttpRequest.setRequestHeader() to set the request headers containing
// the CSRF token generated earlier by your application.
// Send the request.
this.xhr.send(data);
}
}
In the Angular component
onReady($event: any) {
$event.plugins.get('FileRepository').createUploadAdapter = (loader: any) => {
return new MyUploadAdapter(loader, this.serverUrl, this.apiUrl);
};
}
The C# Web API controller
[HttpPost("add-post-photo")]
public async Task<ActionResult<string>> AddPostPhoto(IFormFile upload)
{
try
{
FileInfo fileInfo = new FileInfo(upload.FileName);
System.Console.WriteLine(upload.FileName);
var folderDirectory = $"\\Photos\\PostPhotos";
var path = Path.Combine("Photos\\PostPhotos", upload.FileName);
var memoryStream = new MemoryStream();
await upload.OpenReadStream().CopyToAsync(memoryStream);
if (!Directory.Exists(folderDirectory))
{
Directory.CreateDirectory(folderDirectory);
}
await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(fs);
}
return Ok(new { Url = path });
}
catch(Exception exception)
{
return BadRequest(exception.Message);
}
}
It is important to have the parameter upload, otherwise the find the back-end endpoint

Download xls in browser

I would like the browser to show that the xls file has been saved. How to do it? In the example code below: it saves my Report in the correct path, but the browser does not show that the file has been saved. When I cliced button "Download report" return me empty page and not download report.
public ActionResult FilePathResult(List<PrintOutModel> printOutModelList )
{
Prints.Print.PrintReport.Printer print = new Prints.Print.PrintReport.Printer();
var printReport = print.CreateRender(printOutModelList);
byte[] reportResult = printReport;
string path = AppDomain.CurrentDomain.BaseDirectory +"Report9802.xls";
var fileStream = new FileStream(path,
FileMode.Create,
FileAccess.ReadWrite
);
fileStream.Write(reportResult, 0, reportResult.Length);
return new FileStreamResult(fileStream, "application/xls");
}
Any help or sugestion is welcome
Because you are talking about browser, I am assuming your API implementation is all good and the question is front-end focussed.
Here is an example code for Angular 7. In order to download a file, your API "must" be a POST call. That is how browsers can request for a file download. You should basically be calling the postBlob method on a service, which obviously has the HttpClient injected as a member named http in this example. Because this code reads the content disposition sent by the server and sets the dynamic link, it creates an effect of user clicking a hyperlink to an XLS file and invokes the browser's default download behaviour (prompt a "save as", open automatically, etc).
Hope this helps !!
public postBlob(url: string, param: any): Observable<BlobFile> {
return this.http.post<Blob>(url, param, { observe: 'response', responseType: 'blob' as 'json' }).pipe(map(r => {
return this.onBlobResponse(r);
}));
}
private onBlobResponse(r: HttpResponse<Blob>) {
const contentDisposition = r.headers.get('Content-Disposition');
const filename = contentDisposition.split(';')[1].split('filename')[1].split('=')[1].trim();
this.saveFile(filename, r.body);
return <BlobFile>{
name: filename,
blob: r.body
};
}
private saveFile(filename: string, blob: Blob) {
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, filename);
} else {
const a = document.createElement('a');
document.body.appendChild(a);
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = filename;
a.click();
setTimeout(() => {
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}, 0);
}
}

How to create downloadable file link for the file to download in ASP.net core

Previously I was sending file as Byte array from ASP.net core 2.0 and in Angular 4 application I am calling below function to download the file
function (response) { // Here response is byte array
var url= window.URL.createObjectURL(res);
var link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", this.zipLocation + ".zip");
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
But now I want to send the file path from the server like below
https://websiteaddress/file/path/to/download.ext
So in Angular 5, I can directly attach link to href attribute of anchor tag and will make automatic click on that. So I don't need to Convert byte array to url
Here the issue is I don't know how to create that downloadable file path using ASP.net core and send it to frontend
And also I want to know, which approach is better, whether sending Byte array or Sending the direct link? Is there any performance issue with any of the two?
If you are using api response as file data
add responseType: 'arraybuffer' in request header.
Try something like this:
HTML:
<a (click)="downLoad()">Click To Download</a>
TS:
downLoad(){
this.fileService.getFileFromServer(fileId.toString()).subscribe(respData => {
this.downLoadFile(respData, this.type);
}, error => {
});
}
/**
* Method is use to download file.
* #param data - Array Buffer data
* #param type - type of the document.
*/
downLoadFile(data: any, type: string) {
var blob = new Blob([data], { type: type.toString() });
var url = window.URL.createObjectURL(blob);
var pwa = window.open(url);
if (!pwa || pwa.closed || typeof pwa.closed == 'undefined') {
console.log('Please disable your Pop-up blocker and try again');
}
}
file-service.ts:
getFileFromServer(id){
return this.http.get(url, {responseType: 'arraybuffer',headers:headers});
}
your question make confuse about angular frontend and backend
frontend you can use mvc
<a asp-controller="Controller"
asp-action="Download"
asp-route-id="#Model.FileName">Download #Model.FileName</a>
or using angular
Download
<a [href]="ControllerRoute+'/Download?name='+fileName" download>Download {{fileName}}</a>
Ok maybe your problem is your action (in controller) doesnt server a file
you need return a HttpResponse with a MediaType, this is just a example, dont forget best practices on your code
[HttpGet]
public HttpResponseMessage GetDownloadableFIle(string name)
{
try
{
var result = new HttpResponseMessage(HttpStatusCode.OK);
var filePath = $"{MyRootPath}/{name}";
var bytes = File.ReadAllBytes(filePath );
result.Content = new ByteArrayContent(bytes);
var mediaType = "application/octet-stream";
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mediaType);
return result;
}
catch (Exception ex)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.InternalServerError, ex.ToString()));
}
}

How to send files along with the viewModel using Web API or how to save using temporary data

I've read many stackoverflow posts with the similar problems as well as several blogs but I am still uncertain as how to solve my problem :(
I have angularJS directive that allows to upload files to the server. The code is like this:
[HttpPost]
[Route("UploadFile")]
public async Task<HttpResponseMessage> UploadFile()
{
// Check if the request contains multipart/form-data.
if (Request.Content.IsMimeMultipartContent("form-data"))
{
try
{
var resultOut = new List<FileUploadResult>();
var streamProvider = new MultipartMemoryStreamProvider();
streamProvider = await Request.Content.ReadAsMultipartAsync(streamProvider);
foreach (
var item in
streamProvider.Contents.Where(c => !string.IsNullOrEmpty(c.Headers.ContentDisposition.FileName))
)
{
FileUploadResult file = new FileUploadResult()
{
FileName = item.Headers.ContentDisposition.FileName,
// Content = fileBytes, // No need to pass the info back as we're not going to read it save it yet
Key = Guid.NewGuid().ToString(),
Type = item.Headers.ContentDisposition.DispositionType
};
resultOut.Add(file);
//using (Stream stFileSource = new MemoryStream(await item.ReadAsByteArrayAsync())) {
// byte[] fileBytes;
// fileBytes = new Byte[stFileSource.Length];
// stFileSource.Read(fileBytes, 0, Convert.ToInt32(stFileSource.Length));
// FileUploadResult file = new FileUploadResult()
// {
// FileName = item.Headers.ContentDisposition.FileName,
// // Content = fileBytes, // No need to pass the info back as we're not going to read it save it yet
// Key = Guid.NewGuid().ToString(),
// Type = item.Headers.ContentDisposition.DispositionType
// };
// resultOut.Add(file);
//}
}
return Request.CreateResponse(HttpStatusCode.OK, resultOut.ToArray());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
Also directive saves the Files array into a property. My user form allows to remove some files / add more files and then I want to save the information from the form (somewhat complex view model) along with the files. I was unable to figure that problem so far. One possibility I see here is to save the files in the UploadFile method using Repository into a database. However, I would prefer to save that into some temporary table instead (e.g. #FileInfo table) and not the actual table. Or perhaps there is a way to save files (with its binary content) into some memory object so I will be able to get that content back when I am ready to save my model's data? Can you either show implementation of the temporary repository storage or give some other ideas for my dilemma?
Firstly, Your directive need to create a post request with 'multipart/form-data'.
Check this link for reference.
However, we use angular file upload to do this.
angular
.module('app', ['angularFileUpload'])
.controller('AppController', function($scope, FileUploader) {
$scope.uploader = new FileUploader(
{
url: 'Your/upload/url',
headers: {
'autorization': 'Bearer token if you need it'
},
onProgressItem: function () {
...
},
onSuccessItem: function (opt, data) {
...
},
onErrorItem: function (opt) {
...
}
});
//you may want to wrap the following in an event
var uploadItem = $scope.uploader.queue[uploader.queue.length - 1];
uploadItem.formData.push({
someData: "someData",
moreData: "moreData"
});
uploadItem.upload();
uploadItem.formData = [];
});
Then in your controller, you can do the following to retrieve what you need:
//your request
var request = HttpContext.Current.Request;
//your fields
var someData = request.Form["someData"];
var moreData = request.Form["moreData"];
//your file
var file = request.Files["file"];
Looks like a job for TempData:
TempData in ASP.NET MVC is basically a dictionary object derived from
TempDataDictionary. TempData stays for a subsequent HTTP Request as
opposed to other options (ViewBag and ViewData) those stay only for
current request. So, TempdData can be used to maintain data between
controller actions as well as redirects.
example:
//Controller Action 1 (TemporaryEmployee)
public ActionResult TemporaryEmployee()
{
Employee employee = new Employee
{
EmpID = "121",
EmpFirstName = "Imran",
EmpLastName = "Ghani"
};
TempData["Employee"] = employee;
return RedirectToAction("PermanentEmployee");
}
//Controller Action 2(PermanentEmployee)
public ActionResult PermanentEmployee()
{
Employee employee = TempData["Employee"] as Employee;
return View(employee);
}

Azure blob download when using ajax not working in MVC project

I have this function which does the actual file download (in a controller called AzureTest); its a MVC project:
private bool DownloadKit()
{
bool bReturn, bSuccess = false;
CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("ConnString");
CloudBlobClient cbcClient = account.CreateCloudBlobClient();
BlobRequestOptions options = new BlobRequestOptions();
options.UseFlatBlobListing = true;
CloudBlobContainer cbcFiles = new CloudBlobContainer("files", cbcClient);
CloudBlob cbKit = cbcFiles.GetBlobReference("Kit.exe");
ControllerContext.HttpContext.Response.Clear();
ControllerContext.HttpContext.Response.ContentType = "application/octet-stream";
ControllerContext.HttpContext.Response.AddHeader("Content-Disposition", "attachment; filename=Kit.exe");
MemoryStream msFile = new MemoryStream();
cbKit.DownloadToStream(msFile);
msFile.Position = 0;
ControllerContext.HttpContext.Response.OutputStream.Write(msFile.ToArray(), 0, msFile.ToArray().Length);
ControllerContext.HttpContext.Response.Flush();
bReturn = bSuccess;
return bReturn;
}
This is called by the following function:
[HttpPost]
public JsonResult Download()
{
try
{
bool bDlKit = DownloadKit();
}
catch (Exception ex)
{
//ToDo
}
return Json(null);
}
Now the cshtml file has the following javascript code:
$("#btnGetKit").click(function () {
$("#btnGetKit").hide();
$.ajax({
url: "AzureTest/Download",
type: "POST",
success: function () {
$("#btnGetKit").show();
}
})
}
Here is the problem: When I call DownloadKit() right at the page load, then everything work and I get prompted to download the file. When I use the ajax mechanism, even though the code works fine, there is no prompt to download the file. Its as if the OutputStream is not being written to.
I will really appreciate someone giving some pointers. I am new to MVC, so still finding my way around.
I don't think it's possible. Based on this thread: Download a file by jQuery.Ajax, JavaScript cannot save files directly to a user's computer. However do take a look at jQuery File Download Plugin (http://johnculviner.com/post/2012/03/22/Ajax-like-feature-rich-file-downloads-with-jQuery-File-Download.aspx) and see if that works for you.

Categories

Resources