Pass large amount of data to MVC get method - c#

I have an angularjs form that I would like users to be able to download a filled out PDF of what they requested. I have an [HTTPGet] method that accepts a base64 encoding of the json request. Everything works except when my base64 encoding goes above 2027 characters in length. When that happens, I don't receive anything more than the 2027 characters. Is there another way to send the data on a get call or a better way to do this.
My angular js app does this.
scope.DownloadTempResults = function () {
window.open('/Home/GetCopyOfRequest?strRequest=' + window.btoa(JSON.stringify(scope.request)), '_blank', '');
}
My C# code behind does this.
[HttpGet]
public ActionResult GetCopyOfRequest(string strRequest)
{
byte[] data = Convert.FromBase64String(strRequest);
string request = Encoding.UTF8.GetString(data);
ExternalModel model = JsonHelper.Deserialize<ExternalModel>(request);
byte[] filedata = ObjectFactory.GetInstance<ManagerFactory>().Create().CreateRequestForm(model);
string contentType = "application/pdf";
var cd = new System.Net.Mime.ContentDisposition
{
FileName = "name of File",
Inline = true
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(filedata, contentType);
}
At the end of the day, what we are trying to do is encode the json in the query string so that you can not look directly at the query string and get the values of the json object. If I am going about this completely wrong, please let me know what to look up to accomplish this.
Updated below here.
So based on the comments, I changed to a post method that looks like this.
[HttpPost]
public HttpResponseMessage GetCopyOfRequest(string strRequest)
{
ExternalModel model = JsonHelper.Deserialize<ExternalModel>(strRequest);
byte[] filedata = ObjectFactory.GetInstance<IManagerFactory>().Create().CreateRequestForm(model);
string fileName = "Request form for request";
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
httpResponseMessage.Content = new ByteArrayContent(filedata);
httpResponseMessage.Content.Headers.Add("x-filename", fileName);
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
httpResponseMessage.Content.Headers.ContentDisposition.FileName = fileName;
httpResponseMessage.StatusCode = HttpStatusCode.OK;
return httpResponseMessage;
}
In the angularjs app, I have the following code.
$http({
url: '/Home/GetCopyOfRequest',
method: 'POST',
data: { strRequest: JSON.stringify(request) },
responseType: 'arraybuffer'
}).success(function (data, status, headers) {
var file = new Blob([data], { type: 'application/pdf' });
var fileURL = URL.createObjectURL(file);
window.open(fileURL);
}).error(function (response) {
});
When I do this on the server, my byte array has a length of 400000. On the client side, I have an array of 270. I followed an example from How to display a server side generated PDF stream in javascript sent via HttpMessageResponse Content .

Related

How to convert asp web api byte[] to pdf file in Angular 8?

I've been having some problems when I try to convert a byte[] coming from C# into a pdf file using Angular.
When I perform the conversion the file is created but is corrupted and it can't be opened "We can't open this file", the file content is just basic text "Hello world".
Please let me know if you have any answer to this problem, the code I am using is below and I also try using "import { saveAs } from 'file-saver';" but it didn't work.
// THE WEB API CONTROLLER THAT GETS THE PDF BYTE ARRAY
[HttpPost("CreatePDF")]
public async Task<HttpResponseMessage> CreatePDF([FromBody] PDFContent pdfContent)
{
byte[] pdf = await _pdfGenerator.GeneratePDF(pdfContent.HtmlContent);
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(pdf);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
// UI SERVICE LAYER THAT CALLS THE SERVER SIDE
createPdfReport(pdfContent: PDFContent): Observable<any> {
return this.restApi.create("CreatePDF",pdfContent).pipe(
tap(pdf =>
console.log('pdf = ' + pdf)
));
}
// THE COMPONENT THAT CALLS THE SERVICE LAYER AND WAITS FOR THE RESPONSE
this.historyService.createPdfReport(pdfContent).subscribe(data => {
var file = new Blob(data, { type: 'application/octet-stream' })
var fileURL = URL.createObjectURL(data);
const link = document.createElement('a');
link.setAttribute('target', '_blank');
link.setAttribute('href', fileURL);
link.setAttribute('download', `file.pdf`);
document.body.appendChild(link);
link.click();
link.remove();
});
}
This is the response I get in Angular after the controller returns the byte[]:

return meaningful error string to browser when expecting a blob

I'm returning a a PDF or XLSX file to the browser. I do this by setting the responseType of the ajax object that initiated the request to 'blob'. This seems to work as needed. I'm having problems figuring out a good way to pass an error string to the browser in cases where the file couldn't be created.
If I don't set any responseType on the ajax object I can read the response text as the meaninful error string I've set. This, though, means that I'm no longer able to properly read the response as a PDF or XLSX file in the cases where things proceeded properly. And, of course, I can't set the responseType of the ajax object after I've received the response.
Controller
public ActionResult GetFile() {
// process work, set stream and success bool
if (wasSuccessful) {
return File(stream, "application/pdf");
}
else {
return Content("a meaningful error for the UI");
}
}
cshtml
function getFile(e, extension) {
var xhr = new XMLHttpRequest();
xhr.open('POST', e.value, true);
xhr.responseType = 'blob';
xhr.setRequestHeader('Content-Type', "application/x-www-form-urlencoded");
xhr.onload = function (ee) {
if (this.status == 200) {
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
var blob = new Blob([this.response], { type: 'octet/stream' }), url = window.URL.createObjectURL(blob);
a.href = url;
a.download = "file." + extension;
a.click();
window.URL.revokeObjectURL(url);
}
};
xhr.send($("#form").serialize());
}
Can I have my ajax expect a blob but somehow read a simple string in certain error conditions?

Download Zip Archive from WebAPI in Response to POST Request

I'm trying to deliver a zip archive in response to an AJAX POST request made from Axios to WebAPI.
On the client side I have
import AjaxDownload from "../../data/AjaxDownload";
AjaxDownload.post(id, pageRecords, {
responseType: "blob"
}).then((response) => {
let blob = new Blob([response.data], { type: extractContentType(response) }),
url = window.URL.createObjectURL(blob);
window.open(url, "_self");
}).catch((error) => {
// ...
}).then(() => {
// ...
});
function extractContentType(response: AxiosResponse): string {
return response.headers["content-type"] || "";
}
// AjaxDownload:
import * as axios from "axios";
import { apiUrl } from "./Ajax";
const ajax = axios.default.create({
baseURL: new URL("download", apiUrl).toString(),
timeout: 3600000 // 1 hour
});
export default ajax;
That posts to the following WebAPI method - and the POST part of that client-side logic works exactly as expected.
[HttpPost]
[Route("download/{id:guid}")]
public async Task<HttpResponseMessage> Download(Guid id, [FromBody] IEnumerable<PageRecord> pageRecords)
{
var stream = await _repo.GetAsArchiveStream(id,
pageRecords,
true).ConfigureAwait(false);
stream.Position = 0;
var result = new HttpResponseMessage(HttpStatusCode.OK) {Content = new StreamContent(stream)};
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {FileName = $"{...}.zip"};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); // "application/zip" has same result
result.Content.Headers.ContentLength = stream.Length;
return result;
}
However, the browser displays the result.Content as a JSON object, without the zip archive. I assume that the it's displaying as JSON because the request mentions JSON, but why does it appear to ignore the binary content - particularly as the Content-Type header details the type of content?
And as you can see, the JavaScript is also expecting to read in the content as a blob.
I don't see how my code differs meaningfully from this answer - please explain if there is a crucial difference.
On the server-side, I've also tried returning...
return new FileStreamResult(stream, "application/zip");
The problem with this approach is that there's no way to set a filename. Firefox does download the zip albeit with a random name while Chrome doesn't appear to download anything at all.
There must be a way to do this, right? To POST a request to a WebAPI method which returns a zip archive, and the client-side then presents a Save dialog? What am I missing?
I managed to solve this simply by returning the zip from the controller action using...
return File(stream,
"application/zip",
"FILENAME.zip");
And in the client-side code I can fetch the filename from the headers using some JavaScript found in this SO answer.
let blob = new Blob([response.data], { type: extractContentType(response) }),
downloadUrl = window.URL.createObjectURL(blob),
filename = "",
disposition = response.headers["content-disposition"];
if (disposition && disposition.indexOf("attachment") !== -1) {
let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/,
matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}

Angular download file after POST request

I have several export routes on an asp.net core application, but all of them are accessed by a GET request, eg: /api/{projectid}/{parameter}
These requests are generating xlsx files and sending them to the client. Now I have a similar request but I have to pass a long array to the method, so I would like to make it into a POST method and send the array (and other parameters) in the http body.
I get the correct response from the server (an array buffer starting with PK...) but I can't tell angular to save it as a file as I did with the similar GET requests. If I rewrite this back to start a GET request it works fine. What am I doing wrong?
Controller method:
[HttpPost("[action]")]
public IActionResult Export([FromBody] DistributionExportPostModel model)
{
var project = _ctx.Projects.FirstOrDefault(x=>x.Id == model.ProjectId);
byte[] xlsx = createXlsxFile(project, model.Selection, model.ComparisonBase);
var mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var fileName = $"TaskExport-"+project.Name+"-"+DateTime.Now.ToString("yyyyMMddHHmmss")+".xlsx";
return File(xlsx, mimeType, fileName);
}
Angular provider method:
export(projectid:string, selection:string[], comparisonBase:string):Promise<any[]> {
//let headers:Headers = new Headers();
//headers.append('Accept', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
//let params: URLSearchParams = new URLSearchParams();
let requestOptions = new RequestOptions();
requestOptions.responseType = ResponseContentType.ArrayBuffer;
return new Promise<any[]>((resolve) =>
this.http.post('/api/Distribution/Export', {
//'+projectid+'/'+comparisonBase+'/'+selection.join(','),
'ProjectId': projectid,
'Selection': selection,
'ComparisonBase': comparisonBase
}, requestOptions).subscribe((res) => {
window.open(res.url, '_blank');
resolve();
})
);
}
Below should work on Chrome.
var blob = new Blob(yourData, {type: "octet/stream"});
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = name;
a.click();
window.URL.revokeObjectURL(url);
For IE and Firefox, try this:
window.navigator.msSaveBlob(blob, filename);
You may need to add the anchor to the DOM before clicking it.

Using Imgur API 3 to upload an image reports that I'm not the owner of the album

I created an Imgur account and acquired my client ID and secret key before creating an album. My goal was to write in a test environment before integrating it into my application.
Below is final version of the code I have used up until now:
string base64String;
string message;
string album = "abcxyz";
using (Image image = Image.FromFile("c:\\path_to\\image.jpg"))
{
using (MemoryStream m = new MemoryStream())
{
image.Save(m, image.RawFormat);
byte[] imageBytes = m.ToArray();
base64String = Convert.ToBase64String(imageBytes);
}
}
using (var client = new WebClient())
{
string clientID = "supersecret";
client.Headers.Add("Authorization", $"Client-ID {clientID}");
System.Collections.Specialized.NameValueCollection valueCollection = new System.Collections.Specialized.NameValueCollection();
valueCollection["image"] = base64String;
valueCollection["type"] = "base64";
valueCollection["album"] = album;
try
{
byte[] response = client.UploadValues("https://api.imgur.com/3/upload", "POST", valueCollection);
message = Encoding.ASCII.GetString(response);
}
catch (Exception ex)
{
message = ex.Message;
}
}
When I run the code I get the message back that I don't apparently own the album that I'm trying to. Specifically:
error=You are not the owner of album '<album hash>', which means you can't
add images to it. For anonymous albums, use the album deletehash.
I had thought that since I created the album while logged on with my account that it should work - and now I'm missing something. Any extra eyes on this would be greatly appreciated thank you.
Andy
It was an easy peasy 2 step process. but imgur api docs made it very hard for me.
this is in JS:
so you should
⠀ 1. make sure you have these headers and this body and a correct + supported type (The type of the file that's being sent; file, base64 or URL) for me base64
var myHeaders = new Headers();
var formdata = new FormData();
myHeaders.append(
"Authorization",
"Bearer 8efbea9b750ad38aeb2e8384a378456363ddddd" //your access-token
);
formdata.append("image", res);
formdata.append("type", "base64");
formdata.append("name", any_string_you_want);
formdata.append("album", "2ddl3LUD");
var requestOptions = {
method: "POST",
headers: myHeaders,
body: formdata,
};
make sure you don't include any other extra headers or meta-data like, Client-ID : your-client-id.
extra: if you don't have an access token, generate one by going back to imgur docs, if you don't have an album id, google how to get it.

Categories

Resources