User uses my browser based on CefSharp. He uploads a file (which is selected in file input HTML control) to known server using XMLHttpRequest (it's JavaScript object for AJAX requests). I want to intercept and to read the uploading file in my browser.
I do the same in my browser based on Awesomium using IResourceInterceptor. It's easy, because ResourceRequest parameter contains full local path of the file. How can I do the same in CefSharp browser?
How user uploads file using XMLHttpRequest (JavaScript):
var file = document.getElementById('fileInput').files[0];
var formData = new FormData();
formData.append('file', file);
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.open('POST', '/upload', true);
xhr.send(formData);
Awesomium way to intercept user's file (C#):
class MyResourceInterceptor : IResourceInterceptor
{
public ResourceResponse OnRequest(ResourceRequest request)
{
// intercept URL with /upload path only
if (request.Url.AbsolutePath != "/upload")
{
return null;
}
// full local path for user's file
var filePath = request[1].FilePath;
// now I can read and process the file
}
public bool OnFilterNavigation(NavigationRequest request)
{
return false;
}
}
CefSharp doesn't currently expose a way to access Post Data which is what I'm guessing you require.
I have implemented a PR that contains a basic implementation, feel free to test it out. See https://github.com/cefsharp/CefSharp/pull/1113
The other option you have is implement OnFileDialog, you'd have to display your own dialog, simple enough though.
https://github.com/cefsharp/CefSharp/blob/cefsharp/41/CefSharp/IDialogHandler.cs#L38
Related
This question already has answers here:
How to delete a file after it was streamed in ASP.NET Core
(5 answers)
Closed 2 years ago.
I am working on an ASP.NET Core 2.1 API project, which will be consumed by an Angular App, and later on a mobile app. one of the required functionality is to zip and download a collection of files, the result zipped file is going to be large (1GB or more).
the code I have now is working as follows:
the Angular app requests the API and expects a blob response.
the API on the server creates a Zip file and reads it using memory stream.
the API returns the memory stream using File response.
the method that subscribes to the download service in Angular saves
the file.
what is happening now is when I click in the browser on the download button I have to wait for the download to be finished then the browser shows the default popup that allows the user to save and select where to save.
I was wondering if what I'm doing is correct and won't cause any memory problems in the future?
is there a better methodology where the file could be streamed smoothly, so when the download starts the browser directly shows the save message and shows the default browser progress bar?
angular code:
component click function:
download(event,id){
event.stopPropagation();
event.preventDefault();
this.myservice.Downloadservice(avatar_id).subscribe((res: any) => {
saveAs(res.data, res.filename);
});
}
service code:
DownloadAllservice(id): Observable<any> {
let authToken = localStorage.getItem('auth_token');
let _options = { headers: new Headers({ 'Authorization': `Bearer ${authToken}`, } ),responseType: ResponseContentType.Blob };
let formData = new FormData();
let options ={
type: "zip",
id: id
};
formData.append('options',JSON.stringify(options));
return this.http.post(this.baseUrl + "/api/Download/", formData, _options)
.map(response =>{
return {
'filename': this.getFileNameFromHttpResponse(response),
'data': response.blob()
} })
.catch(this.handleError);
}
.net core code:
[Authorize(Policy = "Admin")]
[DisableRequestSizeLimit]
[HttpPost("Download", Name = "Download")]
public async Task<IActionResult> Download()
{
// get files list then start creating the temp folder and zipped folder
var archive = Path.Combine(Directory.GetDirectoryRoot("wwwroot"), #"home\" + FilePath + #"\temp\" + "filename");
var temp = Path.Combine(Directory.GetDirectoryRoot("wwwroot"), #"home\" + FilePath + #"\temp");
if (!System.IO.Directory.Exists(temp))
{
Directory.CreateDirectory(temp);
}
Directory.CreateDirectory(archive);
try
{
foreach (var file_id in filelist)
{
var path = file.path;
if (!System.IO.File.Exists(Path.Combine(archive, Path.GetFileName(path)))) {
System.IO.File.Copy(path, Path.Combine(archive, Path.GetFileName(path)));
}
}
var archivezip = Path.Combine(Directory.GetDirectoryRoot("wwwroot"), #"home\" + FilePath + #"\temp\" + "filename" + ".zip");
// create a new archive
ZipFile.CreateFromDirectory(archive, archivezip);
var memory = new MemoryStream();
using (var stream = new FileStream(archivezip, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
Directory.EnumerateFiles(archive).ToList().ForEach(f => System.IO.File.Delete(f));
Directory.EnumerateDirectories(archive).ToList().ForEach(f => System.IO.Directory.Delete(f, true));
Directory.Delete(archive);
System.IO.File.Delete(archivezip);
return File(memory, "application/octet-stream","filename.zip");
}
catch (Exception ex)
{
return new BadRequestObjectResult(ex.Message);
}
}
please note that in the future not only angular app will use the API, but mobile apps will also be added
I do a similar thing (I guess everyone does because that's apparently how it's done with Angular). I just do a basic unlimited loading spinner because, so far, I haven't needed to worry about tracking the progress.
However, there are various guides to handling this out there for you to follow.
It seems to boil down to changing your request from a simple, standard get/post request to one that listens to the events of the response.
From the linked article:
this.http.get(url, {
reportProgress: true,
observe: 'events',
responseType: 'blob'
})
Important parts there are to ensure it wants progress reports and to observe events. Once the request is observing events, then you need to handle observing them (obviously).
That, however, is a longer more involved part of the article. Go forth and good luck.
Edit: Ah, the issue is actually API side you're worried about. Fair enough, there are a variety of similar questions that might be of use then.
I created an ASP.NET Core api controller which return a FileStreamResult object. (I can change the type of result if needed)
Here is the code of the Get function:
[HttpGet("[action]/{p_gInspectionID}/{p_nIndex}")]
public async Task<FileStreamResult> GetInspectionPictureToDownload(Guid p_gInspectionID, int p_nIndex)
{
var l_strFilePath = await GetPictureFilePathAsync(p_gInspectionID, p_nIndex);
using (var l_sReader = System.IO.File.OpenRead(l_strFilePath))
{
return (File(l_sReader, "image/jpeg"));
}
}
Now I need to consume this result in the Blazor (Webassembly) client side application.
My goal is to have a button to launch the download of the file in the browser when user clicks on it.
This should launch download functionnality of the browser.
Is it possible to achieve this in Blazor client application ?
I was trying to do the same thing, but my API was authorized, so after reading this article I end up downloading the bytes in the web assembly application and use JavaScript to download the file from the bytes.
function downloadFromByteArray(options: {
byteArray: string,
fileName: string,
contentType: string
}): void {
// Convert base64 string to numbers array.
const numArray = atob(options.byteArray).split('').map(c => c.charCodeAt(0));
// Convert numbers array to Uint8Array object.
const uint8Array = new Uint8Array(numArray);
// Wrap it by Blob object.
const blob = new Blob([uint8Array], { type: options.contentType });
// Create "object URL" that is linked to the Blob object.
const url = URL.createObjectURL(blob);
// Invoke download helper function that implemented in
// the earlier section of this article.
downloadFromUrl({ url: url, fileName: options.fileName });
// At last, release unused resources.
URL.revokeObjectURL(url);
}
here is how I solved the problem. In fact the solution was really straightforward. Thank you #Data Juggler for pointing me in the right direction.
My Blazor solution holds two project:
the server side API (Blazor server)
the client side (Blazor WebAssembly).
Here is the code for the server side:
[AllowAnonymous]
[HttpGet("[action]/{p_strPictureFilePath}")]
public IActionResult GetInspectionPicture(string p_strPictureFilePath)
{
var l_sReader = System.IO.File.OpenRead(p_strPictureFilePath);
return (File(l_sReader, "application/octet-stream", Path.GetFileName(p_strPictureFilePath)));
}
... and the code on the client side:
Added this script in client-shared.js file:
window.downloadInspectionPicture = function (p_strServerFilePath)
{
var link = document.createElement('a');
link.href = 'api/Data/GetInspectionPicture/' + this.encodeURIComponent(p_strServerFilePath);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
of course, a reference to that file is present in index.html:
<script src="client-shared.js"></script>
And finally, added a link in the razor file, and invoke script when link is clicked:
Download
#code
{
[Inject]
IJSRuntime ThisJSRuntime { get; set; }
private async Task DownloadPictureAsync()
{
await ThisJSRuntime.InvokeVoidAsync("downloadInspectionPicture", ServerFilePath);
}
}
Hope my answer is clear and can be useful to someone
I don't know if in fact your way is possible, but what I do is similar for my site https://pixeldatabase.net .
The user clicks the download button, and I show a link like this:
public async void Download()
{
// Set the ImagePath
DownloadLink = ImagePath;
}
Then on the page, I just show a Download link conditionallay:
#if (HasDownloadLink)
{
<a class="downloadlink" download="#FileName" href="#DownloadLink"
target="_blank">Download</a>
}
I'm developing a website with ASP.NET MVC 5 + Web API. One of the requirements is that users must be able to download a large zip file, which is created on the fly.
Because I immediately want to show progress of the user, my idea was to use a PushStreamContent with a callback in the resonse. The callback creates the zipfile and streams it to the response.
When I implement this as follows, starting from an empty ASP.NET MVC + Web API project, it works as expected. As soon as the result is returned to the client, the callback gets invoked and
the zipfile is streamed to the client. So the user can see progress as soon as the callback creates the zip archive and add files to it.
[RoutePrefix("api/download")]
public class DownloadController : ApiController
{
[HttpGet]
public HttpResponseMessage Get()
{
var files = new DirectoryInfo(#"c:\tempinput").GetFiles();
var pushStreamContent = new PushStreamContent(async (outputStream, httpContext, transportContext) =>
{
using (var zipOutputStream = new ZipOutputStream(outputStream))
{
zipOutputStream.CompressionLevel = CompressionLevel.BestCompression;
foreach (var file in files)
{
zipOutputStream.PutNextEntry(file.Name);
using (var stream = File.OpenRead(file.FullName))
{
await stream.CopyToAsync(zipOutputStream);
}
}
}
});
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = pushStreamContent
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition =
new ContentDispositionHeaderValue("attachment") {FileName = "MyZipfile.zip"};
return response;
}
}
Now, I have to integrate this in an existing website, which is configured to use Microsoft.Owin.OwinMiddleware. I used the same code as pasted above, but now the behavior is different: during the creation of the zipfile, it 's not streamed to the response, but only downloaded when the creation of the zip has finished. So the user doesn't see any progress during the creation of the file.
I also tried a different approach in my Web API + Owin project, as described here: (generate a Zip file from azure blob storage files).
In an empty Asp.NET MVC project (without OWIN middleware), this works exactly as expected, but when OWIN is involved, I get this HTTPException and stacktrace:
System.Web.HttpException: 'Server cannot set status after HTTP headers have been sent.'
System.Web.dll!System.Web.HttpResponse.StatusCode.set(int value) Unknown
System.Web.dll!System.Web.HttpResponseWrapper.StatusCode.set(int value) Unknown
Microsoft.Owin.Host.SystemWeb.dll!Microsoft.Owin.Host.SystemWeb.OwinCallContext.Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.IPropertySource.SetResponseStatusCode(int value) Unknown
It seems that OWIN wants to set a response status, although that was already done in my Get() method (HttpResponseMessage(HttpStatusCode.OK)).
Any suggestions how to fix this or ideas for a different approach?
Thanks a lot!
I have PDF file placed on different (FILE-Server) server machine, and the IIS machine on which my MVC application is hosted have rights to that File-Server. From IIS machine i can access the file through following URI:
file://file-server/data-folder/pdf/19450205.pdf
I want to enable my MVC app's users to download their respective files by clicking on download link or button. So probably i would have to write some Action for that link/button.
I tried to use File return type for my Action method in following way:
public ActionResult FileDownload()
{
string filePth = #"file://file-server/data-folder/pdf/19450205.pdf";
return File(filePth , "application/pdf");
}
but the above code gives exception of URI not supported.
I also tried to use FileStream to read bytes inside array return that bytes towards download, but FileStream also gives error of not proper "Virtual Path" as the file is not placed inside virtual path, its on separate server.
public ActionResult Download()
{
var document = = #"file://file-server/data-folder/pdf/19450205.pdf";
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = document.FileName,
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = false,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(document.Data, document.ContentType);
}
Thanks for the replies, but both suggestion did not work.
as file needs to be accessed over URI, using FileInfo gives error: URI formats are not supported.
I managed to get this done through following mechanism:
public ActionResult FaxFileDownload()
{
string filePth = #"file://file-server/data-folder/pdf/19450205.pdf";
WebClient wc = new WebClient();
Stream s = wc.OpenRead(filePth);
return File(s, "application/pdf");
}
Thanks to All.
I am required to integrate a signature pad into an intranet (MVC4) application allowing people to apply electronic signatures to system generated documents. Unfortunately, the signature pad I've been given only has a COM/ActiveX API, so I've written a short Windows Forms application that will allow the user to capture the signature and upload it to the server. When it is uploaded, I need the MVC4 action to associate the signature image with a specified document entity sent by the Windows Forms request. So, say I have this model:
public class DocumentToSign {
public int DocumentId { get; set; }
public int DocumentTypeId { get; set; }
}
Then I have this action to receive the uploaded image:
[HttpPost]
public ActionResult UploadSignature(DocumentToSign doc, HttpPostedFileBase signature)
{
//do stuff and catch errors to report back to winforms app
return Json(new {Success = true, Error = string.Empty});
}
Then, the code to upload the image:
var doc = new DocumentToSign{ DocumentId = _theId, DocumentTypeId = _theType };
var fileName = SaveTheSignature();
var url = GetTheUrl();
using(var request = new WebClient())
{
request.Headers.Add("enctype", "multipart/form-data");
foreach(var prop in doc.GetType().GetProperties())
{
request.QueryString.Add(prop.Name, Convert.ToString(prop.GetValue(doc, null)));
}
var responseBytes = request.UploadFile(url, fileName);
//deserialize resulting Json, etc.
}
The model binder seems to pick up the DocumentToSign class without any problems, but the HttpPostedFileBase is always null. I know that I need to somehow tell the model binder that the uploaded image is the signature parameter in the action, but I can't figure out how to do it. I tried using UploadValues with a NameValueCollection, but NameValueCollection only allows the value to be a string, so the image (even as a byte[]) can't be part of that.
Is it possible to upload a file as well as a model to the same action from outside of the actual MVC4 application? Should I be using something other than HttpPostedFileBase? Other than the WebClient? I am at a loss.
var responseBytes = request.UploadFile(url, fileName); is not sending your file in the format your controller expect.
HttpPostedFileBase works with multipart/form-data POST request. But WebClient.UploadFile is not sending a multipart request, it sends file content as a body of request with no other information.
You can save the file by calling Request.SaveAs(filename, false);
or you have to change the way you are sending the file. But I don't think WebClient support sending multipart requests.