Howto upload MultipartFormDataContent which contains a stream using c# in webapi - c#

Given is the following webapi HttpPost method:
using Microsoft.AspNetCore.Mvc;
/// <summary>
/// Eviget controller used for uploading artefacts
/// Either from teamcity or in case of the misc files
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class UploadDemoController : ControllerBase
{
[HttpPost]
public IActionResult Upload([FromForm] UploadContent input)
{
return Ok("upload ok");
}
}
public class UploadContent
{
public string Id { get; set; }
public string Name { get; set; }
public Stream filecontent { get; set; }
}
The following code is used to upload a MultipartFormDataContent
using System.Net.Http.Headers;
HttpClient http = new HttpClient();
MultipartFormDataContent form = new MultipartFormDataContent();
StringContent IdStringContent = new StringContent(Guid.NewGuid().ToString());
form.Add(IdStringContent, "Id");
StringContent NameStringContent = new StringContent($#"foobar");
form.Add(NameStringContent, "Name");
StreamContent TestStream = new StreamContent(GenerateStreamFromString("test content of my stream"));
TestStream.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "filecontent", FileName = "test.txt" };
TestStream.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
form.Add(TestStream, "filecontent");
//set http heder to multipart/form-data
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("multipart/form-data"));
try
{
System.Console.WriteLine("start");
var response = http.PostAsync("http://localhost:5270/api/UploadDemo/Upload", form).Result;
response.EnsureSuccessStatusCode();
}
catch (System.Exception ex)
{
System.Console.WriteLine(ex.Message);
}
By default, the response is 400 (Bad Request).
With the following controller option, the request is sent to the rest server. This option just says the rest server should ignore null values.
builder.Services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true)
The stream is always null. (Note: The other values are properly set)
But the stream is actually part of the multipart form data(fiddler output)
What do i need to do that the Stream is properly mapped in this case?

Instead of using the data type Stream, use IFormFile.
So then you can access the properties and the file as follows:
var file = input.filecontent // This is the IFormFile file
To persist/save the file to disk you can do the following:
using (var stream = new FileStream(path, FileMode.Create))
{
await file.File.CopyToAsync(stream);
}

This is a sample based on the information from ibarcia.
I've created two webapi controller which allow to read files uploaded as part of a MultipartFormDataContent.
One method defines a parameter which is attributed with [FromForm] and which then contains a property of type IFormFile.
In the second implementation, no parameter is specified and the file can be read
via Request.ReadFormAsync and then accessing the File
using Microsoft.AspNetCore.Mvc;
/// <summary>
/// Eviget controller used for uploading artefacts
/// Either from teamcity or in case of the misc files
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class UploadDemoController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> UploadWithoutParameter()
{
IFormCollection formCollection = await this.Request.ReadFormAsync();
//contains id and name
foreach (var form in formCollection)
{
System.Console.WriteLine(form.Key);
System.Console.WriteLine(form.Value);
}
foreach (var file in formCollection.Files)
{
System.Console.WriteLine(file.FileName);
System.Console.WriteLine(file.Length);
}
return Ok("upload ok");
}
[HttpPost]
public async Task<IActionResult> Upload([FromForm] UploadContent input)
{
System.Console.WriteLine(input.Id);
System.Console.WriteLine(input.Name);
using (var stream = new FileStream(#"c:/temp/neutesttext.txt", FileMode.Create))
{
await input.filecontent.CopyToAsync(stream);
}
return Ok("upload ok");
}
public class UploadContent
{
public string Id { get; set; }
public string Name { get; set; }
// public Stream filecontent { get; set; }
public IFormFile filecontent { get; set; }
}
}

Related

C# .NET Core 3.1 Web API Post parameter is Null

I am trying to make a post request from WPF to Web API using the following code but the request parameter is always null.
Request Model
public class Document
{
public string FileName { get; set; }
public byte[] Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
WPF Client
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"C:\Users\john.doe\Downloads\I Love Coding.pdf.pdf")
}
}
};
using (var http = new HttpClient())
{
var encodedJson = JsonConvert.SerializeObject(obj);
var conent = new StringContent(encodedJson, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.PostAsync("https://my-app.com/api/upload", conent);
response.EnsureSuccessStatusCode();
}
Web API
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromBody] Request request)
{
// request is always null when app is running in production
// https://my-app.com/api/upload
//request is not null when running on https://localhost:8080/api/upload
}
}
Please what am I missing in the above implementation?
The request parameter is not null on localhost but always null in production.
Please what am I missing in the above implementation? The request
parameter is not null on localhost but always null in production.
Well, not sure how are getting data on local server becuse, you are sending MultipartFormData means your POCO object and file buffer. As you may know we can send json object in FromBody but not the files as json. Thus, I am not sure how it working in local and getting null data is logical in IIS Or Azure.
what am I missing in the above implementation?
As explained above, for sending both POCO object and Files as byte or steam we need to use FromForm and beside that, we need to bind our request object as MultipartFormDataContent to resolve your null data on your UploadDocumentsAsync API action.
Required Change For Solution:
WPF:
In your WPF http request please update your request code snippet as following:
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"YourFilePath")
}
}
};
var httpClient = new HttpClient
{
BaseAddress = new("https://YourServerURL")
};
var formContent = new MultipartFormDataContent();
formContent.Add(new StringContent(obj.Uploader), "Uploader");
formContent.Add(new StringContent(obj.Documents[0].FileName), "Documents[0].FileName");
formContent.Add(new StreamContent(new MemoryStream(obj.Documents[0].Buffer)), "Documents[0].Buffer", obj.Documents[0].FileName);
var response = await httpClient.PostAsync("/api/upload", formContent);
if (response.IsSuccessStatusCode)
{
var responseFromAzureIIS = await response.Content.ReadAsStringAsync();
}
Note: Class in WPF side would remain same as before. No changes required.
Asp.net Core Web API:
In asp.net core web API side you should use [FromForm] instead of [FromBody]
So your controller Action would as following:
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromForm] Request file)
{
if (file.Documents[0].Buffer == null)
{
return Ok("Null File");
}
return Ok("File Received");
}
}
Note: For remote debugging I have checked the logs and for double check I have used a simple conditionals whether file.Documents[0].Buffer == null. I have tested both in local, IIS and Azure and working accordingly.
Update POCO Class in API Project:
For buffer you have used byte for your WPF project but for Web API project update that to IFormFile instead of byte. It should be as following:
public class Document
{
public string FileName { get; set; }
public IFormFile Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
Output:
If you would like to know more details on it you could check our official document here

Service Stack - Trying to create a POST function and read the JSON data

I'm just trying to create a simple POST function that let's me POST a JSON. I've tried to copy examples but I'm not sure what I'm doing differently. Any help would be appreciated, I feel like it's something simple that I'm missing.
What I'm trying to post:
POST Address: http://localhost:49653/save/file
Headers:
Content-Type: application/json
Raw Body:
{
uuid: "someUuid",
fileName: "test",
dateTime: "dateee",
json: "some json"
}
namespace SomeNamespace.Model
{
[Route("/save/file", "POST")]
public class SaveFileRequest
{
public Stream RequestStream { get; set; }
}
public class SaveFileResponse
{
public bool Success { get; set; }
}
}
namespace SomeNamespace.ServiceInterface
{
[EnableCors(allowedMethods:"POST")]
public class SaveFileService : Service
{
public object Any(SaveFileRequest request)
{
var response = new SaveFileResponse { Success = false };
string savedataJson;
using (var reader = new StreamReader(Request.InputStream))
{
savedataJson = reader.ReadToEnd();
}
try
{
Console.WriteLine(savedataJson); // When I debug, the contents are ""
}
catch(Exception ex) {...}
}
}
}
}
Your SaveFileRequest Request DTO needs to implement IRequiresRequestStream.
Here are the docs for reading directly from the request stream:
Reading directly from the Request Stream
Instead of registering a custom binder you can skip the serialization of the request DTO, you can add the IRequiresRequestStream interface to directly retrieve the stream without populating the request DTO.
//Request DTO
public class RawBytes : IRequiresRequestStream
{
/// <summary>
/// The raw Http Request Input Stream
/// </summary>
Stream RequestStream { get; set; }
}
Which tells ServiceStack to skip trying to deserialize the request so you can read in the raw HTTP Request body yourself, e.g:
public object Post(RawBytes request)
{
byte[] bytes = request.RequestStream.ReadFully();
string text = bytes.FromUtf8Bytes(); //if text was sent
}

HttpClient.PostAsync return System.IO.Stream as property

I have some legacy code that I'm trying to work with and need to return an existing object that contains a Stream from an ApiController
public class LegacyObject
{
public bool Result { get; set; }
public Stream Stream { get; set; }
}
API Code
public class BindJson : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
string rawRequest;
using (var stream = new StreamReader(actionContext.Request.Content.ReadAsStreamAsync().Result))
{
stream.BaseStream.Position = 0;
rawRequest = stream.ReadToEnd();
}
rawRequest = rawRequest.ToString();
var obj = JsonConvert.DeserializeObject<LegacyParameters>(rawRequest.ToString());
actionContext.ActionArguments["parameter"] = obj;
}
}
public class ReportsController : ApiController
{
[BindJson]
[HttpPost]
public LegacyObject ReturnReport([FromBody]LegacyParameters parameter)
{
LegacyObject r = LegacyClass.GetReportStream(parameters);
return r; //Object properties are correctly set and no errors at this point
}
}
My call to the Api is
using (var httpClient = new System.Net.Http.HttpClient())
{
httpClient.BaseAddress = new Uri("http://myserver/");
string contents = JsonConvert.SerializeObject(paramList);
var response = httpClient.PostAsync("/api/ReturnReport", new StringContent(contents, Encoding.UTF8, "application/json")).Result;
}
I get a 500 Internal Server Error on the PostAsync when my LegacyObject.Stream has content. It works when the stream content is null. I'm working locally on my development PC and web server for the API is IIS Express.
Any advice would be appreciated.
So in order to get more detail on the 500 Internal Server Error, I added this to my projects WebApiConfig Register
config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
With this class
public class UnhandledExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
var log = context.Exception.ToString();
//Write the exception to your logs
}
}
The log variable now gives me the detail I need to debug
FYI - The error was a read timeout on my Stream

Deserialize JSON to custom class returns null

I have an MVC application with two micro services. In the first I call PostAsync() method to send my custom object
public class Logo {
public Guid Id { get; set; }
public IEnumerable<byte> Content { get; set; }
}
to another service
public async Task PostLogo(Logo logo)
{
using (var client = new HttpClient())
{
await client.PostAsync(_url, new StringContent(JsonConvert.SerializeObject(logo), Encoding.UTF8, "application/json"));
}
}
In the second micro service I try to deserialize using
[HttpPost, Route("logo")]
public Task<FileUploadResultModel> SaveAsync([FromBody]Logo model)
{
return _fileService.SaveAsync(null);
}
but it gets null instead of input object
.
Can anyone explain how can I send/process custom object using Post request, please?
Update action to follow preferred syntax structure.
[HttpPost, Route("logo")]
public async Task<IHttpActionResult> SaveAsync([FromBody]Logo model) {
if(ModelState.IsValid) {
FileUploadResultModel result = await _fileService.SaveAsync(model);
return Ok(result);
}
return BadRequest();
}
Based on comments it is possible that the byte array is too large so the model binder is unable to properly bind the model. Check the web.config and see what is the max size of data that can be sent. You may need to increase it. The max is 2GB but I would advise against such a high value.
Check this answer: Maximum request length exceeded
If you are able to get the image smaller you should base64 Url encode that image to a string and sent that as the content.
public class Logo {
public Guid Id { get; set; }
public string Content { get; set; }
}
And when you get the model convert the content back to a byte array
[HttpPost, Route("logo")]
public async Task<IHttpActionResult> SaveAsync([FromBody]Logo model) {
if(ModelState.IsValid) {
byte[] content = Convert.FromBase64String(model.Content);
var id = model.Id;
//...
return Ok(result);
}
return BadRequest();
}

Call MVC web API controller method from client

I am trying to consume/call an MVC Web API controller method, which will be used to upload a file. I am struggling to call it from my MVC controller.
Here's my code for the API Controller
public class ImportController : ApiController
{
[HttpPost]
public bool PutImportFile(byte[] fileToBeImported, string nameOfTheFileToBeImported)
{
// I am doing file saving stuff here
}
}
I have tested the file saving part by changing the method to HttpGet and its working when I called it directly from the browser. I removed the parameters for that.
However, I am not able to figure out how to call it from a client.
I have tried below.
public class ImportFileModel
{
public byte[] FileToBeImported { get; set; }
public string NameOfTheFileToBeImported { get; set; }
}
The below code will accept a file from the browser uploaded by user and post it to the API controller to save the file.
[HttpPost]
public async Task<JsonResult> Upload()
{
byte[] file;
string fileName = string.Empty;
if (Request.Files.Count > 0)
{
try
{
fileName = Request.Files[0].FileName;
using (MemoryStream ms = new MemoryStream())
{
Request.Files[0].InputStream.CopyTo(ms);
file = ms.ToArray();
}
//To do: get url from configuration
string url = "http://localhost:(port)/api/Import/PutImportFile";
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(url);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/bson"));
ImportFileModel request = new ImportFileModel
{
FileToBeImported = file,
NameOfTheFileToBeImported = fileName
};
MediaTypeFormatter bsonFormatter = new BsonMediaTypeFormatter();
var result = await client.PostAsync(url, request, bsonFormatter);
HttpResponseMessage response = result.EnsureSuccessStatusCode();
}
}
catch (Exception ex)
{
// exception handling here
}
}
return Json(true, JsonRequestBehavior.AllowGet);
}
It ends up in an exception at the last line.
HttpResponseMessage response = result.EnsureSuccessStatusCode();
Throwing 404 not found error.
I have also tried the same from a console application using HttpWebRequest. It also throws the same error.
Your Web API method PutImportFile is setup to receive two values, not a single model; hence, your HttpClient call is not recognized (no matching route found). Change your Web API method to receive a model:
public class ImportController : ApiController
{
[HttpPost]
public bool PutImportFile(ImportFileModel fileInfo)
{
//Your code to save the file...
}
}

Categories