I have to download file from server directory and keep its download count on Database.
However, I got this to work by the following code.
Razor View
Download
Controller
private AppDbContext db = new AppDbContext();
public ActionResult Download(int id)
{
Item item = db.Items.Find(id);
if (item != null)
{
item.DownloadCount++;
db.SaveChanges();
return File(item.Location, MimeMapping.GetMimeMapping(item.Location), Path.GetFileName(item.Location));
}
return HttpNotFound();
}
This code works perfectly fine except the downloads can't be resumed later.
I know there is other way to download a file using HTML tag like below (which is resumable)...
<a href="/myfile.mp4" download>Download</a>
But, how do I count download on this way?
After hours of browsing I found a solution that works...
Razor View
<a onclick="download(#item.Id)">Download</a>
Javascript
function download(id) {
$.ajax({
url: '#Url.Action("Download", "Home", new {id = "id"})'.replace('id', id),
type: "GET",
success: function (result) {
if (result.success) {
var link = document.createElement('a');
link.href = result.fileLocation;
link.download = '';
link.click();
} else {
alert(result.responseText);
}
},
error: function (errormessage) {
alert(errormessage.responseText);
}
});
}
Controller
public ActionResult Download(int id)
{
Item item = db.Items.Find(id);
if (item != null)
{
item.DownloadCount++;
db.SaveChanges();
string fileLocation = string.Format(
"{0}/{1}",
Request.Url.GetLeftPart(UriPartial.Authority),
item.Location.Replace(#":\\", "/").Replace(#"\", "/")
);
return Json(new { success = true, fileLocation = fileLocation},
JsonRequestBehavior.AllowGet);
}
return Json(new { success = false, responseText = "Bad request." },
JsonRequestBehavior.AllowGet);
}
Related
I am trying to export selected records in to a file and reload the page to update the records in a current view. I am calling web api asynchronously to get all the records. An AJAX call is executing an action in a controller successfully and returning expected data without any error but none of the 'success', 'complete' or 'error' part of ajax function is executing. There are no errors in a developer tool of the browser, no exception, nothing unusual so its getting trickier for me to investigate this issue further. Can I request your a suggestions on this please? Thanks
View :
#Html.ActionLink("Export records", "Index", null, new { Id = "myExportLinkId")
Script :
$("a#myExportLinkId").click(function (e) {
var selected = "";
$('input#myCheckBoxList').each(function () {
if (this.checked == true) {
selected += $(this).val() + ',';
}
});
if (selected != "") {
$.ajax({
url: '/MyController/MyAction',
type: 'GET',
contentType: "application/json; charset=utf-8",
dataType: "json",
data: {
'MyString': 'stringValue'
},
success: function (data) {
alert("success");
},
error: function () {
alert("error");
}
});
})
And the action/method looks like this :
[HttpGet]
public async Task<ActionResult> ExportNewOrders(string OrderIdString)
{
//code to create and store file
//actually want to send the file details as json/jsonResult but for testing only returning
//string here
return Json( "Success", "application/json", JsonRequestBehavior.AllowGet);
}
Finally I have resolved this with Promisify functionality of an AJAX call. Obviously the json response I was returning had an issue so I have replaced
return Json( "Success", "application/json", JsonRequestBehavior.AllowGet);
to
return new JsonResult(){
Data = new { success = true, guid = handle, fileName = exportFileName },
ContentType = "application/json",
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
which has fixed the bug and the success function of ajax call got executed.
But other than this there were issues to wait until the file download (which involved encryption decryption, server validations etc) completes and then refresh the page. This I have resolved by implementing an ajax call with Promisify fuctionality. You can find codepen example here and the original post here.
Here is the complete code.
View/HTML
#Html.ActionLink("Export", "yourActionName", null, new { Id = "exportRequest", #onclick = "letMeKnowMyFileIsDownloaded();" })
Script/Ajax
function letMeKnowMyFileIsDownloaded() {
return new Promise(function (resolve, reject) {
$("a#exportRequest").on("click", function () {
$.ajax({
url: this.href + "?param=whatever params you want to pass",
dataType: "json",
data: {
'param1': 'value'
},
success: function (data) {
var a = document.createElement("a");
var url = '/yourControllerName/Download?fileGuid=' + data.guid + '&filename=' + data.fileName;//window.URL.createObjectURL(data);
a.href = url;
a.download = data.fileName;
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
resolve(true);
},
error: function (error) {
reject(error);
}
});
});
});
}
letMeKnowMyFileIsDownloaded()
.then(function (bool) {
if (bool) {
//alert("File downloaded 👇");
window.location.reload(1);
}
})
.catch(function (error) {
alert("error");
});
I have used nuget package ClosedXML to handle excel file functionality. Using the stream to create and download the data in excel file without storing the file physically on the server.
And in the controller
//can be async or sync action
public async Task<ActionResult> Index(YourModel model)
{
//do stuff you want
var exportOrders = your_object;
//using DataTable as datasource
var dataSource = new DataTable();
//write your own function to convert your_object to your_dataSource_type
dataSource = FormatTypeToDataTable(exportOrders);
if (dataSource != null && dataSource.Rows.Count > 0)
{
//install ClosedXML.Excel from nuget
using (XLWorkbook wb = new XLWorkbook())
{
try
{
var handle = Guid.NewGuid().ToString();
wb.Worksheets.Add(dataSource, "anyNameForSheet");
string exportFileName = "yourFileName" + ".xlsx";
MemoryStream stream = GetStream(wb);
TempData[handle] = stream; exportFileName);
return new JsonResult()
{
Data = new { success = true, guid = handle, fileName = exportFileName },
ContentType = "application/json",
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
catch (Exception ex)
{
//ModelState.AddModelError("", ex.Message);
}
}
}
}
public virtual ActionResult Download(string fileGuid, string fileName)
{
if (TempData[fileGuid] != null)
{
var stream = TempData[fileGuid] as MemoryStream;
var data = stream.ToArray();
return File(data, "application/vnd.ms-excel", fileName);
}
else
{
return new EmptyResult();
}
}
I'm trying to make use of the answer provided here:
Upload File Using WebAPI Ajax
But I keep receiving a 400 (Bad Request) error.
I've been submitting a pdf file but I keep receiving this error...
What am I doing wrong?
(FYI I'm not using MVC)
My code:
CSHTML (using Razor Syntax)
#{
Layout = "~/_SiteLayout.cshtml";
}
<label>Enter File</label>
<input type="file" name="UploadFile" id="datasheet_uploadfile" class="" accept="application/pdf"/>
<script>
$(document).ready(function() {
$('#datasheet_uploadfile').change(function() {
var data = new FormData();
var file = this.files;
data.append('file', file);
$.ajax({
url: '/api/file',
processData: false,
contentType: false,
data: data,
type: 'POST'
}).done(function(result) {
alert(result);
}).fail(function(a, b, c) {
console.log(a, b, c);
});
});
});
</script>
My WebAPI Controller
FileController.cs
public class FileController : 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("~/Content/stuff/" + changed_name);
postedFile.SaveAs(filePath); // save the file to a folder "Images" in the root of your app
changed_name = #"~\Content\stuff\" + 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;
}
}
Use the below code to upload files
$(document).ready(function () {
$('#datasheet_uploadfile').change(function () {
var data = new FormData();
data.append("file", this.files[0]);
From my Home page i have a search textbox that when search is clicked calls an ajax search function which calls my controller. Based on the searchString that is passed I want to either replace a div with a gridview (this part is working) or have it load a new page. Instead it is loading the new view in the same div that the partial views are replacing. How can I do this? Ill post my code below.
All my data is coming back correctly and everything else is working just fine.
<script>
$(function () {
$('.search').click(function () {
var $buttonClicked = $(this);
var searchString = $("#searchStringTextBox").val();
$.ajax({
url: '#Url.Action("ShowGrids")',
type: 'GET',
data: { searchString: searchString },
modal: true,
success: function (partialView) {
$('#gridViews').html(partialView);
$('#gridViews').show();
}
});
});
});
</script>
controller
public ActionResult ShowGrids(string searchString)
{
if (IsValidPersonIdFormat(searchString))
{
var id = searchString.Substring(1);
id = id.Replace("-", "");
var x = Convert.ToInt64(id);
var model = cs.GetById(x);
TempData["model"] = model;
return Redirect(Url.Action("ShowPersonDetails", "Data"));
}
else if(IsValidIdFormat(searchString))
{
var id = searchString.Substring(1);
id = id.Replace("-", "");
var model = ps.GetById(Convert.ToInt64(id));
return View("Details", model);
}
else if (IsValidServiceIdFormat(searchString))
{
var id = searchString.Substring(1);
id = id.Replace("-", "");
var model = vss.GetById(Convert.ToInt64(id));
return PartialView("ServiceDetails", model);
}
}
public ActionResult ShowPersonDetails()
{
var model = TempData["model"];
return View("PersonDetails", model);
}
This has every way I tried to get it to work. Just figured I would show what I was trying and it not working.
The if on server-side is not affecting what the javascript does on success: it's stuffing the results of the call into a div.
If that's not what you want to do, you need to test for the results in javascript and do something else if what you got is not a partial view.
You can do something like this. This is not fully tested.
public JsonResult ShowGrids(string searchString)
{
if (IsValidPersonIdFormat(searchString))
{
var id = searchString.Substring(1);
id = id.Replace("-", "");
var x = Convert.ToInt64(id);
var model = cs.GetById(x);
TempData["model"] = model;
return Json(new { IsRedirect = true, RedirectUrl = Url.Action("ShowPersonDetails", "Data") }, JsonRequestBehavior.AllowGet);
}
else if(IsValidIdFormat(searchString))
{
var id = searchString.Substring(1);
id = id.Replace("-", "");
var model = ps.GetById(Convert.ToInt64(id));
return Json(new { IsRedirect = false, Content = RenderRazorViewToString("Details", model) }, JsonRequestBehavior.AllowGet);
}
else if (IsValidServiceIdFormat(searchString))
{
var id = searchString.Substring(1);
id = id.Replace("-", "");
var model = vss.GetById(Convert.ToInt64(id));
return Json(new { IsRedirect = false, Content = RenderRazorViewToString("ServiceDetails", model) }, JsonRequestBehavior.AllowGet);
}
}
<script>
$(function () {
$('.search').click(function () {
var $buttonClicked = $(this);
var searchString = $("#searchStringTextBox").val();
$.ajax({
url: '#Url.Action("ShowGrids")',
type: 'GET',
data: { searchString: searchString },
modal: true,
success: function (data) {
if(data.IsRedirect){
window.location.href = data.RedirectUrl;
}
else{
$('#gridViews').html(data.Content);
$('#gridViews').show();
}
}
});
});
});
</script>
Code for rendering view to string:
public string RenderRazorViewToString(string viewName, object model)
{
ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext,
viewName);
var viewContext = new ViewContext(ControllerContext, viewResult.View,
ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
make sure when ever you call action using jQuery, action will return HTML string into jQuery, if you want to redirect than return some json string from your action to jQuery call and redirect page from there
I am new to this wonderful framework AngularJS. I have a C# API controller where I would like to upload data from a form that includes an image. Normally (razor) I would upload a form as json and include the image as a HttpPostedFileBase:
public ArtWork SaveArtWork(ArtWork artWork, HttpPostedFileBase file)
{ // save in db and return object }
I have found a lot of different ways for uploading the file wrapped in a FormData object ([AngularJS Uploading An Image With ng-upload):
$scope.uploadFile = function(files) {
var fd = new FormData();
//Take the first selected file
fd.append("file", files[0]);
$http.post(uploadUrl, fd, {
withCredentials: true,
headers: {'Content-Type': undefined },
transformRequest: angular.identity
}).success( ...all right!... ).error( ..damn!... );
};
But I have some other properties I have parsed to a json object, and now I would like to upload it all in a bundle. Or is it possible to get the image data as a base64 and add it to my json object? I know that a base64 is 1/3 bigger than a byte stream, but it's so easy to work with :)
Here's my Angular Controller:
'use strict';
(function () {
// Factory
angular.module('umbraco').factory('artworkResource', function ($http) {
return {
getById: function (id) {
return $http.get("backoffice/Trapholt/ArtWorkApi/GetById/" + id);
},
save: function (artwork) {
return $http.post("backoffice/Trapholt/ArtWorkApi/SaveArtWork", angular.toJson(artwork));
},
save2: function (artwork, fd) {
return $http.post("backoffice/Trapholt/ArtWorkApi/SaveArtWork", angular.toJson(artwork), fd);
}
};
});
// Controller
function artworkController($scope, $routeParams, artworkResource, $http) {
$scope.categories = ['Keramik', 'Maleri', 'Møbel', 'Skulptur'];
artworkResource.getById($routeParams.id).then(function (response) {
$scope.curatorSubject = response.data;
});
var fd;
$scope.uploadFile = function(files) {
fd = new FormData();
fd.append("file", files[0]);
};
$scope.save = function (artwork) {
artworkResource.save(artwork, fd).then(function (response) {
$scope.artwork = response.data;
alert("Success", artwork.Title + " er gemt");
});
};
};
//register the controller
angular.module("umbraco").controller('ArtworkTree.EditController', artworkController);
})();
So how can I combine my image and the other properties in one json object or two arguments? Please leave a comment if I need to explain some more, any help would really be appreciated :)
I found a solution, where I added the file and the model to the form data. So it was actually pretty easy to expand solution from here. This is my Angular controller:
function artworkController($scope, $routeParams, artworkResource, $http) {
$scope.categories = ['Keramik', 'Maleri', 'Møbel', 'Skulptur'];
artworkResource.getById($routeParams.id).then(function (response) {
$scope.curatorSubject = response.data;
});
var fd;
$scope.uploadFile = function(files) {
fd = new FormData();
fd.append("file", files[0]);
};
$scope.save = function (artwork) {
fd.append("ArtWork", angular.toJson(artwork));
$http.post("backoffice/Trapholt/ArtWorkApi/Post", fd, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
});
};
};
And this i my C# mvc API controller:
public HttpResponseMessage Post()
{
HttpResponseMessage result = null;
var httpRequest = HttpContext.Current.Request;
if (httpRequest.Files.Count > 0)
{
var file = httpRequest.Files[0];
var artworkjson = httpRequest.Form[0];
var artwork = JsonConvert.DeserializeObject<ArtWork>(artworkjson);
if (artwork == null)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, "No saved");
}
using (var binaryReader = new BinaryReader(file.InputStream))
{
artwork.Picture = binaryReader.ReadBytes(file.ContentLength);
}
result = Request.CreateResponse(HttpStatusCode.Created, "ok");
}
else
{
result = Request.CreateResponse(HttpStatusCode.BadRequest);
}
return result;
}
The html view is a normal form where all the inputs are bound with the model, expect the file input field:
<input type="file" id="file" name="picture" onchange="angular.element(this).scope().uploadFile(this.files)"/>
I have a problem, Im trying to display a message on the same page after the form has been submitted, but my result is always false because my action in my controller gets called twice, when I click on the submit button, The first time around my action is called the data is passed through to the action with the correct info and is saved to the db, the second time around every parameter is null thus return false.
I want a way to submit only once and return the appropriate Json result in the same page without redirecting, I hope someone can help me with this, here is my code:
I tried:
function PdfHeaderAndFooterManager() {
$('#submitPdf').ajaxSubmit(function (e) {
$.ajax({
url: "/Dashboard/PdfHeaderAndFooterManager",
dataType: "json",
type:"POST",
data: {headerImage: headerImage, footerImage: footerImage},
success: function (data) {
if (data.success) {
alert(data.message);
$('#resultMessage').text(data.message).css("color", "RED");
}
else {
alert(data.message);
$('#resultMessage').text(data.message).css("color", "RED");
}
}
});
e.preventDefault();
});
}
My view some code removed for simplicity
#using (Html.BeginForm("PdfHeaderAndFooterManager", "Dashboard", FormMethod.Post, new { enctype = "multipart/form-data", id = "formPdfImages" }))
div id="resultMessage"></div>
}
public ActionResult PdfHeaderAndFooterManager(HttpPostedFileBase headerImage, HttpPostedFileBase footerImage)
{
//some code to declare variables
if (headerImage != null)
{
if (!String.IsNullOrEmpty(headerImage.ContentType))
{
headerImageContentType = imageHelper.IsValidImageType(headerImage.ContentType);
if (headerImageContentType)
{
resizedHeaderImage = imageHelper.ResizeImage(headerImage.InputStream);
}
else
{
return Json(new { success = false, message = "Please Upload an image* file less than 2GB." });
}
}
}
if (footerImage != null)
{
if (!String.IsNullOrEmpty(footerImage.ContentType))
{
footerImageContentType = imageHelper.IsValidImageType(footerImage.ContentType);
if (footerImageContentType)
{
resizedFooterImage = imageHelper.ResizeImage(footerImage.InputStream);
}
else
{
return Json(new { success = false, message = "Please Upload an image* file less than 2GB." });
}
}
}
if (P24DataPrincipal.CurrentIdentity != null)
{
if (resizedHeaderImage != null || resizedFooterImage != null)
{
//add to DB code
return Json(new { success = true, message = "Image(s) Uploaded Successfully." });
}
else
{
return Json(new {success = false, message = "Upload atleast 1 image file." });
}
}
return Json("someview");
}
and above is my action the most important parts are the "return" keywords I just want to see that in my view even when the result is true not only when the result is false and this action should be only called once and not redirect. Thanks.
Do you have an <input type="submit" />? If so, change it to a button and wire it up in your jQuery ready so that it hits your function:
$.ready(function(){
$("#submitButtonId").click(function() {
//put your ajax stuff here
});
});
Your first function actually looks close to being correct. A few small tweaks would look as per below.
Does the following work?
$(function() {
$('#formPdfImages').submit(function(event) {
event.preventDefault();
event.stopPropagation();
$.post("#(Url.Action("PdfHeaderAndFooterManager", "Dashboard"))", $(this).serialize(), function(data) {
if (data.success) {
$("#resultMessage").text(data.success);
} else {
$("resultMessage").text(data.success);
}
});
});
});
You are using #using (Html.BeginForm()) and you can't make Ajax call using submit with this,
for ajax call you will have to use: #using (Ajax.BeginForm()),
But you need to upload an image, so you must have to use:
#using (Html.BeginForm("PdfHeaderAndFooterManager", "Dashboard", FormMethod.Post, new { enctype = "multipart/form-data", id = "formPdfImages" }))
So, Ajax call is not possible. For displaying messages on the same page, use another alternative, that may be:
public ActionResult PdfHeaderAndFooterManager(HttpPostedFileBase headerImage, HttpPostedFileBase footerImage)
{
//some code to declare variables
if (headerImage != null)
{
if (!String.IsNullOrEmpty(headerImage.ContentType))
{
headerImageContentType = imageHelper.IsValidImageType(headerImage.ContentType);
if (headerImageContentType)
{
resizedHeaderImage = imageHelper.ResizeImage(headerImage.InputStream);
}
else
{
ViewBag.ReturnMessage="<span style='color:red'> Please Upload an image* file less than 2GB.</span>";
return View();
}
}
}
if (footerImage != null)
{
if (!String.IsNullOrEmpty(footerImage.ContentType))
{
footerImageContentType = imageHelper.IsValidImageType(footerImage.ContentType);
if (footerImageContentType)
{
resizedFooterImage = imageHelper.ResizeImage(footerImage.InputStream);
}
else
{
ViewBag.ReturnMessage="<span style='color:red'>Please Upload an image* file less than 2GB.</span>";
return View();
}
}
}
if (P24DataPrincipal.CurrentIdentity != null)
{
if (resizedHeaderImage != null || resizedFooterImage != null)
{
//add to DB code
ViewBag.ReturnMessage="<span style='color:green'>Image(s) Uploaded Successfully.</span>";
return View();
}
else
{
ViewBag.ReturnMessage="<span style='color:red'>Upload atleast 1 image file.</span>";
return View();
}
}
In View:
#using (Html.BeginForm("PdfHeaderAndFooterManager", "Dashboard", FormMethod.Post, new { enctype = "multipart/form-data", id = "formPdfImages" })){
<div id="resultMessage">
#ViewBag.ReturnMessage
</div>
}