How to download a file using Ajax in MVC. If no data to generate file then show error label.
I'm trying using action result method which returns File. I can download file . but don't want to refresh the page if no file to download.
My code is like
public ActionResult Excel(MyViewModel model)
{
var result = // DB call to get data
if (no data)
{
return **something**
}
else
{
byte[] excelContent =//passing result to my method( returns xls file in byte)
return File(
excelContent, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
reportName + " Report " + startDate + " - " + endDate + ".xlsx");
}
}
What should I return for something
For now I'm returning emptyResult for something hence I got blank page if no data.
Ajax doesn't support file download.
This code works if I submit the form and there is data.
Suggest something in which page will not get refresh and both task get achieved.
1. File download if data
2. Show error label if no data
If get file using Ajax then best way to create API
Function return type is FileResult
var exportToExcel = function (inputData, fileName) {
var path = "url";
var form = $("<form></form>");
form.attr("enctype", "application/x-www-form-urlencoded");
form.attr("method", "post");
form.attr("action", path);
form.attr("accept-charset", "UTF-8");
var field = $("<input></input>");
field.attr("type", "hidden");
field.attr("name", "data");
field.attr("value", escape(JSON.stringify(inputData)));
form.append(field);
var field2 = $("<input></input>");
field2.attr("name", "fileName");
field2.attr("value", fileName);
form.append(field2);
$(document.body).append(form);
form.submit().remove();
};
var downloadFile = function (inputData) {
// checkFileHasResult is ajax call result if true or false
checkFileHasResult(inputData).then(function (hasFile) {
if (hasFile === true) {
// give file name with extension
exportToExcel(inputData, "asdfasdf.csv");
}
});
};
Doing a quick search in Google, brought up quite a few results, as this question.
In JQuery, you could just point you 'window.location' to your action method in the controller, that returns a FileResult. This will download the file for you.
I would suggest, you return message to an ajax call, stating whether your download was successful or not, and then you can set some sort of text on the front end to notify the user that this process was unsuccessful.
Here is how I would have accomplished this. You can tweak it to work for you. Here is an example of the controller methods.
[HttpGet]
public JsonResult ExportCollection()
{
//Build your excel file, and save it to disk somewhere on server.
//you can also save it in session, depending on size
//Build up response Messages based on success or not
//return json object with your file path
return Json(new { responseMessage = responseMessage }, JsonRequestBehavior.AllowGet);
}
public ActionResult Download(string fileName)
{
return File(model.FilePath, "application/vnd.ms-excel", fileName);
}
And then, call these actions from client side using JQuery and an Ajax call.
$(document).on("click", "#YourButton", function () {
var url = "/YourController/ExportCollection/"
$("#responseText").text("We're getting things ready. Please wait...");
$('#loadingImage').show();
$.ajax({
url: url,
type: "get",
success: function (responseMessage) {
patientCollectionExportSuccess(responseMessage);
}
});
})
//Function responsible for exporting
function patientCollectionExportSuccess(dataReceived) {
var respMessage = dataReceived.responseMessage;
if (respMessage != null) {
if (respMessage != "Error: Not Exported.") {
$("#responseText").text("Download completed.");
$('#loadingImage').hide();
//set window.location to redirect to FileResult, which will download file
window.location = '/PatientListingQuery/Download?fileName=' + respMessage ;
}
else {
$("#responseText").text("Download unsuccessful.");
$('#loadingImage').hide();
$("#responseText").text(dataReceived.responseMessage);
}
}
}
Thanks guys
I got solution
It works for me smoothly ....
my form action is pointing to another method, so updated action before submitting the form. and after file download, i'm setting it to old form action.
$(document).ready(function () {
$('#excel-button').on('click', function () {
$.ajax({
url: '/MyController/IsData',
type: 'POST',
data: $("#myForm").serialize(),
success: function (response) {
if (response == "True") {
var oldUrl="";
var form1 = $('#myForm');
var frm = document.getElementById('myForm') || null;
if (frm) {
oldUrl = frm.action;
frm.action = '/MyController/GenerateExcel';
}
form1.submit();
frm.action = oldUrl;
$('.error-Message').hide();
} else {
$('.error-Message').show();
}
}
});
});
Related
I have the following API call to retrieve page data
List<VillageNewsItem> newsList = pageRetriever.RetrieveAsync<VillageNewsItem>(
query => query
.Path("/Home/Village-News", PathTypeEnum.Children)
.Published(true)
.OnSite(SiteContext.CurrentSiteName)
.OrderByDescending(x => x.DocumentCreatedWhen)
)?.Result?.ToList();
It works fine and return 2 records if I run the query on page load. Inside Index action of the controller.
public VillageNewsListController(IPageDataContextRetriever dataRetriever, VillageNewsListRepository villageNewsListRepository,
IPageRetriever pagesRetriever, IPageDataContextRetriever pageDataContextRetriever, IPageUrlRetriever pageUrlRetriever)
{
this._dataRetriever = dataRetriever;
this._villageNewsListRepository = villageNewsListRepository;
this._pagesRetriever = pagesRetriever;
this.pageDataContextRetriever = pageDataContextRetriever;
this.pageUrlRetriever = pageUrlRetriever;
}
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
try
{
List<VillageNewsItem> newsList = pagesRetriever.RetrieveAsync<VillageNewsItem>(
query => query
.Path("/Home/Village-News", PathTypeEnum.Children)
.Published(true)
.OnSite(SiteContext.CurrentSiteName)
.OrderByDescending(x => x.DocumentCreatedWhen)
)?.Result?.ToList();
newsItems.VillageNewsItems = newsList;
return View(newsItems);
}
catch (Exception ex)
{
ErrorHandler.EventLog.LogError(ex.Source, ex.Message, ex.StackTrace);
return RedirectToAction("ErrorPage", "Error");
}
}
However, if I try to make the same API call via a client side AJAX call, it doesn't work and return 0 records. Why it's not working with Ajax calls?
Ajax call
function loadMoreNews() {
$.ajax({
url: '/VillageNewsList/VillageNewsItemList',
//data: { "term": request.term },
type: "POST",
success: function (data) {
response($.map(data,
function (item) {
console.log(data);
}));
},
error: function (response) {
//alert(response.responseText);
},
failure: function (response) {
// alert(response.responseText);
}
});
}
Server side method.
[HttpPost]
[Route("VillageNewsList/VillageNewsItemList")]
public VillageNewsListViewModel VillageNewsItemList(string NodeAliasPath = "", int villageId = 0, string state = "", int page = 1, int pageSize = 4)
{
try
{
List<VillageNewsItem> newsList = pagesRetriever.RetrieveAsync<VillageNewsItem>(
query => query
.Path("/Home/Village-News", PathTypeEnum.Children)
.Published(true)
.OnSite(SiteContext.CurrentSiteName)
.OrderByDescending(x => x.DocumentCreatedWhen)
)?.Result?.ToList();
var model = new VillageNewsListViewModel
{
VillageNewsItems = newsList, // returns 0 records
};
return model;
}
catch (Exception ex)
{
ErrorHandler.EventLog.LogError(ex.Source, ex.Message, ex.StackTrace);
//return RedirectToAction("ErrorPage", "Error");
}
return null;
}
Couple things I see.
You're calling IPageRetriever.RetrieveAsync, but you aren't putting an await before it. There may be some odd behavior due to this. Get rid of the ?.Result?.ToList() and instead just put await before it, it will return an IEnumerable of the specified type.
You don't need ".Published" nor "OnSite" with IPageRetriever, this API automatically uses the Current Site Context, the current culture, and either Published or not / Latest Version or not based on if it's in edit/preview mode or not.
See if those things fix the issue!
I also asume it is caused by async context here...
You can try to use a document query instead.
Would be something like this:
var items = new DocumentQuery<VillageNewsItem>(
.Path("/Home/Village-News", PathTypeEnum.Children)
.PublishedVersion()
.Published()
.OnCurrentSite()
.OrderByDescending(x => x.DocumentCreatedWhen))
?.Result
?.ToList();
If you have multiple cultures, add the culture to your query, too.
.Culture(LocalizationContext.CurrentCulture.CultureCode)
I'm trying to create a central function for dynamic web requests.
function makeWebRequest(remoteURL, requestString, callBackFunction) {
var myWebRequest = new SMF.Net.WebClient({
url : remoteURL,
httpMethod : "POST",
requestString : requestString,
requestHeaders : [
"Content-Type: application/x-www-form-urlencoded"],
onSyndicationSuccess : callBackFunction,
onServerError : function (e) {
alert(e);
}
});
myWebRequest.run(false);
}
While calling makeWebRequest, passing a callBackFunction to it like;
var remoteURL = "http://parse.com/12/test";
var requestString = "category=news&type=world";
function callBackFunction(e) {
responseText = this.responseText;
if (responseText != null) {
parsedJSON = JSON.parse(responseText);
}
}
makeWebRequest(remoteURL,requestString,callBackFunction);
Application raises an error at line
responseText = this.responseText;
How can I pass myWebRequest itself to a function like that?
I used your codeLines. I just add a textButton to Page1, and it works fine both for Android and iOS .
In Global.js;
function makeWebRequest(remoteURL, requestString, callBackFunction) {
var myWebRequest = new SMF.Net.WebClient({
url : remoteURL,
httpMethod : "POST",
requestString : requestString,
requestHeaders : [
"Content-Type: application/x-www-form-urlencoded"],
onSyndicationSuccess : callBackFunction,
onServerError : function (e) {
alert(e);
}
});
myWebRequest.run(false);
}
var remoteURL = "http://parse.com/12/test";
var requestString = "category=news&type=world";
function callBackFunction(e) {
var responseText = this.responseText;
alert(responseText);
if (responseText != null) {
parsedJSON = JSON.parse(responseText);
}
}
function Global_Events_OnStart(e) {
changeLang(Device.language, true);
include("BC.js"); //included for future BC support. Removing is not advised.
// Comment following block for navigationbar/actionbar sample. Read the JS code file for usage.
// Also there is a part of code block in Page1, which should be copied to every page for HeaderBar usage
load("HeaderBar.js");
header = new HeaderBar();
// Uncomment following block for menu sample. Read the JS code file for usage.
/*
load("Menu.js");
/**/
}
function Global_Events_OnError(e) {
switch (e.type) {
case "Server Error":
case "Size Overflow":
alert(lang.networkError);
break;
default:
SES.Analytics.eventLog("error", JSON.stringify(e));
//change the following code for desired generic error messsage
alert({
title : lang.applicationError,
message : e.message + "\n\n*" + e.sourceURL + "\n*" + e.line + "\n*" + e.stack
});
break;
}
}
In Page1.js;
function Page1_Self_OnKeyPress(e) {
if (e.keyCode === 4) {
Application.exit();
}
}
function Page1_Self_OnShow() {
//Comment following block for removing navigationbar/actionbar sample
//Copy this code block to every page onShow
header.init(this);
header.setTitle("Page1");
header.setRightItem("RItem");
header.setLeftItem();
/**/
}
function Page1_TextButton1_OnPressed(e){
makeWebRequest(remoteURL,requestString,callBackFunction);
}
it works fine. Check your makeWebRequest function, must be on Global.js. Also define "responseText" variable with "var".
I am "piping" a json feed (in some cases quite big) that is returned from an external service, to hide an access api-key to the client (the access key is the only available authentication system for that service).
I am using Gaelyk and I wrote this groovlet:
try {
feed(params.topic)
} catch(Exception e) {
redirect "/failure"
}
def feed(topic) {
URL url = new URL("https://somewhere.com/$topic/<apikey>/feed")
def restResponse = url.get()
if (restResponse.responseCode == 200) {
response.contentType = 'application/json'
out << restResponse.text
}
}
The only problem is that the "restResponse" is very big and the value returned by the groovlet is truncated. So I will get back a json like this:
[{"item":....},{"item":....},{"item":....},{"ite
How can I return the complete json without any truncation?
Well I found the solution and the problem was at the beginning (the URL content must be read as stream). So the content truncated it was not the output but the input:
def feed(topic) {
URL url = "https://somewhere.com/$topic/<apikey>/feed".toURL()
def restResponse = url.get()
if (restResponse.responseCode == 200) {
response.contentType = 'application/json'
StringBuffer sb = new StringBuffer()
url.eachLine {
sb << it
}
out << sb.toString()
}
}
I am exporting a HTML table to Excel file. I was using this for all other browsers:
window.open('data:application/vnd.ms-excel,<meta http-equiv="content-type" content="text/plain; charset=UTF-8"/>' + encodeURIComponent(tblHtml));
But for IE8, I have done following to make this export work:
jQuery post to send table html markup as string
Javascript that doing the post request:
if (($.browser.msie && parseInt($.browser.version) <= 8)) {
$.ajax({
url: '/exportdatatoexcel',
data: {
'tblToExport': tblHtml//encodeURIComponent(tblHtml)
, 'tblID': id
},
type: 'POST',
async: false,
success: function (html) {
//nothing to do
},
error: function (jqXHR, textStatus, errorThrown) {
if (jqXHR.readyState != 0) {
alert('error occurred');
}
}
});
window.open("/exportdatatoexcelfilesavedialog", 'PopUp', 'width=500,height=300');
}
Action that handles post request and window.open request (for opening file save dialog):
[HttpPost]
[ValidateInput(false)]
public ActionResult ExportDataToExcel(FormCollection form)
{
string tblToExport = form["tblToExport"], tblID = form["tblID"];
Session["ExportTbl"] = tblToExport;
ContentResult content = new ContentResult();
return content;
}
[ValidateInput(false)]
public ActionResult ExportDataToExcelFileSaveDialog()
{
Response.ContentType = "application/vnd.ms-excel";
Response.AddHeader("Content-Disposition", "attachment;filename=download.xls");
Response.ContentEncoding = System.Text.Encoding.UTF8;
object tblToExport = Session["ExportTbl"];
return PartialView("_ExportToExcelForIE8", tblToExport.ToString());
}
Now, when the file save dialog opens I can able to save the excel file but the excel file content is:
%0d%0a%3cTABLE+dir%3dltr+id%3dtblCompAnalysis+class%3d%22clear+table+company-analysis-general%22+cellSpacing%3d0+cellPadding%3d0+width%3d%22100%25%22%3e%3cTBODY%3e%0d%0a%3cTR+class%3d%22labh+sort%22%3e%0d%0a%3cTD%3eCompany%3c%2fTD%3e%0d%0a%3cTD%3eMarket+Sector%3c%2fTD%3e%0d%0a%3cTD%3eArgaam+Sector+%3c%2fTD%3e%0d%0a%3cTD+class%3dcity%3eCity%3c%2fTD%3e%0d%0a%3cTD+class%3dtel%3ePhone%3c%2fTD%3e%0d%0a%3cTD+class%3dtel%3eFax%3c%2fTD%3e%0d%0a%3cTD%3eEmail%3c%2fTD%3e%0d%0a%3cTD%3eWebsite%3c%2fTD%3e%3c%2fTR%3e%0d%0a%3cTR%3e%0d%0a%3cTD%3e%3c
I have the gotten a certain result from the Notes tab.
The link you see inside the iframe is the name of the file.
I have the DocumentBody from the annotation in some format that looks like base64.
How do I download it?
Thanks,
Fabio
Perform a JQuery request to a URL like this
Xrm.Page.context.getServerUrl() + "XRMServices/2011/OrganizationData.svc/ActivityMimeAttachmentSet(guid'abc...')?$select=Body"
By specifying the select you will request only what you want.
Assign the result to a variable and prepend
data:application/pdf;base64,
From there you could display it inline as an HTML object or try to open it as a new window with
window.location or window.open or document.location.href
I had already the base64 documentbody string extracted like this:
function getSla() {
// Define SOAP message
var objectId;
if (typeof crmForm === "undefined") {
objectId = parent.crmForm.ObjectId;
}
else {
objectId = crmForm.ObjectId;
}
var xml =
[
"<?xml version='1.0' encoding='utf-8'?>",
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" ",
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ",
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">",
GenerateAuthenticationHeader(),
"<soap:Body>",
"<RetrieveMultiple xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>",
"<query xmlns:q1='http://schemas.microsoft.com/crm/2006/Query' ",
"xsi:type='q1:QueryExpression'>",
"<q1:EntityName>annotation</q1:EntityName>",
"<q1:ColumnSet xsi:type='q1:AllColumns' />",
"<q1:Distinct>false</q1:Distinct><q1:Criteria><q1:FilterOperator>And</q1:FilterOperator>",
"<q1:Conditions><q1:Condition><q1:AttributeName>objectid</q1:AttributeName><q1:Operator>Equal</q1:Operator>",
"<q1:Values><q1:Value xsi:type=\"xsd:string\">",
objectId,
"</q1:Value></q1:Values></q1:Condition></q1:Conditions></q1:Criteria>",
"</query>",
"</RetrieveMultiple>",
"</soap:Body>",
"</soap:Envelope>"
].join("");
var resultXml = executeSoapRequest("RetrieveMultiple", xml);
var result = filter(resultXml.getElementsByTagName("q1:filename"), function (element) {
return /master.*sla/i.test(element.text);
});
if (result.length == 0) {
return null;
}
else {
return result[0].parentNode;
}
}
function getSlaDocumentBody(sla) {
return sla.getElementsByTagName("q1:documentbody")[0].text;
}
window.open("data:application/pdf;base64," + getSlaDocumentBody(sla));
It opened a new window with the string data:application/pdf.......... in the address bar but did nothing. I would prefer that solution indeed.
Ended up using srasmussen solution in here: http://social.microsoft.com/Forums/en/crm/thread/05134277-dd76-4fbb-8f6e-89b1a2a45af1.
var URL = serverUrl + "/userdefined/edit.aspx?etc=5&id=" + slaId;
$.get(URL, function (data) {
var WRPCTokenElement = $(data).find("[WRPCTokenUrl]");
if (WRPCTokenElement) {
var WRPCTokenUrl = WRPCTokenElement.attr("WRPCTokenUrl");
if (WRPCTokenUrl) {
URL = "/Activities/Attachment/download.aspx?AttachmentType=5&AttachmentId=" + slaId + "&IsNotesTabAttachment=undefined" + WRPCTokenUrl;
window.open(URL);
}
}
return false;
});