I'm building a file upload web application using ASP.NET Core 6 and I would like the user to be able to confirm whether or not to overwrite an existing file. The application allows for multiple file uploads
Questions:
where to check if the file exists? (i.e. client-side or on the server)
if on the client-side how to check if file exists on the server?
how to handle the POST request so that it can "wait" for user confirmation?
is this kind of "communication" with the user even possible in ASP.NET Core 6? or should I be using Blazor or Signal R (I'm really new to .NET)
This is my form:
<form method="POST" enctype="multipart/form-data">
<input type="file" multiple name="files">
<button type="submit" class="btn custom-button">Upload Files</button>
</form>
This is the controller action:
[HttpPost]
public async Task<IActionResult> UploadFiles(IEnumerable<IFormFile> files, string folders)
{
foreach (var file in files)
{
string trimmedFileName = String.Concat(file.FileName.Where(c =>!Char.IsWhiteSpace(c)));
var filePath = Path.Combine(folders, trimmedFileName);
if (System.IO.File.Exists(filePath))
{
ViewBag.Error = $"File {trimmedFileName} already exists on the server";
this.Redirect("UploadFiles");
}
using var filestream = System.IO.File.Create(filePath);
await file.CopyToAsync(filestream);
}
}
I have simplified the above code so as not too add too much detail
Attempts
I have tried AJAX calls to stop the POST request and add some logic but I don't seem to be able to access the folder on the server to even check if the file already exists
$("#uploadForm").submit(function(e){
e.preventDefault();
let files = $(".form-control-file")[0].files;
for (let i = 0; i < files.length; i++){
let url = $("#filePathUrl").val() + "/" + files[i].name;
if(url){
$.ajax({
url: url,
type: 'HEAD',
error: function(){
alert(files[i].name + " exists!");
},
success: function(){
alert(files[i].name + " does not exist on server");
I have simplified and removed closing braces from the above code to keep things simple
Here's what you can do,
Use AjaxRequest to submit the form (by preventing default)
In Controller Action save files if all the files are new
Dont save files if there is any duplicate. Send a message to the client stating that these files are duplicate, Do you want to overwrite these files?
if user says "Yes", send the same AjaxRequest again with an additional parameter and Save all the files (with overwriting duplicate files)
If user says "No", then nothing needs to be done
Lets move to code
JS
function processFiles(confirmOverwrite = false){
const files = $(".form-control-file")[0].files;
const formData = new FormData();
files.forEach(file => formData,append('files[]', file));
const folderName = 'Uploads';
const url = $("#filePathUrl").val();
$.ajax({
url: url+'?folders='+folderName+'&confirmOverwrite='+confirmOverwrite,
type: 'POST',
data: formData,
processData: false,
success: (response) =>{
if(response.Status){
alert('Files uploadedn successfully');
}
else{
if(response.Message){
if(confirm(response.Message + ' files already exist. Do you want to overwrite these?')){
processFiles(true);
}
else{
alert('Files not uploaded');
}
}
}
},
error: (a,b,c) => {
console.error({a,b,c});
}
});
}
$("#uploadForm").submit(function(e){
e.preventDefault();
processFiles(false);
});
Controller
[HttpPost]
public async Task<IActionResult> UploadFiles(IEnumerable<IFormFile> files, string folders, bool confirmOverwrite = false)
{
var existingFiles = new List<string>();
var filesToBeSaved = new List<(IFormFile file, string filePath)>();
foreach (var file in files)
{
string trimmedFileName = file.FileName.Replace(" ", "");
var filePath = Path.Combine(folders, trimmedFileName);
if (System.IO.File.Exists(filePath) && !confirmOverwrite)
{
existingFiles.Add(file.FileName + "("+ trimmedFileName + ")");
}
else
{
filesToBeSaved.Add((file, filePath));
}
}
if (existingFiles.Any())
{
return Json(new { Status = false, Message = string.Join(", ", existingFiles) });
}
else
{
foreach (var (file, filePath) in filesToBeSaved)
{
using var filestream = System.IO.File.Create(filePath);
await file.CopyToAsync(filestream);
}
return Json(new { Status = true, Message = "Success" });
}
}
Your POST to upload the file could return a 409 Conflict status if a file already exists. The UI would then detect this on first posting, and show a popup. If the user selects 'continue', then the POST could include a query parameter to say the user has confirmed the OK is allowed. Something like this:
[HttpPost]
public async Task<IActionResult> UploadFiles(bool? allowOverwrite, IEnumerable<IFormFile> files, string folders)
{
foreach (var file in files)
{
string trimmedFileName = String.Concat(file.FileName.Where(c => !Char.IsWhiteSpace(c)));
var filePath = Path.Combine(folders, trimmedFileName);
if (!allowOverwrite.GetDefaultValue() && System.IO.File.Exists(filePath)) < ---
{
ViewBag.Error = $"File {trimmedFileName} already exists on the server";
return Conflict(); < ---
}
using var filestream = System.IO.File.Create(filePath);
await file.CopyToAsync(filestream);
}
}
I have a large(ish) form in MVC.
I need to be able to generate an excel file containing data from a subset of that form.
The tricky bit is that this shouldn't affect the rest of the form and so I want to do it via AJAX. I've come across a few questions on SO that seem to be related, but I can't quite work out what the answers mean.
This one seems the closest to what I'm after: asp-net-mvc-downloading-excel - but I'm not sure I understand the response, and it is a couple years old now. I also came across another article (can't find it anymore) about using an iframe to handle the file download, but I'm not sure how to get this working with MVC.
My excel file returns fine if I'm doing a full post back but I can't get it working with AJAX in mvc.
You can't directly return a file for download via an AJAX call so, an alternative approach is to to use an AJAX call to post the related data to your server. You can then use server side code to create the Excel File (I would recommend using EPPlus or NPOI for this although it sounds as if you have this part working).
UPDATE September 2016
My original answer (below) was over 3 years old, so I thought I would update as I no longer create files on the server when downloading files via AJAX however, I have left the original answer as it may be of some use still depending on your specific requirements.
A common scenario in my MVC applications is reporting via a web page that has some user configured report parameters (Date Ranges, Filters etc.). When the user has specified the parameters they post them to the server, the report is generated (say for example an Excel file as output) and then I store the resulting file as a byte array in the TempData bucket with a unique reference. This reference is passed back as a Json Result to my AJAX function that subsequently redirects to separate controller action to extract the data from TempData and download to the end users browser.
To give this more detail, assuming you have a MVC View that has a form bound to a Model class, lets call the Model ReportVM.
First, a controller action is required to receive the posted model, an example would be:
public ActionResult PostReportPartial(ReportVM model){
// Validate the Model is correct and contains valid data
// Generate your report output based on the model parameters
// This can be an Excel, PDF, Word file - whatever you need.
// As an example lets assume we've generated an EPPlus ExcelPackage
ExcelPackage workbook = new ExcelPackage();
// Do something to populate your workbook
// Generate a new unique identifier against which the file can be stored
string handle = Guid.NewGuid().ToString();
using(MemoryStream memoryStream = new MemoryStream()){
workbook.SaveAs(memoryStream);
memoryStream.Position = 0;
TempData[handle] = memoryStream.ToArray();
}
// Note we are returning a filename as well as the handle
return new JsonResult() {
Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" }
};
}
The AJAX call that posts my MVC form to the above controller and receives the response looks like this:
$ajax({
cache: false,
url: '/Report/PostReportPartial',
data: _form.serialize(),
success: function (data){
var response = JSON.parse(data);
window.location = '/Report/Download?fileGuid=' + response.FileGuid
+ '&filename=' + response.FileName;
}
})
The controller action to handle the downloading of the file:
[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{
if(TempData[fileGuid] != null){
byte[] data = TempData[fileGuid] as byte[];
return File(data, "application/vnd.ms-excel", fileName);
}
else{
// Problem - Log the error, generate a blank file,
// redirect to another controller action - whatever fits with your application
return new EmptyResult();
}
}
One other change that could easily be accommodated if required is to pass the MIME Type of the file as a third parameter so that the one Controller action could correctly serve a variety of output file formats.
This removes any need for any physical files to created and stored on the server, so no housekeeping routines required and once again this is seamless to the end user.
Note, the advantage of using TempData rather than Session is that once TempData is read the data is cleared so it will be more efficient in terms of memory usage if you have a high volume of file requests. See TempData Best Practice.
ORIGINAL Answer
You can't directly return a file for download via an AJAX call so, an alternative approach is to to use an AJAX call to post the related data to your server. You can then use server side code to create the Excel File (I would recommend using EPPlus or NPOI for this although it sounds as if you have this part working).
Once the file has been created on the server pass back the path to the file (or just the filename) as the return value to your AJAX call and then set the JavaScript window.location to this URL which will prompt the browser to download the file.
From the end users perspective, the file download operation is seamless as they never leave the page on which the request originates.
Below is a simple contrived example of an ajax call to achieve this:
$.ajax({
type: 'POST',
url: '/Reports/ExportMyData',
data: '{ "dataprop1": "test", "dataprop2" : "test2" }',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function (returnValue) {
window.location = '/Reports/Download?file=' + returnValue;
}
});
url parameter is the Controller/Action method where your code will create the Excel file.
data parameter contains the json data that would be extracted from the form.
returnValue would be the file name of your newly created Excel file.
The window.location command redirects to the Controller/Action method that actually returns your file for download.
A sample controller method for the Download action would be:
[HttpGet]
public virtual ActionResult Download(string file)
{
string fullPath = Path.Combine(Server.MapPath("~/MyFiles"), file);
return File(fullPath, "application/vnd.ms-excel", file);
}
My 2 cents - you don't need to store the excel as a physical file on the server - instead, store it in the (Session) Cache. Use a uniquely generated name for your Cache variable (that stores that excel file) - this will be the return of your (initial) ajax call. This way you don't have to deal with file access issues, managing (deleting) the files when not needed, etc. and, having the file in the Cache, is faster to retrieve it.
I was recently able to accomplish this in MVC (although there was no need to use AJAX) without creating a physical file and thought I'd share my code:
Super simple JavaScript function (datatables.net button click triggers this):
function getWinnersExcel(drawingId) {
window.location = "/drawing/drawingwinnersexcel?drawingid=" + drawingId;
}
C# Controller code:
public FileResult DrawingWinnersExcel(int drawingId)
{
MemoryStream stream = new MemoryStream(); // cleaned up automatically by MVC
List<DrawingWinner> winnerList = DrawingDataAccess.GetWinners(drawingId); // simple entity framework-based data retrieval
ExportHelper.GetWinnersAsExcelMemoryStream(stream, winnerList, drawingId);
string suggestedFilename = string.Format("Drawing_{0}_Winners.xlsx", drawingId);
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", suggestedFilename);
}
In the ExportHelper class I do use a 3rd party tool (GemBox.Spreadsheet) to generate the Excel file and it has a Save to Stream option. That being said, there are a number of ways to create Excel files that can easily be written to a memory stream.
public static class ExportHelper
{
internal static void GetWinnersAsExcelMemoryStream(MemoryStream stream, List<DrawingWinner> winnerList, int drawingId)
{
ExcelFile ef = new ExcelFile();
// lots of excel worksheet building/formatting code here ...
ef.SaveXlsx(stream);
stream.Position = 0; // reset for future read
}
}
In IE, Chrome, and Firefox, the browser prompts to download the file and no actual navigation occurs.
First Create the controller action that will create the Excel File
[HttpPost]
public JsonResult ExportExcel()
{
DataTable dt = DataService.GetData();
var fileName = "Excel_" + DateTime.Now.ToString("yyyyMMddHHmm") + ".xls";
//save the file to server temp folder
string fullPath = Path.Combine(Server.MapPath("~/temp"), fileName);
using (var exportData = new MemoryStream())
{
//I don't show the detail how to create the Excel, this is not the point of this article,
//I just use the NPOI for Excel handler
Utility.WriteDataTableToExcel(dt, ".xls", exportData);
FileStream file = new FileStream(fullPath, FileMode.Create, FileAccess.Write);
exportData.WriteTo(file);
file.Close();
}
var errorMessage = "you can return the errors in here!";
//return the Excel file name
return Json(new { fileName = fileName, errorMessage = "" });
}
then create the Download action
[HttpGet]
[DeleteFileAttribute] //Action Filter, it will auto delete the file after download,
//I will explain it later
public ActionResult Download(string file)
{
//get the temp folder and file path in server
string fullPath = Path.Combine(Server.MapPath("~/temp"), file);
//return the file for download, this is an Excel
//so I set the file content type to "application/vnd.ms-excel"
return File(fullPath, "application/vnd.ms-excel", file);
}
if you want to delete the file after downloaded create this
public class DeleteFileAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Flush();
//convert the current filter context to file and get the file path
string filePath = (filterContext.Result as FilePathResult).FileName;
//delete the file after download
System.IO.File.Delete(filePath);
}
}
and finally ajax call from you MVC Razor view
//I use blockUI for loading...
$.blockUI({ message: '<h3>Please wait a moment...</h3>' });
$.ajax({
type: "POST",
url: '#Url.Action("ExportExcel","YourController")', //call your controller and action
contentType: "application/json; charset=utf-8",
dataType: "json",
}).done(function (data) {
//console.log(data.result);
$.unblockUI();
//get the file name for download
if (data.fileName != "") {
//use window.location.href for redirect to download action for download the file
window.location.href = "#Url.RouteUrl(new
{ Controller = "YourController", Action = "Download"})/?file=" + data.fileName;
}
});
I used the solution posted by CSL but I would recommend you dont store the file data in Session during the whole session. By using TempData the file data is automatically removed after the next request (which is the GET request for the file). You could also manage removal of the file data in Session in download action.
Session could consume much memory/space depending on SessionState storage and how many files are exported during the session and if you have many users.
I've updated the serer side code from CSL to use TempData instead.
public ActionResult PostReportPartial(ReportVM model){
// Validate the Model is correct and contains valid data
// Generate your report output based on the model parameters
// This can be an Excel, PDF, Word file - whatever you need.
// As an example lets assume we've generated an EPPlus ExcelPackage
ExcelPackage workbook = new ExcelPackage();
// Do something to populate your workbook
// Generate a new unique identifier against which the file can be stored
string handle = Guid.NewGuid().ToString()
using(MemoryStream memoryStream = new MemoryStream()){
workbook.SaveAs(memoryStream);
memoryStream.Position = 0;
TempData[handle] = memoryStream.ToArray();
}
// Note we are returning a filename as well as the handle
return new JsonResult() {
Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" }
};
}
[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{
if(TempData[fileGuid] != null){
byte[] data = TempData[fileGuid] as byte[];
return File(data, "application/vnd.ms-excel", fileName);
}
else{
// Problem - Log the error, generate a blank file,
// redirect to another controller action - whatever fits with your application
return new EmptyResult();
}
}
using ClosedXML.Excel;
public ActionResult Downloadexcel()
{
var Emplist = JsonConvert.SerializeObject(dbcontext.Employees.ToList());
DataTable dt11 = (DataTable)JsonConvert.DeserializeObject(Emplist, (typeof(DataTable)));
dt11.TableName = "Emptbl";
FileContentResult robj;
using (XLWorkbook wb = new XLWorkbook())
{
wb.Worksheets.Add(dt11);
using (MemoryStream stream = new MemoryStream())
{
wb.SaveAs(stream);
var bytesdata = File(stream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "myFileName.xlsx");
robj = bytesdata;
}
}
return Json(robj, JsonRequestBehavior.AllowGet);
}
$.ajax({
type: "GET",
url: "/Home/Downloadexcel/",
contentType: "application/json; charset=utf-8",
data: null,
success: function (Rdata) {
debugger;
var bytes = new Uint8Array(Rdata.FileContents);
var blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "myFileName.xlsx";
link.click();
},
error: function (err) {
}
});
The accepted answer didn't quite work for me as I got a 502 Bad Gateway result from the ajax call even though everything seemed to be returning fine from the controller.
Perhaps I was hitting a limit with TempData - not sure, but I found that if I used IMemoryCache instead of TempData, it worked fine, so here is my adapted version of the code in the accepted answer:
public ActionResult PostReportPartial(ReportVM model){
// Validate the Model is correct and contains valid data
// Generate your report output based on the model parameters
// This can be an Excel, PDF, Word file - whatever you need.
// As an example lets assume we've generated an EPPlus ExcelPackage
ExcelPackage workbook = new ExcelPackage();
// Do something to populate your workbook
// Generate a new unique identifier against which the file can be stored
string handle = Guid.NewGuid().ToString();
using(MemoryStream memoryStream = new MemoryStream()){
workbook.SaveAs(memoryStream);
memoryStream.Position = 0;
//TempData[handle] = memoryStream.ToArray();
//This is an equivalent to tempdata, but requires manual cleanup
_cache.Set(handle, memoryStream.ToArray(),
new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(10)));
//(I'd recommend you revise the expiration specifics to suit your application)
}
// Note we are returning a filename as well as the handle
return new JsonResult() {
Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" }
};
}
AJAX call remains as with the accepted answer (I made no changes):
$ajax({
cache: false,
url: '/Report/PostReportPartial',
data: _form.serialize(),
success: function (data){
var response = JSON.parse(data);
window.location = '/Report/Download?fileGuid=' + response.FileGuid
+ '&filename=' + response.FileName;
}
})
The controller action to handle the downloading of the file:
[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{
if (_cache.Get<byte[]>(fileGuid) != null)
{
byte[] data = _cache.Get<byte[]>(fileGuid);
_cache.Remove(fileGuid); //cleanup here as we don't need it in cache anymore
return File(data, "application/vnd.ms-excel", fileName);
}
else
{
// Something has gone wrong...
return View("Error"); // or whatever/wherever you want to return the user
}
}
...
Now there is some extra code for setting up MemoryCache...
In order to use "_cache" I injected in the constructor for the controller like so:
using Microsoft.Extensions.Caching.Memory;
namespace MySolution.Project.Controllers
{
public class MyController : Controller
{
private readonly IMemoryCache _cache;
public LogController(IMemoryCache cache)
{
_cache = cache;
}
//rest of controller code here
}
}
And make sure you have the following in ConfigureServices in Startup.cs:
services.AddDistributedMemoryCache();
$.ajax({
global: false,
url: SitePath + "/User/ExportTeamMembersInExcel",
"data": { 'UserName': UserName, 'RoleId': RoleId, UserIds: AppraseeId },
"type": "POST",
"dataType": "JSON",
"success": function (result) {
var bytes = new Uint8Array(result.FileContents);
var blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "myFileName.xlsx";
link.click();
},
"error": function () {
alert("error");
}
})
[HttpPost]
public JsonResult ExportTeamMembersInExcel(string UserName, long? RoleId, string[] UserIds)
{
MemoryStream stream = new MemoryStream();
FileContentResult robj;
DataTable data = objuserservice.ExportTeamToExcel(UserName, RoleId, UserIds);
using (XLWorkbook wb = new XLWorkbook())
{
wb.Worksheets.Add(data, "TeamMembers");
using (stream)
{
wb.SaveAs(stream);
}
}
robj = File(stream.ToArray(), System.Net.Mime.MediaTypeNames.Application.Octet, "TeamMembers.xlsx");
return Json(robj, JsonRequestBehavior.AllowGet);
}
I may sound quite naive, and may attract quite a criticism, but here's how I did it,
(It doesn't involve ajax for export, but it doesn't do a full postback either )
Thanks for this post and this answer.
Create a simple controller
public class HomeController : Controller
{
/* A demo action
public ActionResult Index()
{
return View(model);
}
*/
[HttpPost]
public FileResult ExportData()
{
/* An example filter
var filter = TempData["filterKeys"] as MyFilter;
TempData.Keep(); */
var someList = db.GetDataFromDb(/*filter*/) // filter as an example
/*May be here's the trick, I'm setting my filter in TempData["filterKeys"]
in an action,(GetFilteredPartial() illustrated below) when 'searching' for the data,
so do not really need ajax here..to pass my filters.. */
//Some utility to convert list to Datatable
var dt = Utility.ConvertToDataTable(someList);
// I am using EPPlus nuget package
using (ExcelPackage pck = new ExcelPackage())
{
ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Sheet1");
ws.Cells["A1"].LoadFromDataTable(dt, true);
using (var memoryStream = new MemoryStream())
{
pck.SaveAs(memoryStream);
return File(memoryStream.ToArray(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"ExportFileName.xlsx");
}
}
}
//This is just a supporting example to illustrate setting up filters ..
/* [HttpPost]
public PartialViewResult GetFilteredPartial(MyFilter filter)
{
TempData["filterKeys"] = filter;
var filteredData = db.GetConcernedData(filter);
var model = new MainViewModel();
model.PartialViewModel = filteredData;
return PartialView("_SomePartialView", model);
} */
}
And here are the Views..
/*Commenting out the View code, in order to focus on the imp. code
#model Models.MainViewModel
#{Layout...}
Some code for, say, a partial View
<div id="tblSampleBody">
#Html.Partial("_SomePartialView", Model.PartialViewModel)
</div>
*/
//The actual part.. Just **posting** this bit of data from the complete View...
//Here, you are not posting the full Form..or the complete View
#using (Html.BeginForm("ExportData", "Home", FormMethod.Post))
{
<input type="submit" value="Export Data" />
}
//...
//</div>
/*And you may require to pass search/filter values.. as said in the accepted answer..
That can be done while 'searching' the data.. and not while
we need an export..for instance:-
<script>
var filterData = {
SkipCount: someValue,
TakeCount: 20,
UserName: $("#UserName").val(),
DepartmentId: $("#DepartmentId").val(),
}
function GetFilteredData() {
$("#loader").show();
filterData.SkipCount = 0;
$.ajax({
url: '#Url.Action("GetFilteredPartial","Home")',
type: 'POST',
dataType: "html",
data: filterData,
success: function (dataHTML) {
if ((dataHTML === null) || (dataHTML == "")) {
$("#tblSampleBody").html('<tr><td>No Data Returned</td></tr>');
$("#loader").hide();
} else {
$("#tblSampleBody").html(dataHTML);
$("#loader").hide();
}
}
});
}
</script>*/
The whole point of the trick seems that, we are posting a form (a part of the Razor View ) upon which we are calling an Action method, which returns: a FileResult, and this FileResult returns the Excel File..
And for posting the filter values, as said, ( and if you require to), I am making a post request to another action, as has been attempted to describe..
This thread helped me create my own solution that I will share here. I was using a GET ajax request at first without issues but it got to a point where the request URL length was exceeded so I had to swith to a POST.
The javascript uses JQuery file download plugin and consists of 2 succeeding calls. One POST (To send params) and one GET to retreive the file.
function download(result) {
$.fileDownload(uri + "?guid=" + result,
{
successCallback: onSuccess.bind(this),
failCallback: onFail.bind(this)
});
}
var uri = BASE_EXPORT_METADATA_URL;
var data = createExportationData.call(this);
$.ajax({
url: uri,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
success: download.bind(this),
fail: onFail.bind(this)
});
Server side
[HttpPost]
public string MassExportDocuments(MassExportDocumentsInput input)
{
// Save query for file download use
var guid = Guid.NewGuid();
HttpContext.Current.Cache.Insert(guid.ToString(), input, null, DateTime.Now.AddMinutes(5), Cache.NoSlidingExpiration);
return guid.ToString();
}
[HttpGet]
public async Task<HttpResponseMessage> MassExportDocuments([FromUri] Guid guid)
{
//Get params from cache, generate and return
var model = (MassExportDocumentsInput)HttpContext.Current.Cache[guid.ToString()];
..... // Document generation
// to determine when file is downloaded
HttpContext.Current
.Response
.SetCookie(new HttpCookie("fileDownload", "true") { Path = "/" });
return FileResult(memoryStream, "documents.zip", "application/zip");
}
CSL's answer was implemented in a project I'm working on but the problem I incurred was scaling out on Azure broke our file downloads. Instead, I was able to do this with one AJAX call:
SERVER
[HttpPost]
public FileResult DownloadInvoice(int id1, int id2)
{
//necessary to get the filename in the success of the ajax callback
HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
byte[] fileBytes = _service.GetInvoice(id1, id2);
string fileName = "Invoice.xlsx";
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
CLIENT
(modified version of Handle file download from ajax post)
$("#downloadInvoice").on("click", function() {
$("#loaderInvoice").removeClass("d-none");
var xhr = new XMLHttpRequest();
var params = [];
xhr.open('POST', "#Html.Raw(Url.Action("DownloadInvoice", "Controller", new { id1 = Model.Id1, id2 = Model.Id2 }))", true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
if (this.status === 200) {
var filename = "";
var disposition = xhr.getResponseHeader('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, '');
}
var type = xhr.getResponseHeader('Content-Type');
var blob = typeof File === 'function'
? new File([this.response], filename, { type: type })
: new Blob([this.response], { type: type });
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location = downloadUrl;
}
setTimeout(function() {
URL.revokeObjectURL(downloadUrl);
$("#loaderInvoice").addClass("d-none");
}, 100); // cleanup
}
}
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));
});
This works for me. Make sure you return a File from your controller action with contentType as "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" and file name as e.g. "List.xlsx" which should be the same as in the AJAX success call. I have used ClosedXML NuGet package to generate the excel file.
$.ajax({
url: "Home/Export",
type: 'GET',
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
xhrFields: { responseType: 'blob' },
success: function (data) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'List.xlsx';
a.click();
window.URL.revokeObjectURL(url);
}
});
I am using Asp.Net WebForm and just I wanna to download a file from server side. There is a lot article but I cannot find just basic answer.
Now, I tried a basic way and got it.
That's my problem.
I have to create a lot of input button dynamically on runtime. And I want to add each button to download button with giving an unique fileNumber.
I create each button like this:
fragment += "<div><input type=\"button\" value=\"Create Excel\" onclick=\"CreateExcelFile(" + fileNumber + ");\" /></div>";
Each button call this ajax method.
$.ajax({
type: 'POST',
url: 'index.aspx/CreateExcelFile',
data: jsonData,
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function (returnValue) {
window.location = '/Reports/Downloads/' + returnValue.d;
}
});
Then I wrote a basic simple method.
[WebMethod]
public static string CreateExcelFile2(string fileNumber)
{
string filePath = string.Format(#"Form_{0}.xlsx", fileNumber);
return filePath;
}
I am generating this Form_1, Form_2, Form_3.... And I am going to delete this old files with another program. But if there is a way to just sending byte array to download file like using Response. I wanna to use it.
I hope this will be usefull for anyone.
On Submit form
public ActionResult ExportXls()
{
var filePath="";
CommonHelper.WriteXls(filePath, "Text.xls");
}
public static void WriteXls(string filePath, string targetFileName)
{
if (!String.IsNullOrEmpty(filePath))
{
HttpResponse response = HttpContext.Current.Response;
response.Clear();
response.Charset = "utf-8";
response.ContentType = "text/xls";
response.AddHeader("content-disposition", string.Format("attachment; filename={0}", targetFileName));
response.BinaryWrite(File.ReadAllBytes(filePath));
response.End();
}
}
I want to upload file using a WebApi by making an ajax call and the file will save into database. I tried the code given in the
this link. Here, it is saved the received data to hard drive as a file with no extension specified but I want to do something like when I'm saving the file to Database I also want to save the file name and the extension cause later if I need to download the file I can provide the file name and extension with it. And in the link the file is saved to hard drive as a file but is there any way that I can directly save the file to DB.
The answer has several parts.
First, to upload the file, you can use a view with code like this:
#using (Html.BeginForm())
{
<input type="file" value="Choose a file"/>
<br/>
<input type="button" value="Upload" id="upload"/>
}
#section scripts
{
<script type="text/javascript">
$(document).ready(function() {
$('#upload').click(function () {
var data = new FormData();
var file = $('form input[type=file]')[0].files[0];
data.append('file',file);
$.ajax({
url: '/Api/File/Upload',
processData: false,
contentType: false,
data: data,
type: 'POST'
}).done(function(result) {
alert(result);
}).fail(function(a, b, c) {
console.log(a, b, c);
});
});
});
</script>
}
Second, to receive this data, create a controller, with a method like this:
public class FileController : ApiController
{
[HttpPost]
public async Task<string> Upload()
{
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
// extract file name and file contents
var fileNameParam = provider.Contents[0].Headers.ContentDisposition.Parameters
.FirstOrDefault(p => p.Name.ToLower() == "filename");
string fileName = (fileNameParam == null) ? "" : fileNameParam.Value.Trim('"');
byte[] file = await provider.Contents[0].ReadAsByteArrayAsync();
// Here you can use EF with an entity with a byte[] property, or
// an stored procedure with a varbinary parameter to insert the
// data into the DB
var result
= string.Format("Received '{0}' with length: {1}", fileName, file.Length);
return result;
}
}
Third, by default the maximum upload size is limited. You can overcome this limitations modifying web.config:
Add maxRequestLength="max size in bytes" in <configuration><system.web><httpRuntime>. (Or create this lement if it doesn't exist):
Add maxAllowedContentLength to <configuration><system.web><security><requestFiltering><requestLimits> element (or create this element if it doesn't exist)
These entries look like this:
<configuration>
<system.web>
<!-- kilobytes -->
<httpRuntime targetFramework="4.5" maxRequestLength="2000000" />
<configuration>
<system.webServer>
<security>
<requestFiltering>
<!-- bytes -->
<requestLimits maxAllowedContentLength="2000000000"/>
NOTE: you should include this inside a <location> element, so that this limits are only applied to the particular route where the files are uploaded, like this:
<location path="Api/File/Upload">
<system.web>
...
<system.webServer>
...
Beware to modify the root web.config, not the one in the Views folder.
Fourth, as to saving the data in the database, if you use EF, you simply need an entity like this:
public class File
{
public int FileId { get; set; }
public string FileName { get; set; }
public byte[] FileContent { get; set; }
}
Create a new object of this class, add to the context and save changes.
If you use stored procedures, create one which has a varbinary parameter, and pass the byte[] file as value.
A cleaner way to do this using webAPI controller is as follows:
Create a web api controller file:
UploadFileController.cs
public class UploadFileController : ApiController
{
// POST api/<controller>
public HttpResponseMessage Post()
{
HttpResponseMessage result = null;
var httpRequest = HttpContext.Current.Request;
if (httpRequest.Files.Count > 0)
{
var docfiles = new List<string>();
foreach (string file in httpRequest.Files)
{
var postedFile = httpRequest.Files[file];
int hasheddate = DateTime.Now.GetHashCode();
//Good to use an updated name always, since many can use the same file name to upload.
string changed_name = hasheddate.ToString() + "_" + postedFile.FileName;
var filePath = HttpContext.Current.Server.MapPath("~/Images/" + changed_name);
postedFile.SaveAs(filePath); // save the file to a folder "Images" in the root of your app
changed_name = #"~\Images\" + changed_name; //store this complete path to database
docfiles.Add(changed_name);
}
result = Request.CreateResponse(HttpStatusCode.Created, docfiles);
}
else
{
result = Request.CreateResponse(HttpStatusCode.BadRequest);
}
return result;
}
}
To use this webAPI in your markup. Use following:
<input type="hidden" id="insertPicture" />
<input id="insertFileupload" type="file" name="files[]" accept="image/*" data-url="/api/uploadfile" multiple>
<script>
$(function () {
$('#insertFileupload').fileupload({
add: function (e, data) {
var jqXHR = data.submit()
.success(function (result, textStatus, jqXHR) {/* ... */
$('#insertPicture').val(result);
alert("File Uploaded");
})
.error(function (jqXHR, textStatus, errorThrown) {/* ... */
alert(errorThrown);
})
}
});
});
You can change the type of file (extensions to accept) in the "accept" attribute of the input tag.
Hope it will help! Enjoy!
You can't directly save file to the database.
One of the options, apart from saving the file locally, is saving it into the memory stream and then passing it to the database. This question can give you the code example of how you can get the filename and extension and save the file into the memory: Web API: how to access multipart form values when using MultipartMemoryStreamProvider?
I think what you want to achieve here is partly answered in this previous question
Now, about saving directly to database, you should be able to achieve this without saving the file to a hard drive first, normally by taking the stream byte array and putting it into your database entity or row property as a byte[] (array of bytes)
If you want to save the file to a BLOB field in your database, then you can use the code provided in the following post: Saving any file to in the database, just convert it to a byte array?.
The relevant code is below:
public static int databaseFilePut(MemoryStream fileToPut)
{
int varID = 0;
byte[] file = fileToPut.ToArray();
const string preparedCommand = #"
INSERT INTO [dbo].[Raporty]
([RaportPlik])
VALUES
(#File)
SELECT [RaportID] FROM [dbo].[Raporty]
WHERE [RaportID] = SCOPE_IDENTITY()
";
using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
using (var sqlWrite = new SqlCommand(preparedCommand, varConnection))
{
sqlWrite.Parameters.Add("#File", SqlDbType.VarBinary, file.Length).Value = file;
using (var sqlWriteQuery = sqlWrite.ExecuteReader())
while (sqlWriteQuery != null && sqlWriteQuery.Read())
varID = sqlWriteQuery["RaportID"] is int ? (int) sqlWriteQuery["RaportID"] : 0;
}
return varID;
}
You can combine the approach in the link you sent with the code for committing a MemoryStream to the database provided in an answer I linked to.
You will need a specific column in your table to save the actual file name. Basically, you will need a BLOB column for the file content and another TEXT or VARCHAR column for the file name. The link you provided shows a way to obtain the name of the file.
However, as others have pointed out, you should not save files to the database. The most common way of handling file uploads is to save them to some location on the server and commit the path to the saved file to a TEXT or VARCHAR field in the database.
js code as below.
var Sendmodel = new FormData();Sendmodel.append("TemplatePath",$('#fileTemplatePath')[0].files[0]);Sendmodel.append("Name","pradip");
$.ajax({
url: "api/project/SaveCertificateSettings",
type: 'POST',
contentType: false,
processData: false,
data: Sendmodel,
success: function (data, textStatus, xhr) {
},
error: function (xhr, textStatus, errorThrown) {
alert('error');
}
});
WEb api code as below.
public object SaveCertificateSettings()
{
string Name = Convert.ToString(HttpContext.Current.Request.Form["Name"]);
if (HttpContext.Current.Request.Files.AllKeys.Any())
{
// Get the uploaded image from the Files collection
var httpPostedFile = HttpContext.Current.Request.Files["TemplatePath"];
if (httpPostedFile != null)
{
// httpPostedFile.FileName;
// Get the complete file path
}
}
}
I have been working on this whole day. I do not want to use any fancy plugin. My problem is basically this: In asp.net c#, the user can create an instance of an class (let's say student) and each instance has a file (image). I am using AJAX, and the code works fine. When user presses create button, I want to also send the posted file to the Page method, which can can save the file, while adding the record to the db. However, I am not able to get the posted file as FileInfo in Page method but as string. Is there a way to make this work?
function addBadge() {
var badgeName = $('#txtBadgeName').val();
var badgeDesc = $('#txtBadgeDesc').val();
var badgeImage = $('#file_BadgeImage').get().files[0];
$.ajax({
type: "POST",
url: "/Instructor/ManageBadges.aspx/CreateBadge",
data: "{badgeName:'" + badgeName + "', \
badgeImage:'" + badgeImage + "',\
badgeDescription:'" + badgeDesc + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
.....
}
}
});
}
[WebMethod]
public static string CreateBadge(string badgeImage, string badgeName, string badgeDescription)
{
//HERE HOW CAN USE badgeImage ??
//HOW CAN I CONVERT IT TO FileInfo and save ?
Guid badgeId = Guid.NewGuid();
BadgeInfo newbadge = new BadgeInfo();
newbadge.BadgeId = badgeId;
newbadge.BadgeName = badgeName;
newbadge.BadgeDescription = badgeDescription;
}
First, i would try setting the content type to:
contentType:'multipart/form-data'
Then i would read this article about the support for AJAX and file upload.
jQuery Ajax File Upload
It appears that only recently it has started being supported. I know for a fact that when you post a regular form without ajax, it must have the enctype 'multipart/form-data in order to send the file.
I honestly am not familiar with file manipulation on ASP C# side of things but, i know you have some issues before you get to that side.
For those who need an ajax asp.net solution (submitting a form with a file) without using a fancy file upload plugin:
I strongly refer to a recent solution here: File Upload using jQuery AJAX in ASP.NET Web API
The indicated solution uses a controller which handles the fileupload, and the fileupload function of controller is invoked from the ajax. Let's say you have one ajax method for saving the form input to the database. After this ajax is done with success, in the success event, you need to write another piece of ajax code to perform the file upload which is defined in the controller class, as seen below:
[HttpPost]
public KeyValuePair<bool, string> UploadFile()
{
try
{
if (HttpContext.Current.Request.Files.AllKeys.Any())
{
// Get the uploaded image from the Files collection
var httpPostedFile = HttpContext.Current.Request.Files["UploadedImage"];
if (httpPostedFile != null)
{
// Validate the uploaded image(optional)
// Get the complete file path
var fileSavePath = Path.Combine(HttpContext.Current.Server.MapPath("~/Images/UploadedFiles"), httpPostedFile.FileName);
// Save the uploaded file to "UploadedFiles" folder
httpPostedFile.SaveAs(fileSavePath);
return new KeyValuePair<bool, string>(true, "File uploaded successfully.");
}
return new KeyValuePair<bool, string>(true, "Could not get the uploaded file.");
}
return new KeyValuePair<bool, string>(true, "No file found to upload.");
}
catch (Exception ex)
{
return new KeyValuePair<bool, string>(false, "An error occurred while uploading the file. Error Message: " + ex.Message);
}
}
Please refer to the post for the details.
I would like to provide a file download operation by using the jQuery AJAX call with some params under MVC
Example
(javascript)
function DoDownload(startDate) {
$.ajax({
url:"controller/GetFile/",
data: {startDate:startDate}
...
});
}
C# Controller Code
public void GetFile(string startDate) {
var results = doQueryWith(startDate);
// Create file based on results
....
// How do I tell the server to make this a file download??
}
I typically would just make my file download a link such as:
<a h r e f="mycontroller/getfile/1"/>Download</a>
but in the case above the date will be dynamic.
If I don't use ajax, what would be a preferred way to pass in the params to the MVC controller using javascript?
Example:
window.location = "mycontroller/GetFile/" + $("#fromDate").val();
assuming the date is 12-25-2012
Would this produce
mycontroller/GetFile/12/25/2012
would MVC treat this as three params?
What I ended up doing is calling my controller from my javascript like:
var url = "/mycontroller/GetFile?startDate=" + $("#mydate").val() + etc...
window.location = url;
mycontroller.cs
public void GetFile(DateTime startDate)
{
}
My original concern was with the date parameters. I didnt want to have to parse it.
Using the ActionLink helper, you can pass multiple params to your controller:
HtmlHelper.ActionLink(
string linkText,
string actionName,
string controllerName,
object routeValues,
object htmlAttributes
)
So in your case:
#Html.ActionLink("Download file", "GetFile", "MyController", new { startDate = "##" }, new { id="mydownloadlink" })
Using jQuery you can change the value of the startDate in the link with the content of your date picker or textbox.
$("#mydownloadlink").attr("href").replace("##", $("#yourdatetexbox").val);
Then, in your controller, just use one of the other answers here, about FileResult.
Hope this help you...
You can use the File method of controller class to return a file back to the browser.
The below sample returns a pdf file.
public ActionResult GetFile(int id)
{
var fileInfo=repositary.GetFileDedetails(id);
var byteArrayOFFile=fileInfo.FileContentAsByteArray();
return File(byteArrayOFFile,"application/pdf","yourFriendlyName.pdf");
}
Assuming repositary.GetFileDedetails method returns the details of the file from the id.
You may also return the file from a physical location(a path) or a stream. Check all the overloads of the File method and use appropriate one.
This has nothing to do with ajax. this is normal GET request over a browser.
Your controller action method should return a FileResult instead of void. And there is no need to do this via AJAX - in fact, you don't want to do this with AJAX. You'll need the browser involved so it knows to provide a download dialog for the user.
See these links:
Handling an ASP.NET MVC FileResult returned in an (jQuery) Ajax call
File download in Asp.Net MVC 2
I hope this helps.
This works for me. Make sure you return a File from your controller action with contentType as "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" and file name as e.g. "List.xlsx" which should be the same as in the AJAX success call. I have used ClosedXML NuGet package to generate the excel file.
public IActionResult GetFile(DateTime startDate)
{
var results = doQueryWith(startDate);
DataTable dt = new DataTable("Grid");
//populate dt here.
//if you result is a data table you can assign it to dt
dt = results;
string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
string fileName = "List.xlsx";
using (var workbook = new XLWorkbook())
{
workbook.Worksheets.Add(dt);
using (var stream = new MemoryStream())
{
workbook.SaveAs(stream);
workbook.SaveAs(stream);
var content = stream.ToArray();
return File(content, contentType, fileName);
}
}
}
//.cshtml (JQuery AJAX call to the controller action)
$.ajax({
url:"ControllerName/GetFile/",
data: {startDate:startDate}
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
xhrFields: { responseType: 'blob' },
success: function (data) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'List.xlsx';
a.click();
window.URL.revokeObjectURL(url);
}
});