ASP.Net Web API - Get POSTed file synchronously? - c#

Is there a way to synchronously process an uploaded file POSTed to a controller in the ASP.Net Web API?
I've tried the process Microsoft proposed here, and it works as described, but I'd like to return something other than a Task<> from the Controller method in order to match the rest of my RESTful API.
Basically, I'm wondering if there is there any way to make this work:
public MyMugshotClass PostNewMugshot(MugshotData data){
//get the POSTed file from the mime/multipart stream <--can't figure this out
//save the file somewhere
//Update database with other data that was POSTed
//return a response
}
Again, I have made the asynchronous example work but am hoping for a way to process the uploaded file before responding to the client.

public class UploadController : ApiController
{
public async Task<HttpResponseMessage> Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var appData = HostingEnvironment.MapPath("~/App_Data");
var folder = Path.Combine(appData, Guid.NewGuid().ToString());
Directory.CreateDirectory(folder);
var provider = new MultipartFormDataStreamProvider(folder);
var result = await Request.Content.ReadAsMultipartAsync(provider);
if (result.FileData.Count < 1)
{
// no files were uploaded at all
// TODO: here you could return an error message to the client if you want
}
// at this stage all files that were uploaded by the user will be
// stored inside the folder we specified without us needing to do
// any additional steps
// we can now read some additional FormData
string caption = result.FormData["caption"];
// TODO: update your database with the other data that was posted
return Request.CreateResponse(HttpStatusCode.OK, "thanks for uploading");
}
}
You might notice that the uploaded files are stored inside the specified folder with names that might look like this: BodyPart_beddf4a5-04c9-4376-974e-4e32952426ab. That's a deliberate choice that the Web API team made that you could override if you want.

Related

Retrieve all contents of Zoho module via REST API c#

I am trying to get the full contents of my modules From Zoho to our local Server. The deluge code does work as it returns to me the data which is being sent via the API. However, once it reaches the API, it is null. Any idea?
Below is the deluge code:
// Create a map that holds the values of the new contact that needs to be created
evaluation_info = Map();
evaluation_info.put("BulkData",zoho.crm.getRecords("Publishers"));
data = Map();
data.put(evaluation_info);
response = invokeurl
[
url :"https://zohoapi.xxxxx.com/publisher/publish"
type :POST
parameters:data
connection:"zohowebapi"
];
info data; (data returns all the data from publishers)
Here is my ASP.NET core restful API. It does ping it and create the file but the content of the file is null.
Route("[controller]")]
[ApiController]
public class PublisherController : ControllerBase
{
[HttpGet("[action]"), HttpPost("[action]")]
public void Publish(string data)
{
(it's already null when it comes here. why?)
string JSONresult = JsonConvert.SerializeObject(data);
string path = #"C:\storage\journalytics_evaluationsv2.json";
using (var file = new StreamWriter(path, true))
{
file.WriteLine(JSONresult.ToString());
file.Close();
}
}
}
}
What am I missing? Thank you
After contacting Zoho support, the solution he offered was to loop through the data in order to get all the contents from a module (if they are more than 200 records. With the solution provided, one doesn't really need the deluge code anymore as long as you have the ZOHO api set to your account in code. This was my final solution. This solution is not scalable at all. It's best to work with the BULK CSV.
// Our own ZohoAPI which lets us connect and authenticate etc. Yours may look slightly different
ZohoApi zohoApi = new ZohoApi();
zohoApi.Initialize();
ZCRMRestClient restClient = ZCRMRestClient.GetInstance();
var allMedicalJournals = new List<ZCRMRecord>();
for (int i = 1; i <= 30; i++)
{
List<ZCRMRecord> accountAccessRecords2 =
restClient.GetModuleInstance("Journals").SearchByCriteria("Tag:equals:MedicalSet", i, 200).BulkData.ToList();
foreach (var newData in accountAccessRecords2)
allMedicalJournals.Add(newData);
}

Returning file from ASP.NET Controller [duplicate]

I'm encountering a problem sending files stored in a database back to the user in ASP.NET MVC. What I want is a view listing two links, one to view the file and let the mimetype sent to the browser determine how it should be handled, and the other to force a download.
If I choose to view a file called SomeRandomFile.bak and the browser doesn't have an associated program to open files of this type, then I have no problem with it defaulting to the download behavior. However, if I choose to view a file called SomeRandomFile.pdf or SomeRandomFile.jpg I want the file to simply open. But I also want to keep a download link off to the side so that I can force a download prompt regardless of the file type. Does this make sense?
I have tried FileStreamResult and it works for most files, its constructor doesn't accept a filename by default, so unknown files are assigned a file name based on the URL (which does not know the extension to give based on content type). If I force the file name by specifying it, I lose the ability for the browser to open the file directly and I get a download prompt. Has anyone else encountered this?
These are the examples of what I've tried so far.
//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);
//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);
//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};
Any suggestions?
UPDATE:
This questions seems to strike a chord with a lot of people, so I thought I'd post an update. The warning on the accepted answer below that was added by Oskar regarding international characters is completely valid, and I've hit it a few times due to using the ContentDisposition class. I've since updated my implementation to fix this. While the code below is from my most recent incarnation of this problem in an ASP.NET Core (Full Framework) app, it should work with minimal changes in an older MVC application as well since I'm using the System.Net.Http.Headers.ContentDispositionHeaderValue class.
using System.Net.Http.Headers;
public IActionResult Download()
{
Document document = ... //Obtain document from database context
//"attachment" means always prompt the user to download
//"inline" means let the browser try and handle it
var cd = new ContentDispositionHeaderValue("attachment")
{
FileNameStar = document.FileName
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
return File(document.Data, document.ContentType);
}
// an entity class for the document in my database
public class Document
{
public string FileName { get; set; }
public string ContentType { get; set; }
public byte[] Data { get; set; }
//Other properties left out for brevity
}
public ActionResult Download()
{
var document = ...
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = document.FileName,
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = false,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(document.Data, document.ContentType);
}
NOTE: This example code above fails to properly account for international characters in the filename. See RFC6266 for the relevant standardization. I believe recent versions of ASP.Net MVC's File() method and the ContentDispositionHeaderValue class properly accounts for this. - Oskar 2016-02-25
I had trouble with the accepted answer due to no type hinting on the "document" variable: var document = ... So I'm posting what worked for me as an alternative in case anybody else is having trouble.
public ActionResult DownloadFile()
{
string filename = "File.pdf";
string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
byte[] filedata = System.IO.File.ReadAllBytes(filepath);
string contentType = MimeMapping.GetMimeMapping(filepath);
var cd = new System.Net.Mime.ContentDisposition
{
FileName = filename,
Inline = true,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(filedata, contentType);
}
To view file (txt for example):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);
To download file (txt for example):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");
note: to download file we should pass fileDownloadName argument
Darin Dimitrov's answer is correct. Just an addition:
Response.AppendHeader("Content-Disposition", cd.ToString()); may cause the browser to fail rendering the file if your response already contains a "Content-Disposition" header. In that case, you may want to use:
Response.Headers.Add("Content-Disposition", cd.ToString());
I believe this answer is cleaner, (based on
https://stackoverflow.com/a/3007668/550975)
public ActionResult GetAttachment(long id)
{
FileAttachment attachment;
using (var db = new TheContext())
{
attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
}
return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
}
Below code worked for me for getting a pdf file from an API service and response it out to the browser - hope it helps;
public async Task<FileResult> PrintPdfStatements(string fileName)
{
var fileContent = await GetFileStreamAsync(fileName);
var fileContentBytes = ((MemoryStream)fileContent).ToArray();
return File(fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf);
}
FileVirtualPath --> Research\Global Office Review.pdf
public virtual ActionResult GetFile()
{
return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}
Action method needs to return FileResult with either a stream, byte[], or virtual path of the file. You will also need to know the content-type of the file being downloaded. Here is a sample (quick/dirty) utility method. Sample video link
How to download files using asp.net core
[Route("api/[controller]")]
public class DownloadController : Controller
{
[HttpGet]
public async Task<IActionResult> Download()
{
var path = #"C:\Vetrivel\winforms.png";
var memory = new MemoryStream();
using (var stream = new FileStream(path, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
var ext = Path.GetExtension(path).ToLowerInvariant();
return File(memory, GetMimeTypes()[ext], Path.GetFileName(path));
}
private Dictionary<string, string> GetMimeTypes()
{
return new Dictionary<string, string>
{
{".txt", "text/plain"},
{".pdf", "application/pdf"},
{".doc", "application/vnd.ms-word"},
{".docx", "application/vnd.ms-word"},
{".png", "image/png"},
{".jpg", "image/jpeg"},
...
};
}
}
If, like me, you've come to this topic via Razor components as you're learning Blazor, then you'll find you need to think a little more outside of the box to solve this problem. It's a bit of a minefield if (also like me) Blazor is your first forray into the MVC-type world, as the documentation isn't as helpful for such 'menial' tasks.
So, at the time of writing, you cannot achieve this using vanilla Blazor/Razor without embedding an MVC controller to handle the file download part an example of which is as below:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
[Route("api/[controller]")]
[ApiController]
public class FileHandlingController : ControllerBase
{
[HttpGet]
public FileContentResult Download(int attachmentId)
{
TaskAttachment taskFile = null;
if (attachmentId > 0)
{
// taskFile = <your code to get the file>
// which assumes it's an object with relevant properties as required below
if (taskFile != null)
{
var cd = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileNameStar = taskFile.Filename
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
}
}
return new FileContentResult(taskFile?.FileData, taskFile?.FileContentType);
}
}
Next, make sure your application startup (Startup.cs) is configured to correctly use MVC and has the following line present (add it if not):
services.AddMvc();
.. and then finally modify your component to link to the controller, for example (iterative based example using a custom class):
<tbody>
#foreach (var attachment in yourAttachments)
{
<tr>
<td>#attachment.Filename </td>
<td>#attachment.CreatedUser</td>
<td>#attachment.Created?.ToString("dd MMM yyyy")</td>
<td><ul><li class="oi oi-circle-x delete-attachment"></li></ul></td>
</tr>
}
</tbody>
Hopefully this helps anyone who struggled (like me!) to get an appropriate answer to this seemingly simple question in the realms of Blazor…!

How to receive MultipartFormData on ASP.NET C#

I'm working with an iOS guy. He wants to upload images through WebAPI ASP.NET. I've to make a call that can receive those images.He said he is using AFNetworking to send data through AFMultipartFormData. My question is that how can I receive this at my end? Should I take the data in JSON format? Or what measures needs to be done for this purpose? I want to know the whole process as this is my first time working with MultipartFormData.UPDATEBased on the answer I used this:
[HttpPut]
public IHttpActionResult GetPatientFilesAction(int id, Model.Patients.PatientFiles patientFile)
{
Model.Patients.PatientFiles pFile=new Model.Patients.PatientFiles();
try
{
HttpPostedFile xmlFile = HttpContext.Current.Request.Files[0];
var fileForm = HttpContext.Current.Request.Form;
var fileKey = HttpContext.Current.Request.Form.Keys[0];
string[] jsonformat = fileForm.GetValues(fileKey);
pFile = Newtonsoft.Json.JsonConvert.DeserializeObject<Model.Patients.PatientFiles>(jsonformat[0]);
}
catch (Exception ex)
{
pFile.ErrorMessage = ex.ToString();
}
return Ok(pFile);
}
But the iOS guy got:
Request failed: unsupported media type (415)
Inside the web API controller you can access the image file using the code below :-
HttpPostedFile xmlFile = HttpContext.Current.Request.Files[0];
If you have more than one files posted, replace Files[0] with respective count 1 or 2 etc.
And then you can access the JSON using the code :
var fileForm = HttpContext.Current.Request.Form;
var fileKey = HttpContext.Current.Request.Form.Keys[0];
string[] jsonformat = fileForm.GetValues(fileKey);
var yourModel = JsonConvert.DeserializeObject<YourClassType>(jsonformat[0]);
If you have more than one json strings posted, replace jsonformat[0] with respective count 1 or 2 etc.

Asp.net MVC Async upload Issue

We are working on a file upload and have chosen the blueimp plugin. We have this working in the following way.
User selects all files they wish to upload and clicks a button to upload all in one go.
This part works fine and sends all files at once to the server.
The server code sample only is as follows.
public async Task<ActionResult> Upload()
{
var result = await new Uploader().UploadFile(Request.Files[0]);
return Json(result);
}
public class Uploader
{
public async Task<UploadResult> UploadFile(HttpPostedFileBase file)
{
//do file processing and save file to disk
var filerecord = _fileService.GetById(xxxx);
filerecord.files.Add(new Info { name = file.FileName, size = file.ContentLength });
//Here for us its Mongo but any not sure what the result would be against another db
await _fileService.SaveAsync(filerecord);
return new UploadResult { message = "ok" };
}
}
//js
fi.fileupload({
url: 'uploadfile',
dataType: 'json',
autoUpload: false,
singleFileUploads:false,
maxNumberOfFiles: maxFiles,
acceptFileTypes: /(\.|\/)(jpe?g)$/i,
maxFileSize: 1048576*2, //1MB
messages: { },
// Enable image resizing, except for Android and Opera,
// which actually support image resizing, but fail to
// send Blob objects via XHR requests:
disableImageResize: true,
previewMaxWidth: 50,
previewMaxHeight: 50,
previewCrop: false,
dropZone: $('#dropzone')
});
fi.on('fileuploadadd', function (e, data) {
$('form').submit(function (e) {
data.submit();
return false;
});
});
Just a note that the db in this case is Mongo
The Problem
Uploading single files at a time i.e select a file click upload all works as expected.
Select more than one file and hit upload button all files are uploaded and saved to the disk without an issue. However I would expect the following behaviour
Get record from db
Add info about the file
Save the record
As mentioned this works great with one file at a time but when they all come in at once I am guessing the async behaviour causes the method to loose context of what file its working on as the 3 steps in the debugger seem to be called per file randomly rather than in order.
I have tried various approaches like change the order of the way things happen, also using ConfigureAwait etc.
Any advice would be appreciated.
If i understand your case correctly this code should work for you:
public async Task<ActionResult> Upload()
{
var files = new HttpPostedFileBase[Request.Files.Count];
Request.Files.CopyTo(files, 0);
var tasks = files.Select(f=>new Uploader().UploadFile(f));
await Task.WhenAll(tasks);
return Json(tasks.Where(v=>v.Status == TaskStatus.RanToCompletion ).Select(v=>v.Result).ToList());
}

Why aren't server-side changes being reflected in my app?

I have a simple C# Windows UAP project that uses a HttpClient to call a PHP script on a web server. The script returns an XML document that contains some GUIDs (*.xml files with the extension omitted, leaving a GUID). My app then uses that data. If I make a change on the server, coincidently causing the PHP script to return different data, my app still uses the old data (to be exact, it does this until the app is restarted). If a call the script using a browser, the data appears how I expect it to, but the app doesn't do what it should with the data. It almost seems like the first response is being cached.
Here's an example:
Say I start with one file in the folder where my PHP script finds all *.xml files (eef8401a-b5cd-4da7-ad36-0fb7a8fa6c62.xml in this case).
The script should and does return:
<?xml version="1.0"?>
<eventlist>
<id>eef8401a-b5cd-4da7-ad36-0fb7a8fa6c62</id>
</eventlist>
When I run the app, its response is the same.
So far, all is working as it should.
However, say I add a new XML file in the folder (now eee8401a-b5cd-4da7-ad36-0fb7a8fa6c62.xml and eef8401a-b5cd-4da7-ad36-0fb7a8fa6c62.xml). The script returns just like I expect it to:
<?xml version="1.0"?>
<eventlist>
<id>eee8401a-b5cd-4da7-ad36-0fb7a8fa6c62</id>
<id>eef8401a-b5cd-4da7-ad36-0fb7a8fa6c62</id>
</eventlist>
The app's response this time is still the previous one (with only one id element).
This persists until the app restarts. After that, it works like it should--until I make another change in the folder.
Here's my PHP script:
<?php
header('Content-type: text/xml');
$handler = opendir('C:\path\to\folder\\');
$ids = '';
while (($file = readdir($handler)) !== FALSE) {
if (strpos($file, '.xml') !== FALSE) {
$ids .= '<id>'.str_replace('.xml', '', $file).'</id>';
}
}
closedir($handler);
exit('<eventlist>'.$ids.'</eventlist>');
?>
And my app's C# code:
public static async Task<string> ContactServer(ApiMethod m, IProgress<double[]> prog, params KeyValuePair<string, string>[] args) {
using (var client = new HttpClient()) {
var path = m.ToString().ToLower() + "/"; // in this case, is 'list/'.
//...
// other stuff, omitted for simplicity
//...
var fullUrl = "http://example.com/path/to/api/" + path; // in this case, is 'http://example.com/path/to/api/list/'.
var d = await client.GetAsync(new Uri(fullUrl));
var data = await d.Content.ReadAsStringAsync();
Debug.WriteLine(data);
return data;
}
}
Again, my PHP script works fine, but my app gets a different response than I do when I run the script in my browser manually.
Why is this happening?
Windows Runtime which provides the HTTPClient has a very aggressive webcaching strategy to save user's bandwidth. Unless your server explicitly sets a cache duration header, it will return all** requests with the same Uri directly from the cache without even contacting your server.
You can turn off this behaviour by:
Setting a cache duration header (cache-control: no-cache, etc.).
var request = (HttpWebRequest)WebRequest.Create(url.ToString());
if (request.Headers == null)
request.Headers = new WebHeaderCollection();
request.Headers.Add("Cache-Control", "no-cache");
Adding a random number to your requests query string.
string uri = "http://host.com/path?cache=" + Guid.NewGuid().ToString();
Or, as CodeCaster suggested, you could also avoid the caching by using the If-Modified-Since header
HttpWebRequest request = HttpWebRequest.CreateHttp(url);
if (request.Headers == null)
request.Headers = new WebHeaderCollection();
// Make sure that you format time string according RFC.
request.Headers[HttpRequestHeader.IfModifiedSince] = DateTime.UtcNow.ToString("r");
or you can add to every request the client makes with
httpClient.DefaultRequestHeaders.IfModifiedSince = DateTime.UtcNow.ToString("r");
Using Windows.Web.Http you could also use
var httpFilter = new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
httpFilter.CacheControl.ReadBehavior =
Windows.Web.Http.Filters.HttpCacheReadBehavior.MostRecent;
var httpClient = new Windows.Web.Http.HttpClient(httpFilter);
** I have said all requests, but I don't know if that is strictly correct, I will take a look and check and update here, though CodeCaster has suggested GET and HEAD only; I have certainly seen on GET, unsure about others off the top of my head

Categories