Currently my webAPI has the following POST endpoint:
public async Task<ActionResult<string>> AddUserImage([FromRoute] string userId, [FromHeader] bool doNotOverwrite, [FromBody] byte[] content, CancellationToken ct)
My goal is to send an image file to the endpoint. However, I cannot find a correct way to send an octect-stream or ByteArrayContent or some other type over the internet. All attempts end in an HTTP 415.
This is my best attempt to send the image over the internet:
public async Task<bool> AddOrReplaceImage(string id, string endpoint, byte[] imgBinary)
{
if (imgBinary is null) throw new ArgumentNullException(nameof(imgBinary));
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
request.Headers.Add("doNotOverwrite", "false");
request.Content = JsonContent.Create(imgBinary);
// I also tried: request.Content = new ByteArrayContent(imgBinary);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); // Does not seem to change a thing
var apiResult = await new HttpClient().SendAsync(request); // Returns 415
return apiResult.IsSuccessStatusCode;
}
I doubt both the parameters of the endpoint and the way I send the HTTP request. How can I simply receive and send an image over the internet?
Frist Solution :- Which worked in my case.
You can try [FromForm] and IFormFile Like this :-
If controller is annotated with [ApiController] then[FromXxx] is required. For normal view controllers it can be left.
public class PhotoDetails
{
public string id {get;set;}
public string endpoint {get;set;}
public IFormFile photo {get;set;}
}
public async Task<ActionResult<string>> AddUserImage([FromForm] PhotoDetails photoDetails, CancellationToken ct)
I tried this in .net core and it worked but i needed array of files so i used [FromForm] and IFormFile[] and sending from angular.
Second Solution :-
I tried replicate question scenario with question code.
and then changed the implementation and it worked. Please find the below
code
PhotoDetails photopara = new PhotoDetails();
photopara.id = id;
photopara.endpoint = endpoint;
photopara.photo = imgdata;
string json = JsonConvert.SerializeObject(photopara);
var stringContent = new StringContent(json, Encoding.UTF8, "application/json");
using (var client = new HttpClient())
{
var response = await client.PostAsync("http://localhost:57460/WeatherForecast", stringContent);
if (!response.IsSuccessStatusCode)
{
return null;
}
return (await response.Content.ReadAsStreamAsync()).ToString();
}
public class PhotoDetails
{
public string id {get;set;}
public string endpoint {get;set;}
public byte[] photo {get;set;}
}
In this solution, I changed IformFile to byte[] in photodetail class because httpresponsemessage creating problem.
Get Image or byte array in Post Method
Please try this without json serialization
using (var client = new HttpClient())
using (var formData = new MultipartFormDataContent())
{
formData.Add(idContent, "id", "param1");
formData.Add(endpointContent, "endpoint", "file1");
formData.Add(bytesContent, "photo", "file2");
var response = await client.PostAsync("http://localhost:57460/WeatherForecast", formData);
if (!response.IsSuccessStatusCode)
{
return null;
}
return (await response.Content.ReadAsStreamAsync()).ToString();
}
public async Task<ActionResult<int>> AddUserImage([FromForm] PhotoDetails photo, CancellationToken ct)
{
// logic
}
Still Not working then You can try the below link also
Send Byte Array using httpclient
Related
I have an endpoint which is used to create an item. The controller calls the service which creates the item, makes some changes on the db and db returns data based on the procedure. The db returns a json like response, but is not always the same, so I have to adjust on the backend so that I can formalize the response type.
The problem is that create item service is asynchronous and I need to be able to await the response so I can make a new response based on that. How can I await the response and that I get from db client and then return data based on that.
This is my Action and I want to be able to serialize async response from service
[HttpPost]
public IActionResult CreateItem([FromBody] InputModel item)
{
var jsonString = _itemService.CreateItem(item);
ResponseModel? response = JsonSerializer.Deserialize<ResponseModel>(jsonString);
return new ObjectResult(response.Response) { StatusCode = response.StatusCode };
}
The default response model
public class ResponseModel
{
public string Response { get; set; }
public int StatusCode { get; set; }
}
Create Item service, which makes the post request to the client and it has to be async.
Depending on the status code that is coming from the client, I want to be able to set my action status code as well.
public async Task<string> CreateItem(InputModel item)
{
if (item.VersionType != 1)
{
return new { Response = "Incorrect data", StatusCode = 400 }.ToString()!;
}
var json = JsonSerializer.Serialize(item);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, data);
var content = await response.Content.ReadAsStringAsync();
return content;
}
You can make your action method async and then await the method call _itemService.CreateItem for it :
[HttpPost]
public async Task<IActionResult> CreateItem([FromBody] InputModel item)
{
var jsonString = await _itemService.CreateItem(item);
ResponseModel? response = JsonSerializer.Deserialize<ResponseModel>(jsonString);
return new ObjectResult(response.Response) { StatusCode = response.StatusCode };
}
Now your action method would asyncrounously wait for the result from CreateItem and when it returs result, it will continue executing further and send the deserialized response back to client.
I created a .NET 5 REST Api. I can easily upload files from swagger. That is working fine. When debugging, I can see that the byte array is not empty. Here is the Controller method:
[Route("api/[controller]")]
[ApiController]
public class ImageController : ControllerBase
{
// POST api/<ImageController>
[HttpPost]
public void Post([FromForm] UserModel info)
{
var memoryStream = new MemoryStream();
info.Avatar.CopyTo(memoryStream);
var bytes = memoryStream.ToArray();
}
}
This is the UserModel:
public class UserModel
{
[FromForm(Name = "avatar")]
public IFormFile Avatar { get; set; }
[FromForm(Name = "name")]
public string Name { get; set; }
}
I also tried to upload a file programmatically. This is not entirely working. When putting breakpoints in the controller method, I see that the byte array is empty. So the call itself is working but the data is not entering.
Here is the source code of the .NET 5 Console application to upload files.
As explained, this does something useful as it really calls the REST API which I can see by putting breakpoints in the controller method. However, my controller method does not get any data. The byte array is empty.
private static async Task TryUpload()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:5000");
string filePath = "C:\\Users\\daan1982\\Pictures\\RiderStart.png";
var fileStream = File.Create(filePath);
using (var content =
new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)))
{
content.Add(new StreamContent(fileStream), "avatar", "RiderStart.png");
var result = await client.PostAsync("/api/Image", content);
var request = result.RequestMessage;
}
}
}
static async Task Main(string[] args)
{
await TryUpload();
Console.WriteLine("Hello World!");
}
As I named the content "avatar" in the upload and also in the request model, this should work fine. However, it does work but not fine as the byte array is always empty.
What am I doing wrong? And how can I fix this?
File.Create "creates or overwrites a file in the specified path."
You probably want File.OpenRead.
That's how it worked for me.
static async Task Main(string[] args)
{
await TryUpload();
}
private const string Boundary = "EAD567A8E8524B2FAC2E0628ABB6DF6E";
private static readonly HttpClient HttpClient = new()
{
BaseAddress = new Uri("https://localhost:5001/")
};
private static async Task TryUpload()
{
var requestContent = new MultipartFormDataContent(Boundary);
requestContent.Headers.Remove("Content-Type");
requestContent.Headers.TryAddWithoutValidation("Content-Type", $"multipart/form-data; boundary={Boundary}");
var fileContent = await File.ReadAllBytesAsync(#"<path to file\Unbenannt.PNG");
var byteArrayContent = new ByteArrayContent(fileContent);
byteArrayContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png");
requestContent.Add(byteArrayContent, "avatar", "Unbenannt.PNG");
var postResponse = await HttpClient.PostAsync("/api/Image", requestContent);
}
I'm trying to write an overload for HttpClient.PutAsJsonAsync<T> which allows headers to be set on each request. Looking at the source code for that method, it creates an ObjectContent<T> internally, which I tried to emulate below.
However, my target server always receives a null body when binding (using [FromBody] Form form).
If I change the request to be a StringContent and manually serialize the body, the request succeeds! What could be the difference between the two implementations?
Even more strangely, if I call await content.ReadAsStringAsync() before SendAsync then it also works!
My thoughts are it could be either:
The JSON is being serialized differently (But I'm not configuring any Json.NET globals anywhere.) The obvious problem would be different capitalization, but as far as I know, JSON.net is case insensitive when parsing, and my server will be using JSON.net
I'm not awaiting something and the content is being sent before it serialises fully. I can't spot any un-awaited Tasks in my code though, and the entrypoint is a standard async Main()
My environments is:
Client is a simple netcoreapp2.1 exe, running in a microsoft/dotnet:2.1-sdk-alpine docker image.
Server is a .NET framework MVC5 WebApi, with many similar APIs which other clients can call fine
The code below is a trimmed down version of what my client is calling.
public static class UserUpdater
{
public static async Task UpdateAsync(List<string> users)
{
using (var client = new HttpClient())
{
var form = new { Users = users };
var headers = new Dictionary<string, string>
{
["Authorization"] = "Bearer " // Real JWT here
};
var response = await client.PutAsJsonAsync(new Uri("http://localhost/api/app/v1/gateway-users"), form, headers);
if (!response.IsSuccessStatusCode)
{
throw new InvalidOperationException("Response status code does not indicate success: " +
response.StatusCode + "\n\n" +
await response.Content.ReadAsStringAsync());
}
}
}
public static async Task<HttpResponseMessage> PutAsJsonAsync<T>(this HttpClient client, Uri requestUri, T value,
IEnumerable<KeyValuePair<string, string>> headers, CancellationToken cancellationToken = default)
{
var content = new ObjectContent<T>(value, new JsonMediaTypeFormatter(), (MediaTypeHeaderValue)null);
////var content = new StringContent(JsonConvert.SerializeObject(value), Encoding.UTF8, "application/json"); // This works fine
var message = MakeRequestMessage(HttpMethod.Put, requestUri, headers, content);
return await client.SendAsync(message, cancellationToken);
}
private static HttpRequestMessage MakeRequestMessage(
[NotNull] HttpMethod method,
[NotNull] Uri requestUri,
[CanBeNull] IEnumerable<KeyValuePair<string, string>> headers,
[CanBeNull] HttpContent content = null)
{
var message = new HttpRequestMessage(method, requestUri);
if (headers != null)
{
foreach (var header in headers)
{
message.Headers.Add(header.Key, header.Value);
}
}
if (content != null)
{
message.Content = content;
}
return message;
}
If it helps, this is the server side (MVC5 WebApi):
[Route("")]
[ValidateModel]
public IHttpActionResult Put([Required] [FromBody] GatewayUserCollectionForm form)
{
Ensure.NotNull(form, nameof(form)); // This throws since form is null
this.manager.UpdateGatewayUsers(form.Users.ToList());
return this.Ok();
}
public class GatewayUserCollectionForm
{
[Required]
public List<string> Users { get; set; }
}
I'd like to be able to read a post variable from my controller method.
I currently have the below code:
[HttpPost]
public IHttpActionResult BuildPartitions([FromBody]string PartitionBuildDate)
{
}
I'm using the below code to test:
using (HttpClient httpClient = new HttpClient())
{
var values = new Dictionary<string, string>
{
{ "PartitionBuildDate", "24-May-2017" }
};
var content = new FormUrlEncodedContent(values);
var response = httpClient.PostAsync("http://localhost:55974/api/Controller/BuildPartitions", content);
var responseString = response.Result.Content;
}
Looking online, this looks correct for both sending and receiving a post variable in C#, however the PartitionBuildDate variable is always null.
Try adding the content-type header. I have used Newtonsoft JSON.NET for JSON conversion:
string postBody = JsonConvert.SerializeObject(yourDictionary);
var response = client.PostAsync(url, new StringContent(postBody, Encoding.UTF8, "application/json"));
var responseString = response.Result.Content;
Also, on your web API side, try wrapping your POST parameters inside a class:
public class PostParameters
{
public string PartitionBuildDate {get;set;}
}
[HttpPost]
public IHttpActionResult BuildPartitions([FromBody]PostParameters parameters)
{
//you can access parameters.PartitionBuildDate
}
I can't for the life of me get this to work. I keep getting 404.
Here's the WebApi2 code:
[HttpPost]
public IHttpActionResult Post(string testString)
{
if(!string.IsNullOrEmpty(testString))
{
return Ok(testString);
}
else
{
return BadRequest();
}
}
Here's the WebClient code:
public async Task PostingToWebServiceShouldWork()
{
var apiEndPoint = new Uri(String.Format("{0}/Paging", ConfigurationManager.AppSettings["ApiEndpoint"].ToString()));
var apiRoot = new Uri(apiEndPoint.GetLeftPart(UriPartial.Authority));
var apiCall = apiEndPoint.PathAndQuery.Substring(1, apiEndPoint.PathAndQuery.Length - 1);
using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
client.BaseAddress = apiRoot;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpContent content = new StringContent("testSTring");
HttpResponseMessage response = await client.PostAsync(apiCall, content);
if(response.IsSuccessStatusCode)
{
}
}
}
I just want to post a simple string to the web service. This should be dead simple, and it's giving me a migraine, lol. I've tried everything I can think of, and I have to be missing some tiny detail...
Thanks!
Because your API endpoint is simply a string instead of an object, WebAPI is looking for that string as a query string parameter. You have two options:
Use the [FromBody] attribute in your action's definition
public IHttpActionResult Post([FromBody] string testString)
Send the string on the URL instead of in the body (works, but if you're going for security over HTTPS this exposes what you were posting)
See http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api for a deeper explanation and examples