There are many topic but nothing is helpful to me.
I will show you what have I done, and you can tell me what is wrong.
In api I want to desirialize complex object that is send from client:
[HttpPost]
public async Task Post([FromBody]string item)
{
WorkItem data = JsonConvert.DeserializeObject<WorkItem>(item);
//...
}
How I post complex object:
public async Task AddWorkItem()
{
using (var client = HttpClientFactory.GetClient())
{
var item = new WorkItem()
{
Description = DateTime.Now.ToString("h:mm:ss tt"),
Id = 11
};
var inner1 = new ItemUsage()
{
Id = 45,
UsedFor = "something"
};
Collection<ItemUsage> Usage = new Collection<ItemUsage>();
Usage.Add(inner1);
item.Usage = Usage;
string output = JsonConvert.SerializeObject(item);
var response = await client.PostAsync("api/WorkItem", new StringContent(output));
if (!response.IsSuccessStatusCode)
{
throw new Exception((int)response.StatusCode + "-" + response.StatusCode.ToString());
}
}
I get error : 415-UnsupportedMediaType .
Maybe my aproach is not best?
I need some clean way to post any complex object and my idea is to do this with Json string.
What do you think?
you are missing one of the key features of WebAPI called model binding or parameter binding. Before the Controller's Action is invoked the incoming data is bound to the Actions parameters. In other words the WebAPI pipeline will handle most deserialization for you. In your example your parameter type is a string, but what you are sending to the server cannot be converted into a string. Supposing the rest of your plumbing is fine, changing the parameter type to WorkItem should do the trick.
public async Task Post([FromBody]WorkItem item)
If you must accept a string parameter then you must ensure that the body of the request can be converted to a string or write a custom model binder. See here for more info on raw request body handling
Look like issue is with the datetime serialization. Please see the below answer for help
JsonConvert.SerializeObject vs JsonSerializer.Serialize
Ok it is now clear to me. I will add whole code so that someone else can use it.
[HttpPost]
public async Task Post([FromBody]WorkItem item)
{
//No need to deserialize, you can use item here
}
Call api:
public async Task AddWorkItem()
{
using (var client = HttpClientFactory.GetClient())
{
//Complex object example::
///////////////////
var item = new WorkItem()
{
Description = DateTime.Now.ToString("h:mm:ss tt"),
Id = 11
};
var inner1 = new ItemUsage()
{
Id = 45,
UsedFor = "something"
};
Collection<ItemUsage> Usage = new Collection<ItemUsage>();
Usage.Add(inner1);
item.Usage = Usage;
////////////////////////////
var json = JsonConvert.SerializeObject(item);
var content = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var response = await client.PostAsync("api/WorkItem", content);
if (!response.IsSuccessStatusCode)
{
throw new Exception((int)response.StatusCode + "-" + response.StatusCode.ToString());
}
}
}
Create client
public static class HttpClientFactory
{
public static HttpClient GetClient()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:1431");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
}
Related
I am trying to call one of my APIs POST method from my client code using httpclient.
The problem is that I want to pass array of integer(ids) as request body as per my destination APIs POST method, but I am not able to call this method using my source code.
Could anyone help me ?
Destination code:
public IActionResult GetByIds([FromBody] int[] ids)
{
try
{
var collterals = _collateralRepository.GetCollateralsByIds(ids);
return Ok(collterals);
}
catch (Exception)
{
throw;
}
}
Source Code:
int[] ids = outputsummary.Select(x => x.Collateral_Id).ToArray();
var data = new StringContent(JsonConvert.SerializeObject(ids), Encoding.UTF8, "application/json");
var collateralClient = _httpClientFactory.CreateClient("CollateralClient");
var response = await collateralClient.PostAsync("Collateral/GetByIds", data);
It seems that WEB API is not able to get the data correctly since it does not deal with multiple posted content values. The best way for your case would be to create a Model class that will hold your int[] and then you can POST it to your API.
public class MyIDModel
{
public int[] ids{ get; set; }
}
So your source code will be:
int[] ids = outputsummary.Select(x => x.Collateral_Id).ToArray();
MyIDModel mymodel=new MyIDModel();
mymodel.ids=ids;
var data = new StringContent(JsonConvert.SerializeObject(mymodel), Encoding.UTF8, "application/json");
var collateralClient = _httpClientFactory.CreateClient("CollateralClient");
var response = await collateralClient.PostAsync("Collateral/GetByIds", data);
Your destination code will remain the same.
I don't know why you can not send data without wraping using json, I tested your code using common httpclient and it is working properly at my mashine. The problem coud be in your named client _httpClientFactory.CreateClient("CollateralClient");
But there is another way if you want to sent an array without wraping it in a viewmodel, you can try this code that is using application/x-www-form-urlencoded content
int[i] ids=..your code
int counter = 0;
var values = new List<KeyValuePair<string, string>>();
foreach (int i in ids)
{
values.Add(new KeyValuePair<string, string>("ids[" + counter.ToString() + "]", i.ToString()));
counter++;
}
var content = new FormUrlEncodedContent(values);
var response = await collateralClient.PostAsync("Collateral/GetByIds", content);
and remove [frombody]
public IActionResult GetByIds(int[] ids)
So I am having this weird problem with deserializing a response from my BackEnd, The request works fine and the BackEnd succesfully responds with a result.
This is the error I get:
'Error converting value "{"Succes":true,"AuthKey":"$2a$13..."}" to type 'FrontEnd.LoginUserResponse'. Path '', line 1, position 96.'
The code I am using to make the HTTP call and deserialize the string:
public async Task<bool> loginUser(LoginUserData login)
{
HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(login), Encoding.UTF8);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage responseMessage = await httpClient.PostAsync("http://ip/webBackEnd/api/user/login", httpContent);
string response2 = responseMessage.Content.ReadAsStringAsync().Result;
LoginUserResponse response = JsonConvert.DeserializeObject<LoginUserResponse>(response2);
if (response.Succes)
{
return true;
}
else { return false; }
}
I tried making a response2 to check the value and I have noticed it does something weird with 3 backslashes. This might be the reason why this is occuring.
This is response2 that visual studio shows when I click the magnifying glass:
"{\"Succes\":true,\"AuthKey\":\"$2a$11$tQCw4zGGd2J2fXAxAN68Ruu3xheTuMKq4EHbeLtc9DAa2rgzJe8bS\"}"
When I hover on visual studio:
https://imgur.com/a/jUyLz6d
This is the Class that it is converting to
public class LoginUserResponse
{
[JsonProperty("succes")]
public bool succes { get; set; }
[JsonProperty("authkey")]
public string authkey { get; set; }
}
The Backend code:
[HttpPost]
[Route("login")]
public string Login([FromBody]LogInData logInData)
{
IReadUser.LogInRequest request = new IReadUser.LogInRequest(logInData);
IReadUser.LogInResponse backResponse = readUser.logIn(request);
LogInResponse response = new LogInResponse();
response.succes = backResponse.Succes;
response.authkey = backResponse.AuthKey;
return JsonConvert.SerializeObject(response);
}
EDIT // SOLUTION
Ok so, the front-end was fine, it was my backend code sending a double serialised string. I used
return JsonConvert.SerializeObject(response);
When I also could have used
return response;
So if you every get an error like this, it's probably the backend doubling up on the serialization.
Thanks for all the help!
Couple of things:
1. you should await responseMessage.Content.ReadAsStringAsync():
public async Task<bool> loginUser(LoginUserData login)
{
var httpContent = new StringContent(JsonConvert.SerializeObject(login), Encoding.UTF8);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var responseMessage = await httpClient.PostAsync("http://ip/webBackEnd/api/user/login", httpContent);
var response2 = await responseMessage.Content.ReadAsStringAsync();
var response = JsonConvert.DeserializeObject<LoginUserResponse>(response2);
return response.Succes
}
And 2. Based on your image it looks like the response from your backing service is being serialized twice.
1:
"{\"Succes\":true,\"AuthKey\":\"$2a$11$tQCw4zGGd2J2fXAxAN68Ruu3xheTuMKq4EHbeLtc9DAa2rgzJe8bS\"}"
2:
"\"{\\\"Succes\\\":true,\\\"AuthKey\\\":\\\"$2a$11$tQCw4zGGd2J2fXAxAN68Ruu3xheTuMKq4EHbeLtc9DAa2rgzJe8bS\\\"}\""
now to deserialize you have to do it twice
var s = JsonConvert.DeserializeObject<string>(response2);
var response = JsonConvert.DeserializeObject<LoginUserResponse>(s);
Probably best to fix the service if that's actually what is happening
When testing my web API with Postman my API get executes fine!
When it comes to running the code with HttpClient in my client application the code executes without error but without the expected result on the server.
What could be happening?
From my client application:
private string GetResponseFromURI(Uri u)
{
var response = "";
HttpResponseMessage result;
using (var client = new HttpClient())
{
Task task = Task.Run(async () =>
{
result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
});
task.Wait();
}
return response;
}
Here is the API controller:
[Route("api/[controller]")]
public class CartsController : Controller
{
private readonly ICartRepository _cartRepo;
public CartsController(ICartRepository cartRepo)
{
_cartRepo = cartRepo;
}
[HttpGet]
public string GetTodays()
{
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Add")]
public string GetIncrement()
{
var cart = new CountedCarts();
_cartRepo.Add(cart);
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Remove")]
public string GetDecrement()
{
_cartRepo.RemoveLast();
return _cartRepo.GetTodaysCarts();
}
}
Note these API calls work as expected when called from Postman.
You shouldn't use await with client.GetAsync, It's managed by .Net platform, because you can only send one request at the time.
just use it like this
var response = client.GetAsync("URL").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
var dataObjects = response.Content.ReadAsAsync<object>().Result;
}
else
{
var result = $"{(int)response.StatusCode} ({response.ReasonPhrase})";
// logger.WriteEntry(result, EventLogEntryType.Error, 40);
}
You are doing fire-and-forget approach. In your case, you need to wait for the result.
For example,
static async Task<string> GetResponseFromURI(Uri u)
{
var response = "";
using (var client = new HttpClient())
{
HttpResponseMessage result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
}
return response;
}
static void Main(string[] args)
{
var t = Task.Run(() => GetResponseFromURI(new Uri("http://www.google.com")));
t.Wait();
Console.WriteLine(t.Result);
Console.ReadLine();
}
Simple sample used to get page data.
public string GetPage(string url)
{
HttpResponseMessage response = client.GetAsync(url).Result;
if (response.IsSuccessStatusCode)
{
string page = response.Content.ReadAsStringAsync().Result;
return "Successfully load page";
}
else
{
return "Invalid Page url requested";
}
}
I've had a problem with chace control when using httpclient.
HttpBaseProtocalFilter^ filter = ref new HttpBaseProtocolFilter();
filter->CacheControl->ReadBehavior = Windows::Web::Http::Filters::HttpCacheReadBehavior::MostRecent;
HttpClient^ httpClient = ref new HttpClient(filter);
I'm not really sure what the expected results are or what results your getting at all so this is really just a guessing game right now.
When I POST something using HttpClient I found adding headers by hand seemed to work more often than using default headers.
auto httpClient = ref new HttpClient();
Windows::Web::Http::Headers::HttpMediaTypeHeaderValue^ type = ref new Windows::Web::http::Headers::HttpMediaTypeHeaderValue("application/json");
content->Headers->ContentType = type;
If I don't do these 2 things I found, for me anyways, that half the time my web requests were either not actually being sent or the headers were all messed up and the other half of the time it worked perfectly.
I just read a comment where you said it would only fire once, that makes me think it is the cachecontrol. I think what happens is something (Windows?) sees 2 requests being sent that are the exact same, so to speed things up it just assumes the same answer and never actually sends the request a 2nd time
I am using HttpClient PCL Class library. But When First Time I Get the JSON result It return correct data. After that HttpClient returns the same JSON result again and again for one URL till I close the application and start it again. My code looks like that
public class HttpService : IHttpService
{
public async Task<TResponseType> GetAsync<TResponseType>(string method, string parameters = null) where TResponseType : class
{
var uri = new Uri(string.Format(Constants.ServerUrl + method + parameters));
using (var client=new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
"c2Rzb2w6c2Rzb2w5OQ==");
var response = await client.GetAsync(uri);
var result = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<TResponseType>(result);
}
}
}
As stated in an answer for a different question, the solution is to set the IfModifiedSince property to prevent the default caching behaviour like this:
httpClient.DefaultRequestHeaders.IfModifiedSince = DateTime.Now;
You could also check on MSDN for more information.
I have a custom complex type that I want to work with using Web API.
public class Widget
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
And here is my web API controller method. I want to post this object like so:
public class TestController : ApiController
{
// POST /api/test
public HttpResponseMessage<Widget> Post(Widget widget)
{
widget.ID = 1; // hardcoded for now. TODO: Save to db and return newly created ID
var response = new HttpResponseMessage<Widget>(widget, HttpStatusCode.Created);
response.Headers.Location = new Uri(Request.RequestUri, "/api/test/" + widget.ID.ToString());
return response;
}
}
And now I'd like to use System.Net.HttpClient to make the call to the method. However, I'm unsure of what type of object to pass into the PostAsync method, and how to construct it. Here is some sample client code.
var client = new HttpClient();
HttpContent content = new StringContent("???"); // how do I construct the Widget to post?
client.PostAsync("http://localhost:44268/api/test", content).ContinueWith(
(postTask) =>
{
postTask.Result.EnsureSuccessStatusCode();
});
How do I create the HttpContent object in a way that web API will understand it?
The generic HttpRequestMessage<T> has been removed. This :
new HttpRequestMessage<Widget>(widget)
will no longer work.
Instead, from this post, the ASP.NET team has included some new calls to support this functionality:
HttpClient.PostAsJsonAsync<T>(T value) sends “application/json”
HttpClient.PostAsXmlAsync<T>(T value) sends “application/xml”
So, the new code (from dunston) becomes:
Widget widget = new Widget()
widget.Name = "test"
widget.Price = 1;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:44268");
client.PostAsJsonAsync("api/test", widget)
.ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode() );
You should use the SendAsync method instead, this is a generic method, that serializes the input to the service
Widget widget = new Widget()
widget.Name = "test"
widget.Price = 1;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:44268/api/test");
client.SendAsync(new HttpRequestMessage<Widget>(widget))
.ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode() );
If you don't want to create the concrete class, you can make it with the FormUrlEncodedContent class
var client = new HttpClient();
// This is the postdata
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("Name", "test"));
postData.Add(new KeyValuePair<string, string>("Price ", "100"));
HttpContent content = new FormUrlEncodedContent(postData);
client.PostAsync("http://localhost:44268/api/test", content).ContinueWith(
(postTask) =>
{
postTask.Result.EnsureSuccessStatusCode();
});
Note: you need to make your id to a nullable int (int?)
Note that if you are using a Portable Class Library, HttpClient will not have PostAsJsonAsync method.
To post a content as JSON using a Portable Class Library, you will have to do this:
HttpClient client = new HttpClient();
HttpContent contentPost = new StringContent(argsAsJson, Encoding.UTF8,
"application/json");
await client.PostAsync(new Uri(wsUrl), contentPost).ContinueWith(
(postTask) => postTask.Result.EnsureSuccessStatusCode());
If you want the types of convenience methods mentioned in other answers but need portability (or even if you don't), you might want to check out Flurl [disclosure: I'm the author]. It (thinly) wraps HttpClient and Json.NET and adds some fluent sugar and other goodies, including some baked-in testing helpers.
Post as JSON:
var resp = await "http://localhost:44268/api/test".PostJsonAsync(widget);
or URL-encoded:
var resp = await "http://localhost:44268/api/test".PostUrlEncodedAsync(widget);
Both examples above return an HttpResponseMessage, but Flurl includes extension methods for returning other things if you just want to cut to the chase:
T poco = await url.PostJsonAsync(data).ReceiveJson<T>();
dynamic d = await url.PostUrlEncodedAsync(data).ReceiveJson();
string s = await url.PostUrlEncodedAsync(data).ReceiveString();
Flurl is available on NuGet:
PM> Install-Package Flurl.Http
After investigating lots of alternatives, I have come across another approach, suitable for the API 2.0 version.
(VB.NET is my favorite, sooo...)
Public Async Function APIPut_Response(ID as Integer, MyWidget as Widget) as Task(Of HttpResponseMessage)
Dim DesiredContent as HttpContent = New StringContent(JsonConvert.SerializeObject(MyWidget))
Return Await APIClient.PutAsync(String.Format("api/widget/{0}", ID), DesiredContent)
End Function
Good luck! For me this worked out (in the end!).
Regards,
Peter
I think you can do this:
var client = new HttpClient();
HttpContent content = new Widget();
client.PostAsync<Widget>("http://localhost:44268/api/test", content, new FormUrlEncodedMediaTypeFormatter())
.ContinueWith((postTask) => { postTask.Result.EnsureSuccessStatusCode(); });
In case someone like me didn't really understand what all above are talking about, I give an easy example which is working for me.
If you have a web api which url is "http://somesite.com/verifyAddress", it is a post method and it need you to pass it an address object. You want to call this api in your code. Here what you can do.
public Address verifyAddress(Address address)
{
this.client = new HttpClient();
client.BaseAddress = new Uri("http://somesite.com/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var urlParm = URL + "verifyAddress";
response = client.PostAsJsonAsync(urlParm,address).Result;
var dataObjects = response.IsSuccessStatusCode ? response.Content.ReadAsAsync<Address>().Result : null;
return dataObjects;
}
This is the code I wound up with, based upon the other answers here. This is for an HttpPost that receives and responds with complex types:
Task<HttpResponseMessage> response = httpClient.PostAsJsonAsync(
strMyHttpPostURL,
new MyComplexObject { Param1 = param1, Param2 = param2}).ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode());
//debug:
//String s = response.Result.Content.ReadAsStringAsync().Result;
MyOtherComplexType moct = (MyOtherComplexType)JsonConvert.DeserializeObject(response.Result.Content.ReadAsStringAsync().Result, typeof(MyOtherComplexType));
Make a service call like this:
public async void SaveActivationCode(ActivationCodes objAC)
{
var client = new HttpClient();
client.BaseAddress = new Uri(baseAddress);
HttpResponseMessage response = await client.PutAsJsonAsync(serviceAddress + "/SaveActivationCode" + "?apiKey=445-65-1216", objAC);
}
And Service method like this:
public HttpResponseMessage PutSaveActivationCode(ActivationCodes objAC)
{
}
PutAsJsonAsync takes care of Serialization and deserialization over the network