As you can see, I am trying to send an image, and a name through a POST command to a local function.
How Can I read both of these parameters in C#?
This is what I have tried but It can only read File Image.
[FunctionName("Test")]
public static async Task<HttpResponseMessage>
Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route =
null)]HttpRequestMessage req, TraceWriter log)
{
//Check if the request contains multipart/form-data.
if (!req.Content.IsMimeMultipartContent())
{
return req.CreateResponse(HttpStatusCode.UnsupportedMediaType);
}
foreach (var stream in contents.Contents)
{
try
{
var fileBytes = await stream.ReadAsByteArrayAsync();
var fileinfo = new FileInfo(stream.Headers.ContentDisposition.FileName.Trim('"'));
//Can Read File image like this.
}
catch(Exception e)
{
return req.CreateErrorResponse(HttpStatusCode.Conflict, e);
}
Is there a workaround to do this like using memory stream?
According to your requirement, I assumed that you could use HttpContentMultipartExtensions.ReadAsMultipartAsync and you would get the MultipartMemoryStreamProvider, then you could leverage the following code for reading your uploaded files:
var multipartMemoryStreamProvider= await req.Content.ReadAsMultipartAsync();
foreach (HttpContent content in multipartMemoryStreamProvider.Contents)
{
// for reading the uploaded file
var filename= content.Headers.ContentDisposition.FileName.Trim('"');
var stream=await content.ReadAsStreamAsync();
//for formdata, you could check whether `content.Headers.ContentDisposition.FileName` is empty
log.Info($"name={content.Headers.ContentDisposition.Name},value={await content.ReadAsStringAsync()}");
}
Moreover, you could follow this issue about creating your custom MultipartFormDataMemoryStreamProvider based on MultipartMemoryStreamProvider. And this issue about building custom InMemoryMultipartFormDataStreamProvider based on MultipartStreamProvider.
You can use MultipartFormDataStreamProvider
Use FileData property to get the posted files
Use FormData property to get the values of any form data posted based upon the key.
Check this File Upload and Multipart MIME.
Related
I am trying to write an API endpoint that would allow a user to send a file to my server using Asp.NET Core 3.1.
I have the following action method that needs to respond to the client's request and process the file validation and store the file.
[HttpPost, Route("api/store")]
public async Task<IActionResult> Store([FromBody] MultipartFormDataContent content)
{
// Validate the file, store it.
return Ok();
}
I am trying to send the file from the client-side like this
using HttpClient client = new HttpClient();
var multiForm = new MultipartFormDataContent();
multiForm.Add(new StringContent("1/12/1.jpg"), "custom_file_name");
FileStream fs = System.IO.File.OpenRead("1.jpg");
multiForm.Add(new StreamContent(fs), "file", "1.jpg");
// send request to API
var url = "https://localhost:123/api/store";
var response = await client.PostAsync(url, multiForm);
But the server returns HTTP Code 404 Not Found.
How can I correctly tweak the Store method above so it accepts the file being sent by the client?
You shouldn't include api/ in the action method route. The controller's route is already prefixed with api. Also, api/[controller] means the controller route will be api/ControllerName, then your action will be api/ControllerName/store.
If your controller class is named ThingsController then its route is api/things, and your Store action should be api/things/store.
You don't need to use both HttpPost and Route attributes on an action. The Route attribute accepts all HTTP verbs, so use one or the other (typically just HttpPost or whatever HTTP verb you will be handling with the action method. So:
[ApiController]
[Route("api/[controller]")]
public class ThingsController : ControllerBase
{
// POST api/things/store
[HttpPost("store")]
public async Task<IActionResult> Store([FromBody] MultipartFormDataContent content)
{
// Do stuff
}
}
Also, the controller should be using IFormFile to deal with file uploads in your controller. The documentation is here:
Upload files in ASP.NET Core .
Your controller would accept a IFormFile files parameter rather than MultipartFormDataContent content.
Example from MSDN, adapted to your action
[HttpPost("store")]
public async Task<IActionResult> Store(List<IFormFile> files)
{
// Get total size
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
// Gets a temporary file name
var filePath = Path.GetTempFileName();
// Creates the file at filePath and copies the contents
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size, filePath });
}
If you're dealing with a single file, you can just pass IFormFile file instead of a list, and get rid of the looping in the action method.
I have not tested this code myself, wrote it on a mobile device. Hopefully it solves the problem though.
I am trying to upload a PDF file from postman and trigger the azure function to upload the PDF file into azure blob storage. But when i try to open the PDF file it is always empty.
I tried to convert the file into memory stream and upload it into azure blob. The file gets uploaded but when i try to open the file it will be blank.
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info(req.Content.ToString());
string Message = "";
log.Info("Test storage conn string" + req.Content.Headers.ContentDisposition.ToString());
string contentType = req.Content.Headers?.ContentType?.MediaType;
log.Info("contentType : " + req.Content.IsMimeMultipartContent());
string name = Guid.NewGuid().ToString("n");
log.Info("Name" + name);
string body;
body = await req.Content.ReadAsStringAsync();
log.Info("body" + body.Substring(body.IndexOf("filename=\""),body.IndexOf("pdf")- body.IndexOf("filename=\"")));
//Upload a file to Azure blob
string storageConnectionString = "xxxx";
//DirectoryInfo directoryInfo = new DirectoryInfo("D:\\Upload_Files");
// var files = directoryInfo.EnumerateFiles();
// Retrieve storage account from connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageConnectionString);
// Create the blob client.
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
// Retrieve reference to a previously created container.
CloudBlobContainer container = blobClient.GetContainerReference("docstorage");
//foreach (FileInfo inputFile in files)
//{
CloudBlockBlob blockBlob = container.GetBlockBlobReference("Test\\" + name+".pdf");//write name here
//blockBlob.Properties.ContentType = "application/pdf";
//blockBlob.UploadFromFile(inputFile.FullName);
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(body)))
{
log.Info("streaming : ");
await blockBlob.UploadFromStreamAsync(stream);
}
//}
return Message == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Error")
: req.CreateResponse(HttpStatusCode.OK, "Doc Uploaded Successfully");
}
I want to open the PDF file as it is from the blob. I see that i am able to upload text file and when i download i can see the content but when i upload pdf file i dont see the content
Calling .ReadAsStringAsync on a binary document wont work - you have to call ReadAsByteArrayAsync or ReadAsStreamAsync.
var body = await req.Content.ReadAsByteArrayAsync();
...
using (Stream stream = new MemoryStream(body))
{
await blockBlob.UploadFromStreamAsync(stream);
}
OR
var body2 = await req.Content.ReadAsStreamAsync();
body.Position = 0;
...
await blockBlob.UploadFromStreamAsync(body);
It's really simple to do something like that. Everything relative to bindings should be declared in the function parameters so, having this in mind, you have to declare your blob stream as a parameter. Check this as an example:
public static async Task<string> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
[Blob("azurefunctions/test.pdf", FileAccess.Write)] Stream blob,
ILogger log)
Please, note the second parameter called blob is declared as a Stream to be able to save the content read from the input. The second point is the attribute decorating the parameter, Blob allows to define several aspects of the new blob file that will be uploaded in our Azure Storage service. As you can see, the container is called azurefunctions and the file will be called test.pdf.
In order to save the content you can use the following code:
byte[] content = new byte[req.Body.Length];
await req.Body.ReadAsync(content, 0, (int)req.Body.Length);
await blob.WriteAsync(content, 0, content.Length);
Hope this can be helpful for your question.
These are useful links to check and test your code:
Azure Blob storage bindings for Azure Functions
How to send multipart/form-data with PowerShell Invoke-RestMethod
I got a Web-Api with the following simplified method for obtaining a file:
[HttpPut("Test/{id}")]
public IActionResult PutTest(string id)
{
Stream file = Request.Body;
//rest of method
return StatusCode(201);
}
It works fine, although in the SwaggerUI page that gets created there's no mention of the method expecting a file in the body. Is there a way to specify the method expects a file in the generated SwaggerUI page?
I can simulate the input using the following piece of code:
var content = new StreamContent(new FileStream("C:\temp\test.txt", FileMode.Open));
using (var client = new HttpClient())
using (var response = client.PutAsync(url, content))
{
var result = response.Result;
//more code
}
The file upload is not documented because it's not a param in the action method. Swagger has no way of knowing what you're doing inside the action itself. Attempting to handle a file upload this way is bad practice anyways. You can use IFormFile, but that only works if your request body is encoded as multipart/form-data. If you're dealing with JSON or basically anything that qualifies as FromBody, then you need to bind to a byte[]:
[HttpPut("Test/{id}")]
public IActionResult PutTest(string id, byte[] file)
{
//rest of method
return StatusCode(201);
}
Now, the automatic FromBody applied by the [ApiController] attribute only works with class types, so I'm not sure off the top of my head whether it will apply to a byte[]. If not, just do:
public IActionResult PutTest(string id, [FromBody]byte[] file)
I have created and HTTP Triggered Azure Function (v2) using .NET Core with the hopes that I can execute the function while passing in some info in the request body and then have the function return/download a file in the browser. Unfortunately I am struggling to get this working.
Below is a snippet of Code
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, ILogger log)
{
string csv;
//Do some stuff to create a csv
byte[] filebytes = Encoding.UTF8.GetBytes(csv);
req.HttpContext.Response.Headers.Add("content-disposition", "attachment;filename=Export.csv");
req.HttpContext.Response.ContentType = "application/octet-stream";
return (ActionResult)new OkObjectResult(filebytes);
}
When I do a post using Postman the request is accepted but the response is 406 "unacceptable" and the output in the log states
"Microsoft.AspNetCore.Mvc.Infrastructure.DefaultOutputFormatterSelector[1]
No output formatter was found for content type 'application/octet-stream' to write the response."
I've tried multiple content types including text/plain and text/csv, all give the same response about output formatting.
If I remove or comment out the ContentType the request processes and returns a 200 but the filebytes are returned in the response body instead of being downloaded in the browser.
You'll need a FileContentResult for this:
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, ILogger log)
{
string csv;
//Do some stuff to create a csv
byte[] filebytes = Encoding.UTF8.GetBytes(csv);
return new FileContentResult(filebytes, "application/octet-stream") {
FileDownloadName = "Export.csv"
};
}
While the comments correctly point out that the ideal solution is to kick off processing in the HTTP function asynchronously, return a 202 Accepted response, save the result to blob storage, have the client wait for processing to complete before starting the blob download and then delete the blob once it's been downloaded, current Azure Functions pricing is only $0.000016/GB-s so you may find that to be unnecessarily complicated unless you have quite high traffic.
I'm facing a situation where I've to read the form data from incoming request in ASP.NET Web API twice (from model binder and filter). I've tried using LoadIntoBufferAsync but no luck.
// from model binder
Request.Content.LoadIntoBufferAsync().Wait();
var formData = Request.Content.ReadAsFormDataAsync().Result;
// from filter
var formData = Request.Content.ReadAsFormDataAsync().Result;
The problem is that the underlying buffer for content is a forward-only stream that can only be read once.
Why do you need to read it twice? A little more context would help. Is it that you are reading from two separate filters?
EDIT: might try reading directly from MS_HttpContext and using that as your content stream (don't think this works in a self hosted environment).
using (var s = new System.IO.MemoryStream()) {
var ctx = (HttpContextBase)actionContext.Request.Properties["MS_HttpContext"];
ctx.Request.InputStream.Seek(0, System.IO.SeekOrigin.Begin);
ctx.Request.InputStream.CopyTo(s); var body =
System.Text.Encoding.UTF8.GetString(s.ToArray());
}
During the development of a REST API, we had a need to authenticate a request prior to allowing the response to be processed within the controller, and so this created a need to be able to read the header as well as the form (if any) to determine if the credentials were passed into the request within the body of the form rather than through the request header.
A few lines of code reset the stream pointer to the beginning of the stream so that MVC would be able to read the form and populate the view model in the controller
public class WebServiceAuthenticationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authenticationHeaderValue = actionContext.Request.Headers.Authorization;
try
{
if (authenticationHeaderValue != null)
{
var webRequestInfo = new WebRequestInfo(actionContext.Request.Method, actionContext.Request.RequestUri);
this.AuthenticationHeaderService.LogOnUsingAuthenticationHeader(authenticationHeaderValue, webRequestInfo);
}
else if (actionContext.Request.Content.IsFormData())
{
Task<NameValueCollection> formVals = actionContext.Request.Content.ReadAsFormDataAsync();
this.AuthenticationFormService.LogOnUsingFormsAuthentication(formVals.Result);
// reset the underlying stream to the beginning so that others may use it in the future...
using (var s = new System.IO.MemoryStream())
{
var ctx = (HttpContextBase) actionContext.Request.Properties["MS_HttpContext"];
ctx.Request.InputStream.Seek(0, System.IO.SeekOrigin.Begin);
}
}
}
catch (Exception)
{
throw;
}
}
}
Initially the data model was not being created by MVC and a null was passed into the controller method. After resetting the stream, MVC was able to read the form, create and populate the data model, and pass it into the controller method.
[WebServiceAuthentication]
public HttpResponseMessage Get(DocumentRequestModel requestForm)
{
var response = CreateResponse(HttpStatusCode.OK);
response.Content = new ByteArrayContent(this.documentService.GetDocument(requestForm.DocumentId.ToString()));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return response;
}
You really should not need to do that. At the end of the day, HttpContext Stream points to the same stream Web API reads from.
You can try to put LoadIntoBufferAsync in both places as one could trigger before the other and it was already in the buffer, calling LoadIntoBufferAsync has no side effect.
// from model binder
Request.Content.LoadIntoBufferAsync().Wait();
var formData = Request.Content.ReadAsFormDataAsync().Result;
// from filter
Request.Content.LoadIntoBufferAsync().Wait();
var formData = Request.Content.ReadAsFormDataAsync().Result;