I'm having some issues with calling an API.
I need to send post data containing 2 things: an ID and an array of strings.
I have tried a lot of things, all resulting in errors or simply not sending data in the right way.
All answers I found, do not handle the fact that I want to send 2 different data types.
My current C# code is like this:
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(SERVER_URI);
var content = ; //This is where I need help
HttpResponseMessage response = client.PostAsync("API URL", content).Result;
The API function is set up like this:
public ActionResult Function(int Id, string[] array)
{
// Contents are not relevant
}
The problem here is that I need to be able to set the names for the values.
I have tried serializing the required data to Json with the following code:
StringContent content = new System.Net.Http.StringContent(TypeSerializer.SerializeToString(new { Id = Id, array = array }));
Of course, Id and Array in this example are filled variables.
This results in a successful call to the server, but the server does not receive the data correctly (both variables stay null)
I've also tried doing it with MultiPartContent, but once again I don't see any way to actually give the right names to the values (Every attempt once again results in the API receiving null values)
Edit:
I got it to send the Id using MultipartFormDataContent instead.
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(new StringContent(Id), "Id");
I still can't seem to get it to send an array to the server though.
Wrap things into an object like this
public class MyPostObject
{
public int Id{get;set;}
public IEnumerable<string> Array{get;set;}
}
then send it as json
using (var client = new HttpClient())
{
var myObject = new MyPostObject(){Id =XXXX, Array = YYYYY};
using (var response = await client.PostAsJsonAsync("api/CONTROLLER/METHOD", myObject))
using (var content = response.Content)
{
var result = content.ReadAsAsync<LoginModelResponse>().Result;
}
}
and receive it as json like this in the server
[HttpPost]
public dynamic METHOD([FromBody] MyPostObject mydata)
{
//Do whatever
}
Related
I have implemented the following method format request body
private async Task<string> FormatRequest(HttpRequest request)
{
request.EnableBuffering();
//Create a new byte[] with the same length as the request stream
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
//Copy the entire request stream into the new buffer
await request.Body.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
//Convert the byte[] into a string using UTF8 encoding
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body.Position = 0;
return bodyAsText;
}
I got the following result
------WebKitFormBoundaryY8OPXY2MlrKMjBRe
Content-Disposition: form-data; name="RoleId"
2
------WebKitFormBoundaryY8OPXY2MlrKMjBRe
Content-Disposition: form-data; name="AuthenticationSettingsId"
3
.....
Expected result
"{\"fields\":[\"RoleId\",\"2\",\"AuthenticationSettingsId\",\"1\",\"recommendation\",\"reviewerId\"],\"size\":100,\"filter\":[{\"id\":\"ApplicationId\",\"operator\":\"and\",\"parent\":\"\",\"nested\":false,\"type\":\"integer\",\"value\":[360]}],\"aggregate\":[],\"sort\":[]}"
Note: Previously we used request.EnableRewind() it was returning the above result and later upgraded to .net core 3.0
Here is a high level of how I handle JSON queries. If you really want to get fancy you can implement all this into an abstract class and inherit direct to your data model.
There are plenty of different ways to get where you want to be, hopefully this helps you get there.
I've put comments in the code, but feel free to ask away if something doesn't make sense.
class SomeHttpJsonUtility
{
//If you want to parse your return data
//directly into a data model
class DataModel{
class ReturnData
{
[JsonPropertyName("fields")]
public Field[] Fields { get; set; }
}
class Field
{
[JsonPropertyName("RoleId")]
public int RoleId { get; set; }
//...you get the idea
}
}
//Some data if your sending a post request
private Dictionary<string, string> postParameters = new Dictionary<string, string>();
//Creates a HTTP Client With Specified Parameters
//You can do this any number of ways depending on the
//source you are querying
private HttpClient GetClient()
{
HttpClient _client = new HttpClient();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.Add(
"UserAgent",
new string[] { "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0" });
_client.DefaultRequestHeaders.Add(
"AcceptLanguage",
new string[] { "en-US" });
_client.DefaultRequestHeaders.Add(
"AcceptEncoding",
new string[] { "gzip", "deflate", "br" });
_client.DefaultRequestHeaders.Add(
"Accept",
new string[] { "*/*" });
_client.DefaultRequestHeaders.Add(
"Connection",
new string[] { "keep-alive" });
return _client;
}
private void GetJson(Uri from_uri)
{
//Get the HttpClient With Proper Request Headers
HttpClient _client =
GetClient();
Task.Run(async () =>
{
//If your data comes from a get request
HttpResponseMessage _httpResponse =
await _client.GetAsync(
requestUri:from_uri);
//Or if your response comes from a post
_httpResponse =
await _client.PostAsync(
requestUri: from_uri,
content: new FormUrlEncodedContent(postParameters)
);
//since your initial post used a stream, we can
//keep going in that direction
//Initilize a memory stream to process the data
using(MemoryStream _ms = new MemoryStream())
{
//Send the http response content
////into the memory stream
await _httpResponse.Content.CopyToAsync(
stream: _ms);
//Goto the start of the memory stream
_ms.Seek(
offset: 0,
loc: SeekOrigin.Begin);
//Option 1:
//Send direct to data model
// This is utilizing the Microsoft Library:
// System.Text.Json.Serialization;
DataModel dataModel =
JsonSerializer.Deserialize<DataModel>(
utf8Json: _ms);
//Option 2:
//Send to a string
using(StreamReader _sr = new StreamReader(_ms))
{
string dataAsSting = _sr.ReadToEnd();
}
}
}).Wait();
}
}
If your query is only a Get request, then it's pretty easy get get the exact headers you need.
Using Firefox hit F12 and goto the web address.
Click the Network Tab, then Headers and view the request data.
You really only need a few of these:
Accept
Accept-Encoding
Accept-Language
Connection
User-Agent
Mozilla has some nice resources regarding the different header objects.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
Host should be taken care of by the HttpClient.
Cookies should be handled by the HttpClient (if you need them)
If you are actually getting the data back as Gzip you'll have to implement a reader, unless the HttpClient you are using will automatically decode it.
And at the end, victory :-)
I think you need to set the content-type on the request when you send it to application/json
Could you try to read this way?
var reader = new System.IO.StreamReader(request.Body);
var body = reader.ReadToEndAsync().Result;
Then you can use Newtonsoft or a similar library on body.
You need to to tokenize / encode your string with some JSON encoder.
Here you have two choices:
the internal (Microsoft) JsonConverter
the Newtonsoft.Json JsonConverter
Karthik, it appears you are sending a multipart request from a webkit browser.
If you would be able to just change it on the client side from multipart to application/json your problem would be fixed.
If this is not possible, you can just use:
private async Task<string> FormatRequest(HttpRequest request)
{
var form = request.Form.ToDictionary(x => x.Key, x => x.Value);
return JsonSerializer.Serialize(form);
}
This could parses your form into a dictionary and returns it as a Json.
(This code is written in dotnet 6, which has System.Text.Json. If you need to stick in .net 3.1, you would need to use a JsonSerializer like Newtonsoft.)
I have written a C# Web API in VS Studio and have created numerous DTO's that get serialized into JSON entities which any client can consume. One of my API calls is to return a PDF file, so after some online research, I set up one of the DTO's is set up in the following format. I read somewhere you can do this, but not 100% sure:
public class MyCustomResult
{
public bool Success;
public DateTime? LastRunDate;
public string ErrorCode;
public string Message;
public ByteArrayContent ReportBody;
}
I do not get any errors when return the object as an IHttpActionResult:
return Ok(result);
I can see on the server that the ByteSize of the report byte[] is approx 700K. However, when I retrieve the object on the client, the JSON entity is approx 400B and no byte content in the ByteContentStream. When I run the query in Postman, I get an empty Header, so it appears that the ByteContentStream can't be serialized by Newtonsoft JSON.
Are there any options I should consider?
Here is a scenario where you'd use ByteArrayContent:
using(var req = new HttpRequestMessage(HttpMethod.Post, new Uri("https://example.com"))
{
req.Content = new ByteArrayContent(...);
using(var resp = await _client.SendAsync(req))
{
var data = await resp.Content.ReadAsAsync<object>();
}
}
What you'd want to do is this:
public class MyCustomResult
{
public bool Success;
public DateTime? LastRunDate;
public string ErrorCode;
public string Message;
public byte[] ReportBody; // <-- change this to byte[]
}
var dataToSend = new MyCustomResult(); // fill this in
using(var req = new HttpRequestMessage(HttpMethod.Post, new Uri("https://example.com"))
{
req.Content = new StringContent(
JsonConvert.SerializeObject(dataToSend, Encoding.UTF8, "application/json"));
using(var resp = await _client.SendAsync(req))
{
var data = await resp.Content.ReadAsAsync<object>();
}
}
(note: this code is not tested)
So what will happen is SerializeObject will convert that byte array into a Base64 string then send it.
The consumer would then have to decode that Base64 string. If it's another Newtonsoft.Json client and the model definitions match, then it will automatically decode it for you.
I understand you are doing an API endpoint. The above examples are to show the use of ByteArrayContent and why it exists in .NET. How you are returning data is correct: return Ok(response); as long as you fix your model.
So to sum it up:
ByteArrayContent is an implementation of HttpContent which is supposed to be used as a response body only. It's not to be used in conjunction with a JSON response.
recently i discovered this amazing cms called Directus, where you can manage your database and Tables with web request and Json.
Everything worked fine creating,updating,reading...till i came to the point where i want to Create (Upload) a Image using WebRequest.
Im basicly reading a image as Base64 and writing the data along with the parameters in the Uri using a simple GET request exactly like described in API.
Regardless what i try and use the Images Never show up in my Files.
Am i doing something wrong or forgetting something?
Or does directus want something else from me?
My first try:
public static async void UploadUserImage() {
var uri = "http://IP/Directus/api/1/files?access_token=SecretApiKey";
var data = GetImageData();
var finalUri = $"{uri}&data={data}";
using (var client = new HttpClient()) {
var responseString = await client.GetStringAsync(finalUri);
Console.Write(responseString);
}
}
My Second try with Json:
public static async void UploadUserImage() {
var uri = "http://IP/Directus/api/1/files?access_token=SecretApiKey";
var data = GetImageData();
var finalUri = $"{uri}&data={data}";
var postModel = new PictureModel {
data = data,
title = "Test",
name = "test"
};
using (var client = new HttpClient())
{
// Serialize our concrete class into a JSON String
var content = JsonConvert.SerializeObject(postModel);
var contenta = new StringContent(content, Encoding.UTF8, "application/json");
var response = await client.PostAsync(finalUri, contenta);
var result = await response.Content.ReadAsByteArrayAsync();
Console.Write(System.Text.Encoding.UTF8.GetString(result));
}
}
The docs is incorrect it's actually a POST request. Thanks for pointing that out.
To upload a new file you need three provide three values:
{
"name": "image.png",
"type": "image/png",
"data": "base64content"
}
The data content has to be in this format data:<mime-type>;base64,<data-content> so it will look something like this: data:image/png;base64,ThisIsABase64Content
We are updating the docs and removing the data:image/png which is unnecessary.
I am developing an website for my company and in there I want to tag skills to specific people. So the programming tags that shows in stackoverflow is a valuable source. SO I want to get the tag db of the stackoverflow.
I found an API for that.
API for the TAGS
So what I am trying to do is read this json string and forloop through the pages to get the tags and save them in a DB.
private static void ReadJson()
{
HttpClient client = new HttpClient();
//DefaultRequestHeader to Json
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Create an instance of HttpResponse & invoke the service asynchronously
HttpResponseMessage response = client.GetAsync("https://api.stackexchange.com/2.2/tags?page=400&pagesize=100&order=desc&sort=popular&site=stackoverflow").Result;
//Http Status code 200
if (response.IsSuccessStatusCode)
{
//Read response content result into string variable
string JSON = response.Content.ReadAsStringAsync().Result;
//Deserialize the string(JSON) object
var jObj = (JObject)JsonConvert.DeserializeObject(JSON);
//access items from anonymous (Json object) type and add to the list
var result = jObj["items"].Select(item => new
{
name = item["name"]
}).ToList();
//output the data || NOTE: **NSERT into database table**
foreach (var item in result)
{
Console.WriteLine(item.name);
}
}
}
So at string JSON = response.Content.ReadAsStringAsync().Result;
method it shows some weired charachters ( triangels and shapes ) because of that process is stopping there.
0\0\0�Z�n�8\f��<�E/S��,-�cYtuI�\f�ߗf\a�g�
What I am doing wrong here?
If there any way to make this happen please contribute your answer.
Thanks.
What you're getting is a compressed response. So instead of reading it as a string, read it as a byte[], decompress it and you'll find your JSON string.
static async void DoStuff()
{
HttpClient client = new HttpClient();
var bytes = await client.GetByteArrayAsync("https://api.stackexchange.com/2.2/tags?page=400&pagesize=100&order=desc&sort=popular&site=stackoverflow");
var decompressedJson = new StreamReader(new GZipStream(new MemoryStream(bytes), CompressionMode.Decompress)).ReadToEnd();
// decompressedJson will now contain '{"items":[{"has_synonyms":false, .....'
// Continue with deserialization of JSON
}
static void Main(string[] args)
{
Task t = new Task(DoStuff);
t.Start();
Console.WriteLine("Doing stuff");
Console.ReadLine();
}
}
You can continue with the deserialization from there. Keep in mind that the API will throw an error when you're sending too many requests.
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;