i tried to send a basic file from my integration test project in C# to a web api .
But i don't know why, each call i get an exception .
Json.JsonSerializationException : Error getting value from 'ReadTimeout' on 'System.Io.FileStream'
I found this property can't be read , so maybe that why my httpclient can't serialize it.
So how can i send a file to a web api ?
This is my code from the client:
using (StreamReader reader = File.OpenText("SaveMe.xml"))
{
response = await client.PostAsJsonAsync($"api/registration/test/", reader.BaseStream);
response.EnsureSuccessStatusCode();
}
And my controller:
[Route("api/registration")]
public class RegistrationController : Controller
{
[HttpPost, Route("test/")]
public ActionResult AddDoc(Stream uploadedFile)
{
if (uploadedFile != null)
{
return this.Ok();
}
else
{
return this.NotFound();
}
}
Here the screenShot we can see , the property [ReadTimeout] can't be access.
I'm not sure if they still support PostAsJsonAsync in .NET Core, which I am on. So I decided to rewrite your snippet as follows using PostAsync:
using (StreamReader reader = File.OpenText("SaveMe.xml"))
{
var response = await client.PostAsync($"api/registration/test/", new StreamContent(reader.BaseStream));
}
Update your API method to look like:
[Route("api/registration")]
public class RegistrationController : Controller
{
[HttpPost, Route("test/")]
public ActionResult AddDoc()
{
//Get the stream from body
var stream = Request.Body;
//Do something with stream
}
First you have to read all the data from file and only then send it. To open the .xml files use XmlReader. look here Reading Xml with XmlReader in C#
Related
Example code:
I export an object as JSON in a file
[HttpGet]
public IHttpActionResult ExportObjectToFile()
{
var exampleObject = new ExampleObject();
var response = Request.CreateResponse(HttpStatusCode.OK, exampleObject, JsonMediaTypeFormatter.DefaultMediaType);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue(DispositionTypeNames.Attachment)
{
FileName = exampleObject.FileName;
};
return ResponseMessage(response);
}
Now I want to import and create a new object using multipart/form-data on Postman to attach the file with the request
[HttpPost]
public async Task<IHttpActionResult> ImportObjectFromFile()
{
var multipartForm = await Request.Content.ReadAsMultipartAsync().ConfigureAwait(false);
var jsonString = await multipartForm.Contents[0].ReadAsString().ConfigureAwait(false);
var importedExampleObject = JsonConvert.DeserializeObject<exampleObject>(jsonString);
return ResponseMessage(importedExampleObject);
}
I know it's considered bad practice to not store the file first locally but this is more of a learning exercise for me, it helps me understand how file uploads work.
So with the above code importedExampleObject is created but all the fields are null, essentially all the data is lost, and there are no exceptions thrown, so I'm at a loss.
I am using Insomnia for testing an API, but the same happens with Postman.
I want to test a file upload, with the following controller:
public async Task<IActionResult> Post([FromForm]IFormFile File)
If I set the request as a multipart request:
it works.
However, if I set it as a binary file:
I don't know how to get the data. How can it be done?
Also, in the controller method's signature, if I change [FromForm] to [FromBody], I'm not getting data.
Can someone clarify this for me?
As you've noticed already, using binary file option in Postman/Insomnia doesn't work the standard way. There are three different ways to upload file via RESTful API, and you have to choose one.
I've included code snippets that read the uploaded file contents to a string and output it -- try sending a text file, and you should get the contents of the file in the 200 response.
Form-data upload
This is the most popular/well-known upload method formatting the data you send as a set of key/value pairs. You normally need to specify Content-Type to multipart/form-data in the request, and then use [FromForm] attribute in MVC to bind values to variables. Also, you can use the built-in IFormFile class to access the file uploaded.
[HttpPost]
public async Task<IActionResult> PostFormData([FromForm] IFormFile file)
{
using (var sr = new StreamReader(file.OpenReadStream()))
{
var content = await sr.ReadToEndAsync();
return Ok(content);
}
}
Body upload
You can send body in the format that MVC understands, e.g. JSON, and embed the file inside it. Normally, the file contents would be encoded using Base64 or other encoding to prevent character encoding/decoding issues, especially if you are sending images or binary data. E.g.
{
"file": "MTIz"
}
And then specify [FromBody] inside your controller, and use class for model deserialization.
[HttpPost]
public IActionResult PostBody([FromBody] UploadModel uploadModel)
{
var bytes = Convert.FromBase64String(uploadModel.File);
var decodedString = Encoding.UTF8.GetString(bytes);
return Ok(decodedString);
}
// ...
public class UploadModel
{
public string File { get; set; }
}
When using large and non-text files, the JSON request becomes clunky and hard to read though.
Binary file
The key point here is that your file is the whole request. The request doesn't contain any additional info to help MVC to bind values to variables in your code. Therefore, to access the file, you need to read Body in the Request.
[HttpPost]
public async Task<IActionResult> PostBinary()
{
using (var sr = new StreamReader(Request.Body))
{
var body = await sr.ReadToEndAsync();
return Ok(body);
}
}
Note: the example reads Body as string. You may want to use Stream or byte[] in your application to avoid file data encoding issues.
In addition of the above, in case of multipart file conversion to base64String you can refer to the below:
if (File.Length> 0)
{
using (var ms = new MemoryStream())
{
File.CopyTo(ms);
var fileBytes = ms.ToArray();
string s = Convert.ToBase64String(fileBytes);
}
}
Note: I am using this code for .NET CORE 2.1
Basically my application needs to be able to handle large file uploads and for that I looked into the File uploads docs by Microsoft.
Here they mention large file uploads with streaming, which sounded nice so I copied the code from the docs. However, my problem is that I cannot get the original uploaded file props (FileName, Length etc...) from the HttpRequest.
I have already tried accessing request.Form.Files, but that way I get an exception: IOException: Unexpected end of Stream, the content may have already been read by another component. Since I try to access the request afterwards again.
What's the best way to get the FileName + Length after the upload has completed (with streaming of course)?
Edit:
The controller's code currently looks like this:
[Authorize]
[Route("api/[controller]")]
public class FileController : Controller
{
private FileManagerDbContext _context;
public FileController(FileManagerDbContext context) {
_context = context;
}
[HttpPost("upload")]
[DisableFormValueModelBinding]
public async Task<IActionResult> Upload()
{
using (var stream = System.IO.File.Create("myfile.temp"))
{
await Request.StreamFile(stream);
}
var fileToSave = new File();
fileToSave.FileName = null;
fileToSave.FileSize = 0;
fileToSave.UploadDate = new DateTime();
_context.Files.Add(fileToSave);
_context.SaveChanges();
return Ok(fileToSave);
}
}
After the file upload is complete, I want to persist some minimal metadata to my database about the uploaded file. But I cannot get the necessary properties.
You can rewind inside of your controller as well if you wish:
if (request?.ContentLength != null)
{
request.EnableRewind();
request.Body.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(request.Body, Encoding.UTF8))
{
body = reader.ReadToEnd();
//Do your thing with the body content
}
}
This error is occurred if your don't add this attribute on top of your controller action method. It is use to disable the form value model binding here to take control of handling
potentially large files.
[DisableFormValueModelBinding]
Okay, so a workaround I found is to enable HttpRequest rewinding. For this I needed to add this piece of code to Startup.cs:
app.Use(async (context, next) => {
context.Request.EnableRewind();
await next();
});
This way the UnexpectedEndOfStream exception will not be thrown since, the stream can be rewinded.
You can download the data using the access control file, taking into account that the file has a public URL.
var webRequest = HttpWebRequest.Create(url);
using (var webResponse = webRequest.GetResponse())
{
var file_size = webResponse.Headers.Get("Content-Length");
var file_name = webResponse.ResponseUri.Segments.Last();
}
I have a REST controller that streams the response in csv format using a helper method like below:
public static void CsvStreamHelper(IEnumerable<T> data, Stream stream)
{
using (var writer = new StreamWriter(stream))
{
foreach (var line in data)
{
// format csv lines here
writer.WriteLine(lineString);
}
writer.Flush();
}
}
Then, I'm using this in my Controller like:
public Task<IActionResult> MyController()
{
var data = // Get data here.
CsvStreamHelper(data, this.HttpContext.Response.Body);
return new EmptyResult();
}
This is working fine. However, I would like to use content negotiation middleware with custom formatter like here while continue to stream response.
I can override WriteResponseBodyAsync method using my helper method. What I'm unsure about is if I use it in my Rest controller like this.Ok(data), instead of streaming the response, it will just build the response and send it in one chunk. How can I achieve streaming response with content negotiation middleware?
I am using Insomnia for testing an API, but the same happens with Postman.
I want to test a file upload, with the following controller:
public async Task<IActionResult> Post([FromForm]IFormFile File)
If I set the request as a multipart request:
it works.
However, if I set it as a binary file:
I don't know how to get the data. How can it be done?
Also, in the controller method's signature, if I change [FromForm] to [FromBody], I'm not getting data.
Can someone clarify this for me?
As you've noticed already, using binary file option in Postman/Insomnia doesn't work the standard way. There are three different ways to upload file via RESTful API, and you have to choose one.
I've included code snippets that read the uploaded file contents to a string and output it -- try sending a text file, and you should get the contents of the file in the 200 response.
Form-data upload
This is the most popular/well-known upload method formatting the data you send as a set of key/value pairs. You normally need to specify Content-Type to multipart/form-data in the request, and then use [FromForm] attribute in MVC to bind values to variables. Also, you can use the built-in IFormFile class to access the file uploaded.
[HttpPost]
public async Task<IActionResult> PostFormData([FromForm] IFormFile file)
{
using (var sr = new StreamReader(file.OpenReadStream()))
{
var content = await sr.ReadToEndAsync();
return Ok(content);
}
}
Body upload
You can send body in the format that MVC understands, e.g. JSON, and embed the file inside it. Normally, the file contents would be encoded using Base64 or other encoding to prevent character encoding/decoding issues, especially if you are sending images or binary data. E.g.
{
"file": "MTIz"
}
And then specify [FromBody] inside your controller, and use class for model deserialization.
[HttpPost]
public IActionResult PostBody([FromBody] UploadModel uploadModel)
{
var bytes = Convert.FromBase64String(uploadModel.File);
var decodedString = Encoding.UTF8.GetString(bytes);
return Ok(decodedString);
}
// ...
public class UploadModel
{
public string File { get; set; }
}
When using large and non-text files, the JSON request becomes clunky and hard to read though.
Binary file
The key point here is that your file is the whole request. The request doesn't contain any additional info to help MVC to bind values to variables in your code. Therefore, to access the file, you need to read Body in the Request.
[HttpPost]
public async Task<IActionResult> PostBinary()
{
using (var sr = new StreamReader(Request.Body))
{
var body = await sr.ReadToEndAsync();
return Ok(body);
}
}
Note: the example reads Body as string. You may want to use Stream or byte[] in your application to avoid file data encoding issues.
In addition of the above, in case of multipart file conversion to base64String you can refer to the below:
if (File.Length> 0)
{
using (var ms = new MemoryStream())
{
File.CopyTo(ms);
var fileBytes = ms.ToArray();
string s = Convert.ToBase64String(fileBytes);
}
}
Note: I am using this code for .NET CORE 2.1