Download Binary from WebApi2 As File. Server Side - c#

I've looked a couple of answers on here but still having an issue.
One system is saving a binary byte[] into SQL 2014 with Entity Framework 6.
I have a "name" of the record (not the file) but I want to serve up the binary data as a downloadable file via the webapi2. I have it somewhat working but on the browser it shows the ID as the file name and says can't download file. It is prompting to download but then it can't.
Right now for PoC I'm hard coding the mime type to word docs. What am I doing wrong and how should I refactor this to also provide a file name.
I'm saving the document using Office Apps, Task Pane:
https://msdn.microsoft.com/en-us/library/office/jj715284.aspx
FileType: "Compressed" = Returns the entire document (.pptx or .docx) in Office Open XML (OOXML) format as a byte array.
Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
vm.data.data = result.value;
//I call a angularJs service which post..
//return $http.post('https://stapi.local:8443/api/activities', vm.data);
} else {
}
}
);
Then I try to let a user download the document with the following.
WebAPI2 Controller:
[HttpGet, Route("api/activityObjectFile/{id}/{name}")]
public HttpResponseMessage GetDataFile(int id)
{
var fileByte = _activityService.GetFile(id);
HttpResponseMessage response = new HttpResponseMessage { Content = new StreamContent(new MemoryStream(fileByte))};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/msword");
response.Content.Headers.ContentLength = fileByte.Length;
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment filename=test.docx");
return response;
}
Class Service Library:
public Byte[] GetFile(int id)
{
var existingActivityObjectFile = _repo.QueryAll<ActivityObject>().Where(a => a.Id == id).Select(a => a.BinaryData).First();
return existingActivityObjectFile;
}
Client Url:
Download

This should get you going for now:
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(new MemoryStream(fileByte))
};
response.Content
.Headers
.Add("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "test.docx"
};
return response;
As for suggestions for refactoring, you could use my method.
I have an implementation of the IHttpActionResult that I return, it looks like this:
public class DocumentAttachmentResult : IHttpActionResult {
private readonly string fileName;
private readonly string mimeType;
private readonly byte[] blob;
public DocumentAttachmentResult(string fileName, string mimeType, byte[] blob) {
this.fileName = fileName;
this.mimeType = mimeType;
this.blob = blob;
}
private HttpResponseMessage Execute() {
var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(new MemoryStream(this.blob)) };
response.Content.Headers.Add("Content-Type", this.mimeType);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = this.fileName };
return response;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) {
return Task.FromResult(this.Execute());
}
}
And my controller action looks like this:
[HttpGet]
[Route("attachments/{id:guid}/download")]
public IHttpActionResult DownloadAttachment(Guid id) {
var attachment = this.repository.FindById(id);
if (attachment == null) {
return this.NotFound();
}
return new DocumentAttachmentResult(attachment.Name, attachment.FileType.MimeType, attachment.BinaryBlob);
}
I store the file name, mime type and binary in SQL server have it modeled as one entity called Attachment. The mime type and file is captured when I upload a file using another action on my WebApi controller.

Related

ActionResult not returning a PDF file

so I'm in process of migrating of my app from .NET Framework 4.8 to .NET6. Due to some .NET classes being obsolete in newer version I am experiencing some problems.
I have a controller in which one of endpoints is supposed to return a PDF file. But instead of this what it returns right now is just a JSON file containing only a few values.
Endpoint method:
[HttpPost]
public ActionResult DownloadFile([FromForm] string data, [FromForm] string fileType)
{
try
{
if (!string.IsNullOrWhiteSpace(data))
{
return GenerateReportDocument(data, fileType);
}
}
catch (Exception ex)
{
logger.LogError(ex, $"Unexpected error occured in {nameof(DownloadFile)}.");
return StatusCode(500);
}
return NoContent();
}
Then data is taken into GenerateReportDocument method:
private ActionResult GenerateReportDocument(string data, string fileType)
{
var specificationString = specificationGeneratorService.GenerateSpecificationString(JObject.Parse(data));
logger.LogWarning($"Check images in specificationString: '{specificationString}'");
if (string.IsNullOrWhiteSpace(specificationString))
{
specificationString = "<p></p>";
}
var reportGenerator = generateReportDocuments.SingleOrDefault(r => r.FileType.ToLower().Equals(fileType.ToLower()));
if (reportGenerator is not null)
{
return Ok(reportGenerator.GenerateReportDocument(SpecificationFileName, specificationString));
}
return NoContent();
}
Which then is supposed to be taken into third method:
public HttpContent GenerateReportDocument(string fileName, string specificationString)
{
var requestContent = new StringContent(JsonConvert.SerializeObject(new { Html = specificationString }));
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var httpResponse = Flurl.Url.Combine(_abcPdfOptions.PdfConverterUrl, "pdf/convertfromhtmltopdf")
.PostAsync(requestContent).GetAwaiter().GetResult();
HttpContent httpContent = httpResponse.Content;
httpContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"{fileName}.{FileExt}",
};
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return httpContent;
}
This is how those methods looked like in .NET Framework 4.8 (when they were working correctly, presented in same order as their equivalent methods in .NET6 project):
[HttpPost]
public HttpResponseMessage DownloadFile(HttpRequestMessage requestMessage)
{
try
{
var specification = requestMessage.Content.ReadAsFormDataAsync().Result;
string data = specification.Get("Data");
string fileType = specification.Get("FileType");
if (!string.IsNullOrWhiteSpace(data))
{
return GenerateReportDocument(data, fileType);
}
}
catch (Exception ex)
{
logger.LogError(ex, $"Unexpected error occured in {nameof(DownloadFile)}.");
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
private HttpResponseMessage GenerateReportDocument(string data, string fileType)
{
var specificationString = specificationGeneratorService.GenerateSpecificationString(JObject.Parse(data));
logger.LogWarning($"Check images in specificationString: '{specificationString}'");
if (string.IsNullOrWhiteSpace(specificationString))
{
specificationString = "<p></p>";
}
var reportGenerator = generateReportDocuments.SingleOrDefault(r => r.FileType.ToLower().Equals(fileType.ToLower()));
if (reportGenerator != null)
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = reportGenerator.GenerateReportDocument(SpecificationFileName, specificationString),
};
}
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
public HttpContent GenerateReportDocument(string fileName, string specificationString)
{
var requestContent = new StringContent(JsonConvert.SerializeObject(new { Html = specificationString }));
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var httpResponse = Flurl.Url.Combine(_abcPdfOptions.PdfConverterUrl, "pdf/convertfromhtmltopdf")
.PostAsync(requestContent).GetAwaiter().GetResult();
HttpContent httpContent = httpResponse.Content;
httpContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"{fileName}.{FileExt}",
};
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return httpContent;
}
I have tried editing my code and changing the methods into IActionResult or HttpResponse, also altering the return types and their arguments. What am I doing wrong? Is ActionResult a good choice for what I'm trying to do?
Did you try returning FileStreamResult/File ?
var fileStream = new FileStream(#"file_path", FileMode.Open);
return new FileStreamResult(fileStream, "application/pdf");
Or
var fileStream = new FileStream(#"file_path", FileMode.Open);
return File(fileStream , "application/pdf", "FileDownloadName.ext");
if you are doing an ajax call to the controller, then no file will be downloaded, you will receive some json data.you need to make a non ajax call so that server will download the file

Sending a file with RestClient request

I have a service in .Net Core which takes an IFormFile as a parameter. I would like to send the data from this IFormFile up to an Api. The code below is the auto generated code from postman which worked for my request. In the 'attachmentRequest.AddFile("file", file);' line, file is a string of the local path which postman uploaded the file from. What is the best way to send my IFormfile as the file sent? Do I use a stream and save the IFormfile to a location in .Net Core and pass that address?
public async Task<bool> PostIssue(IFormFile file)
{
var client = new RestClient("https://sample/10000/attachments");
client.Timeout = -1;
var attachmentRequest = new RestRequest(Method.POST);
attachmentRequest.AddHeader("X-Atlassian-Token", "no-check");
attachmentRequest.AddHeader("Authorization", "Basic xxxxxxxxxxxxxx=");
attachmentRequest.AddHeader("Cookie", "atlassian.xsrf.token=xxxxxxxxxxxxxxxxxx_lin");
attachmentRequest.AddFile("file", file);
IRestResponse attachmentResponse = client.Execute(attachmentRequest);
Console.WriteLine(attachmentResponse.Content);
if (attachmentResponse.IsSuccessful)
{
return true;
}
else
{
return false;
}
}
You use the second method of AddFile:
public IRestRequest AddFile (string name, byte [] bytes, string fileName, string contentType = null);
Convert the IFormFile file to byte array format instead of getting the path to transfer the file.
What you said is that uploading to the local using stream is indeed possible, but this is an extra action, you can directly operate IFormFile into byte form and transfer it to the API, as shown below:
public async Task<bool> PostIssue(IFormFile file)
{
var client = new RestClient("http://localhost:50855/Users");
client.Timeout = -1;
var attachmentRequest = new RestRequest(Method.POST);
attachmentRequest.AddHeader("X-Atlassian-Token", "no-check");
attachmentRequest.AddHeader("Authorization", "Basic xxxxxxxxxxxxxx=");
attachmentRequest.AddHeader("Cookie", "atlassian.xsrf.token=xxxxxxxxxxxxxxxxxx_lin");
using (var ms = new MemoryStream())
{
file.CopyTo(ms);
var fileBytes = ms.ToArray();
attachmentRequest.AddFile("filename",fileBytes, file.FileName);
}
IRestResponse attachmentResponse = client.Execute(attachmentRequest);
Console.WriteLine(attachmentResponse.Content);
if (attachmentResponse.IsSuccessful)
{
return true;
}
else
{
return false;
}
}
Api:
public async Task<IActionResult> attachments()
{
var file = Request.Form.Files["filename"]; //get the file
//do something you want
return Ok();
}
Here is the test result:

How to submit a file and some other form content to Web API using C# HttpClient

I am trying to submit a file with some KeyValuePairs.(which is id in this case) using HttpClient in C#. the File is being submitted but i cannot read the KeyValuePairs
This is my controller.
[HttpPost]
public async Task<ActionResult> Index(HttpPostedFileBase File)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:65211/");
MultipartFormDataContent form = new MultipartFormDataContent();
//Here I am adding a file to a form
HttpContent content = new StringContent("fileToUpload");
form.Add(content, "fileToUpload");
var stream = File.InputStream;
content = new StreamContent(stream);
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "fileToUpload",
FileName = File.FileName
};
form.Add(content);
// and here i am adding a dictionary with one keyvaluepair
Dictionary<string, string> Parameters = new Dictionary<string, string>();
Parameters.Add("id", "3");
form.Add(new FormUrlEncodedContent(Parameters));
//this will hit the api
var response = await client.PostAsync("/api/Upload", form);
var k = response.Content.ReadAsStringAsync().Result;
return View();
}
This is the Api Code
[Route("api/Upload")]
[HttpPost]
// i have tested public async Task<HttpResponseMessage> Upload(string id) <= giving parameters. the api doesnt hit if i give any
public async Task<HttpResponseMessage> Upload()
{
var request = HttpContext.Current.Request;
HttpResponseMessage result = null;
if (request.Files.Count == 0)
{
result = Request.CreateResponse(HttpStatusCode.OK, "Ok");;
}
var postedFile = request.Files[0];
return Request.CreateResponse(HttpStatusCode.OK, "Ok");
}
I am able to read the file. It gets submitted to the API. the problem is the "id" that i submitted as a keyvaluepair. I don't know how to read it. If i pass parameters to the Api. client returns the error "Not Found".
I finally was able to read both the file and the parameters I sent to the Web API. It was a simple implimentation with HttpContext.Current.Request
This is how i modified the API code.
[Route("api/Upload")]
[HttpPost]
// i have tested public async Task<HttpResponseMessage> Upload(string id) <= giving parameters. the api doesnt hit if i give any
public async Task<HttpResponseMessage> Upload()
{
var request = HttpContext.Current.Request;
var key = Request.Params["key"]; // **<- LOOK AT THIS HERE**
HttpResponseMessage result = null;
if (request.Files.Count == 0)
{
result = Request.CreateResponse(HttpStatusCode.OK, "Ok");;
}
var postedFile = request.Files[0];
return Request.CreateResponse(HttpStatusCode.OK, "Ok");
}
By using HttpContext.Current.Request.Params, I was able to read the other values from the api. Request.Files contains all the files and Request.Params contains all string parameters.

PDF content returned in web response, how to save locally?

I'm getting a web response via an API which converts file (in this case, Powerpoint presentations to PDF).
I do get the response in a string, but when saving this string to a file or a stream (which ends up being saved in a file anyways) I always end up with a blank file, its size is always well over 0 bytes though.
Here's the class that calls the API:
public class CloudConvert
{
private string apiKey;
private static object ProcessResponse;
public CloudConvert(string apiKey)
{
this.apiKey = apiKey;
}
public async Task<string> Convert(string inputFormat, string outputFormat, string fileUrl)
{
var processUrl = await CreateProcess(inputFormat, outputFormat);
return await Upload(processUrl, fileUrl, outputFormat);
}
private async Task<string> CreateProcess(string inputFormat, string outputFormat)
{
var request = new
{
apikey = apiKey,
inputformat = inputFormat,
outputformat = outputFormat
};
var json = await PostJson("https://api.cloudconvert.com/process", request);
dynamic obj = JObject.Parse(json);
ProcessResponse = obj;
return "https:" + obj.url;
}
private static async Task<string> Upload(string processUrl,
string fileUrl, string outputFormat)
{
var request = new
{
input = "download",
file = fileUrl,
outputformat = outputFormat,
download = "false",
wait = "true",
save = "false"
};
return await PostJson(processUrl, request);
}
private static async Task<string> PostJson(string url, object data)
{
var parameters = JsonConvert.SerializeObject(data);
using (var wc = new WebClient())
{
wc.Headers[HttpRequestHeader.ContentType] = "application/json";
try
{
return await wc.UploadStringTaskAsync(url, "POST", parameters);
}
catch (WebException ex)
{
return string.Empty;
}
}
}
}
And how I'm invoking it:
Task<string> task = Task.Run(async () =>
{
return await cloudConvert.Convert("ppt", "pdf", "http://host.com/myfile.ppt");
});
//I have the PDF in the response, as a string.
var pdfContent = task.Result;
//I think it's here that I'm not handling the resulting string
//properly, ending in a blank file. I've tried writing it to a local file as well, same issue.
using (MemoryStream stream = new MemoryStream())
{
StreamWriter writer = new StreamWriter(stream);
writer.Write(pdfContent);
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
//This below is something that saves to an Azure storage container...
//filePath = fileStorage.SaveFile($"{fileGeneratedName}.pdf", stream);
//But this fails as well. Blank file
using (FileStream fs = new FileStream(#"c:\tmp\output.pdf", FileMode.OpenOrCreate))
{
stream.CopyTo(fs);
fs.Flush();
}
}
So regardless of how I'm trying to save the returned content, it seems my encoding is wrong. I noticed that the resulting blank file always has the same number of pages than the input Powerpoint presentation.
Any ideas?
Have your convert function return an HttpResponseMessage:
public async Task<HttpResponseMessage> convert()
do your conversion into, say, a memory stream, and then:
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(outStream.GetBuffer(),0,(int)outStream.Length)
};
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = fname
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return result;

Post files from ASP.NET Core web api to another ASP.NET Core web api

We are building a web application that consist of an Angular2 frontend, a ASP.NET Core web api public backend, and a ASP.NET Core web api private backend.
Uploading files from Angular2 to the public backend works. But we would prefer to post them forward to the private backend.
Current working code
[HttpPost]
public StatusCodeResult Post(IFormFile file)
{
...
}
From there I can save the file to disk using file.CopyTo(fileStream);
However, I want to re-send that file, or those files, or, ideally, the whole request to my second web api core.
I am not sure how to achieve this with the HttpClient class of asp.net core.
I've tried all kinds of things such as
StreamContent ss = new StreamContent(HttpContext.Request.Body);
var result = client.PostAsync("api/Values", ss).Result;
But my second backend gets an empty IFormFile.
I have a feeling it is possible to send the file(s) as a stream and reconstruct them on the other side, but can't get it to work.
The solution must use two web api core.
Solution
Public backend in DMZ
[HttpPost]
public StatusCodeResult Post(IFormFile file)
{
try
{
if (file != null && file.Length > 0)
{
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri(currentPrivateBackendAddress);
byte[] data;
using (var br = new BinaryReader(file.OpenReadStream()))
data = br.ReadBytes((int)file.OpenReadStream().Length);
ByteArrayContent bytes = new ByteArrayContent(data);
MultipartFormDataContent multiContent = new MultipartFormDataContent();
multiContent.Add(bytes, "file", file.FileName);
var result = client.PostAsync("api/Values", multiContent).Result;
return StatusCode((int)result.StatusCode); //201 Created the request has been fulfilled, resulting in the creation of a new resource.
}
catch (Exception)
{
return StatusCode(500); // 500 is generic server error
}
}
}
return StatusCode(400); // 400 is bad request
}
catch (Exception)
{
return StatusCode(500); // 500 is generic server error
}
}
Private backend
[HttpPost]
public void Post()
{
//Stream bodyStream = HttpContext.Request.Body;
if (Request.HasFormContentType)
{
var form = Request.Form;
foreach (var formFile in form.Files)
{
var targetDirectory = Path.Combine(_appEnvironment.WebRootPath, "uploads");
var fileName = GetFileName(formFile);
var savePath = Path.Combine(targetDirectory, fileName);
using (var fileStream = new FileStream(savePath, FileMode.Create))
{
formFile.CopyTo(fileStream);
}
}
}
}
Hi i had the same issue and this is what worked for me :
My setup is netCore MVC netCoreApi.
My MVC Controller looks like :
[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
Sp4RestClient dataPovider = new Sp4RestClient("http://localhost:60077/");
long size = files.Sum(f => f.Length);
foreach (var file in files)
{
await dataPovider.ImportFile(file);
}
return Ok();
}
DataProvider Method :
public async Task ImportFile(IFormFile file)
{
RestClient restClient = new RestClient(_queryBulder.BuildImportFileRequest());
using (var content = new MultipartFormDataContent())
{
content.Add(new StreamContent(file.OpenReadStream())
{
Headers =
{
ContentLength = file.Length,
ContentType = new MediaTypeHeaderValue(file.ContentType)
}
}, "File", "FileImport");
var response = await restClient.Post<IFormFile>(content);
}
}
And least my WebApi Controller :
[HttpPost]
[Route("ImportData")]
public IActionResult Import(IFormFile file)
{
return Ok();
}
To see the complete code here is my RestClient Post method :
public async Task<RestResult<T>> Post<T>(HttpContent content)
{
using (HttpClient httpClient = new HttpClient())
{
HttpResponseMessage response = await httpClient.PostAsync(Endpoint, content);
if (response.StatusCode == HttpStatusCode.Created)
{
T result = JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
return new RestResult<T> { Result = result, ResultCode = HttpStatusCode.OK };
}
RestResult<T> nonOkResult =
new RestResult<T> { Result = default(T), ResultCode = response.StatusCode, Message = await response.Content.ReadAsStringAsync() };
return nonOkResult;
}
}
// Yeah i know im not getting HttpStatusCode.Created back ;)
happy coding ;)
API Code
[Route("api/upload/{id}")]
[HttpPost]
public async Task<IActionResult> Post(string id)
{
var filePath = #"D:\" + id; //+ Guid.NewGuid() + ".png";
if (Request.HasFormContentType)
{
var form = Request.Form;
foreach (var formFile in form.Files)
{
if (formFile.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await formFile.CopyToAsync(stream);
}
}
}
}
return Ok(new { Path = filePath });
}
Back End
[Route("home/UploadFile")]
[HttpPost]
public IActionResult UploadFile(IFormFile file)
{
if (file == null || file.Length == 0)
return Content("file not selected");
var client = new HttpClient();
byte[] data;
using (var br = new BinaryReader(file.OpenReadStream()))
data = br.ReadBytes((int)file.OpenReadStream().Length);
ByteArrayContent bytes = new ByteArrayContent(data);
MultipartFormDataContent multiContent = new MultipartFormDataContent
{
{ bytes, "file", file.FileName }
};
var result = client.PostAsync("http://localhost:2821/api/upload/" + file.FileName, multiContent).Result;
return RedirectToAction("file");
}
Download Source
I was in a similar situation - I needed a proxy method for forwarding not only files but also JSON data and whatnot. I did not want to do any analysis of the data in my proxy to let the final receiver deal with it.
So with some help from #Anton Tykhyy I came to the following working solution:
byte[] arr = null;
using (var mems = new MemoryStream())
{
// read entire body into memory first because it might be chunked with unknown length
await request.Body.CopyToAsync(mems);
await mems.FlushAsync(); // not sure if needed after CopyToAsync - better safe then sorry
arr = mems.ToArray();
}
msg.Content = new ByteArrayContent(arr);
msg.Content.Headers.ContentLength = arr.Length;
// keep content-type header "as is" to preserve multipart boundaries etc.
msg.Content.Headers.TryAddWithoutValidation("Content-Type", request.ContentType);
var response = await _httpClient.SendAsync(msg);
I tested it with complex request that contained multipart form data with JSON field and multiple attached files, and all the data reached my backend server without any issues.
Ignoring the HttpClient when you call the private backend API, can you reference the private Core API project from the public Core API project and call the controller directly from the Core API project? See the request is still null/empty. If the request comes out with a value then the issue is with the use of the HttpClient.
Ideally, you want to create a package library(kind of SDK) for your private Core API that you want to distribute to consuming clients. This acts like a wrapper/proxy. This way you can isolate the private backend system and you can troubleshoot it in isolation. So you public Core API project(which is the private backend client) can reference it as nuget package.

Categories

Resources