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();
}
Related
I have an API method in Xamarin that uses .NET Core as a back end and I want to use it to transfer both form-data and JSON format.I have read some examples and I tried to figure a method to make it work.Currently I have this:
public async Task AddUser(User user, MediaFile file)
{
string userRegisterUrl = "http://10.0.2.2:53547/api/PostUser";
var json = JsonConvert.SerializeObject(user);
HttpClientHandler clientHandler = new HttpClientHandler();
clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
var formData = new MultipartFormDataContent();
formData.Add(new StreamContent(file.GetStream()),"file",string.Format("{0}.png",Guid.NewGuid()));
formData.Add(new StringContent(json),"user");
using (HttpClient client = new HttpClient(clientHandler))
{
var response = await client.PostAsync(userRegisterUrl, formData);
string result = await response.Content.ReadAsStringAsync();
if(response.IsSuccessStatusCode)
{
}
}
}
The problem is that no matter what I do I keep getting this error:"415 Unsupported Media Type".Now I know that what it's trying to say by this is that the server will not accept the payload format as it's unsupported but I have no idea how to do it in such way that it will not require me to install external nuget packages to achieve it.I know it is possible and what I have tried is the following:
First I have tried to create an HttpContent that will get the media file as stream and I added some headers to it with ContentDispositionHeaderValue of "form-data" and a MediaTypeHeaderValue of "application/octet-stream".The json format I would serialize it as I did in my method and pass it to the MultipartFormDataContent value but without any success as I got the error mentioned above.
The second try I didn't pass any headers to the media file,the json object remained unchanged and I used a try catch block to pass the content but without any luck as it gave me the same error.
The method above is the final method that I have implemented and this is what I have on the server-side:
[HttpPost]
public async Task<ActionResult<User>> PostUser(User user,IFormFile file)
{
if (string.IsNullOrWhiteSpace(_env.WebRootPath))
{
_env.WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
}
if(user!=null)
{
_context.User.Add(user);
var users = Path.Combine(_env.WebRootPath, "uploads", user.Name);
if (!Directory.Exists(users)) Directory.CreateDirectory(users);
if (file.Length > 0)
{
using (var fileStream = new FileStream(Path.Combine(users, file.FileName), FileMode.Create))
{
await file.CopyToAsync(fileStream);
}
}
}
await _context.SaveChangesAsync();
return CreatedAtAction("GetUser", new { id = user.IdUser }, user);
}
Initially I had both methods separated,one for uploading the image and the other for storing user data but I want to create a folder with the user's name every time a new user is registered and someone suggested I should merge the two of them.How can I achieve the desired result without having to deal with the unsupported format?
Alternatif solution you can use Refit to simplify your request. It can be used in any xamarin forms project with/withour ReactiveUI
The last 2 days, I have been trying to create an image upload system for my website. When I try to save an uploaded image in the "wwwroot" of my Api, everything goes as planned except that I get an empty image in my folder.
At the backend, I receive the filename I send in the frontend but the bytes of the image itself are not there. For some reason the the data of the stream I put in the post call is missing but I do receive the filename in the formfile.
Edit:
To clear things up about my application, I'm working with an Asp.Net Mvc as frontend and Asp.Net Api as backend. I know this isn't how you are supposed to use Asp.Net but this is a school project and I have to do it like this. Normally i would work with Angular or something else but that is not an option for me right now.
So, I'm sending data from the Asp.Net Mvc (frontend) to the Asp.Net Api (backend) and I'm trying to do it by sending it as form data. That means there is no real form that is being submitted.
This is the guide I tried to use:
https://ilclubdellesei.blog/2018/02/14/how-to-upload-images-to-an-asp-net-core-rest-service-with-xamarin-forms/
Backend
ImageController:
[HttpPost("upload")]
public async Task<IActionResult> UploadImage([FromForm(Name = "file")] IFormFile file)
{
if (file.Length == 0)
return BadRequest("Empty file");
string imageName = file.FileName;
using (var fs = new FileStream("wwwroot/" + imageName, FileMode.Create, FileAccess.Write))
{
await file.CopyToAsync(fs);
}
return Ok();
}
Frontend
Method that uploads 1 image as a MemoryStream to the server
private async Task<string> Upload(Stream image, string name, string contentType)
{
_httpClient = _clientFactory.CreateClient("ProjectApi");
HttpContent fileStreamContent = new StreamContent(image);
fileStreamContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") { Name = "file", FileName = name };
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
using (var formData = new MultipartFormDataContent())
{
formData.Add(fileStreamContent);
HttpResponseMessage response = await _httpClient.PostAsync("api/images/upload", formData);
var input = await response.Content.ReadAsStringAsync();
return input;
}
}
The content doesn't seem to be empty:
The filename has been successfully send to the Api but the bytes of the image have not been send:
Structure after uploading some images without checking the size of the formfile (They are empty):
I am not 100% sure but I suppose the reason why you get empty file is that you did not set what type data your api endpoint will consume and maybe the form encryption type & method attributes. My suggestion is that you should update your code to below. ,
[Consumes("multipart/form-data")]
private async Task<string> Upload(Stream image, string name, string contentType)
And in case you forget to add form attributes to your html section, please set attributes as follows <form method="post" enctype="multipart/form-data">. Hope this solves your problem.
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
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#
Hi and thanks for looking!
Background
I am using the Rotativa pdf tool to read a view (html) into a PDF. It works great, but it does not natively offer a way to save the PDF to a file system. Rather, it only returns the file to the user's browser as a result of the action.
Here is what that code looks like:
public ActionResult PrintQuote(FormCollection fc)
{
int revisionId = Int32.Parse(Request.QueryString["RevisionId"]);
var pdf = new ActionAsPdf(
"Quote",
new { revisionId = revisionId })
{
FileName = "Quote--" + revisionId.ToString() + ".pdf",
PageSize = Rotativa.Options.Size.Letter
};
return pdf;
}
This code is calling up another actionresult ("Quote"), converting it's view to a PDF, and then returning the PDF as a file download to the user.
Question
How do I intercept the file stream and save the PDF to my file system. It is perfect that the PDF is sent to the user, but my client also wants the PDF saved to the file system simultaneously.
Any ideas?
Thanks!
Matt
I have the same problem, here's my solution:
You need to basically make an HTTP request to your own URL and save the output as a binary file. Simple, no overload, helper classes, and bloated code.
You'll need this method:
// Returns the results of fetching the requested HTML page.
public static void SaveHttpResponseAsFile(string RequestUrl, string FilePath)
{
try
{
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(RequestUrl);
httpRequest.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)";
httpRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)httpRequest.GetResponse();
}
catch (System.Net.WebException ex)
{
if (ex.Status == WebExceptionStatus.ProtocolError)
response = (HttpWebResponse)ex.Response;
}
using (Stream responseStream = response.GetResponseStream())
{
Stream FinalStream = responseStream;
if (response.ContentEncoding.ToLower().Contains("gzip"))
FinalStream = new GZipStream(FinalStream, CompressionMode.Decompress);
else if (response.ContentEncoding.ToLower().Contains("deflate"))
FinalStream = new DeflateStream(FinalStream, CompressionMode.Decompress);
using (var fileStream = System.IO.File.Create(FilePath))
{
FinalStream.CopyTo(fileStream);
}
response.Close();
FinalStream.Close();
}
}
catch
{ }
}
Then inside your controller, you call it like this:
SaveHttpResponseAsFile("http://localhost:52515/Management/ViewPDFInvoice/" + ID.ToString(), "C:\\temp\\test.pdf");
And voilĂ ! The file is there on your file system and you can double click and open the PDF, or email it to your users, or whatever you need.
return new Rotativa.ActionAsPdf("ConvertIntoPdf")
{
FileName = "Test.pdf", PageSize = Rotativa.Options.Size.Letter
};
Take a look at the MVC pipeline diagram here:
http://www.simple-talk.com/content/file.ashx?file=6068
The method OnResultExecuted() is called after the ActionResult is rendered.
You can override this method or use an ActionFilter to apply and OnResultExecuted interceptor using an attribute.
Edit:
At the end of this forum thread you will find a reply which gives an example of an ActionFilter which reads (and changes) the response stream of an action. You can then copy the stream to a file, in addition to returning it to your client.
I successfully used Aaron's 'SaveHttpResponseAsFile' method, but I had to alter it, as the currently logged in user's credentials weren't applied (and so it was forwarding to MVC4's login url).
public static void SaveHttpResponseAsFile(System.Web.HttpRequestBase requestBase, string requestUrl, string saveFilePath)
{
try
{
*snip*
httpRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
httpRequest.Headers.Add(HttpRequestHeader.Cookie, requestBase.Headers["Cookie"]);
*snip*</pre></code>
Then in your calling Controller method, simply add 'Request' into the SaveHttpResponseAsFile call.
You can also do it using Rotativa, which is actually quite easy.
Using Rotativa;
...
byte[] pdfByteArray = Rotativa.WkhtmltopdfDriver.ConvertHtml( "Rotativa", "-q", stringHtmlResult );
File.WriteAllBytes( outputPath, pdfByteArray );
I'm using this in a winforms app, to generate and save the PDFs from Razor Views we also use in our web apps.
I was able to get Eric Brown - Cal 's solution to work, but I needed a small tweak to prevent an error I was getting about the directory not being found.
(Also, looking at the Rotativa code, it looks like the -q switch is already being passed by default, so that might not be necessary, but I didn't change it.)
var bytes = Rotativa.WkhtmltopdfDriver.ConvertHtml(Server.MapPath(#"/Rotativa"), "-q", html);