I'm trying to post anonymous object via httpclient, however orderId is null and collection empty when it hits controller.
public async Task<Response> CancelOrderAsync(int orderId, ICollection<int> ids)
{
Response result = null;
using (IHttpClient client = HttpClientFactory.CreateHttpClient())
{
var obj = new {OrderId = orderId, Ids = ids};
string json = JsonConvert.SerializeObject(obj);
HttpContent postContent = new StringContent(json, Encoding.UTF8, "application/json");
using (var response = await client.PostAsync($"{url}/admin/cancel", postContent).ConfigureAwait(false))
{
if (response != null && response.IsSuccessStatusCode)
{
...
}
}
}
return result;
}
// Controller
[HttpPost]
[ActionName("cancel")]
public async Task<Response> Cancel(int orderId, ICollection<int> ids)
{
// order is null, collection empty
...
EDIT:
Changed my controller to this for simplicity
[HttpPost]
[ActionName("cancel")]
public async Task<SimpleResponse> Cancel(int orderId)
Via Postman, i'm posting this body:
{
"orderId": "12345"
}
Still, orderId comes in as 0 (zero) ??
The controller action on the server side will need a concrete type to read the entire body of the request
public class Order {
public int OrderId { get; set; }
public int[] Ids { get; set; }
}
This is primarily because the action can only read from the body once.
Update action to...
[HttpPost]
[ActionName("cancel")]
public async Task<Response> Cancel([FromBody]Order order) {
if(ModelState.IsValid) {
int orderId = order.OrderId;
int[] ids = order.Ids;
//...
}
//...
}
the original code used to send the request in the example will work as is, but as mentioned it can be improved.
The HttpClient can do the serialisation for you. See if
var response = await client.PostAsJsonAsync($"{url}/admin/cancel", obj);
works better. Then you don't need to write the serialisation code yourself.
If you still have a problem, use a tool such as Fiddler to monitor the actual request and see what parameter and values are submitted in the request body, to see if they match what's expected by the endpoint.
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'm trying to pass a big string in my controller using json. Also i need Controller to send me an answer.
Here is my controller in web api:
public class CustomersController : ApiController
{
// GET: api/Customers
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET: api/Customers/5
public string Get(int id)
{
return "value";
}
// POST: api/Customers
public void Post([FromBody]string value)
{
}
// PUT: api/Customers/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE: api/Customers/5
public void Delete(int id)
{
}
}
First of all where i should read my string and where should i send an answer?
And here is my client which try to send a string
using (var client = new HttpClient())
{
var response = await client.PostAsync("http://192.168.1.15:8282/",new StringContent("Mystring", Encoding.UTF8, "application/json"));
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
}
I need my web api to read my string, and then to send me an answer
Instead of having the methods as void you need to return the string value from the controller methods.
Also, don't forget to decorate the methods with respective http verb attribubte (HttpGet, HttpPost, HttpPut etc.) which the method is responsible to serve.
Here's an example where the method returns an Ok result, this generate an http status code 200 with the string in the response body
[HttpPost]
public IHttpActionResult Post([FromBody]string value)
{
return Ok(value);
}
Then for the client call.
First of, you need to specify the route to the controller correctly
192.168.1.15:8282/api/Customers
Then, sending a single string as content when using the content-type of application/json is not suitable as json always start parsing from an object {} or array [].
The easiest way of sending a single string is therefore to just change the content type to application/x-www-form-urlencoded and adding a = sign infront of the string
using (var client = new HttpClient())
{
var response = await client.PostAsync("http://192.168.1.15:8282/api/Customers",new StringContent("=Mystring", Encoding.UTF8, "application/x-www-form-urlencoded"));
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
}
}
In my WebApi I have a HttpGet and HttpPost method, the get method is working fine and the post method is called but the body content is always null, unless used in a HttpRequestMessage. I tried providing the body content in a string format(preferred datatype) aswell as in a model but neither one of those methods worked. I also tried switching the content type without success. Does anyone know if I'm doing something wrong or how I can easily get the variable data from the HttpRequestMessage, which in the example below is "test".
Method 1:
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]string filecontent, string companycode)
{
MessageBox.Show(filecontent);
Return Ok("");
}
Method 2 (with model):
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]ItemXML filecontent, string companycode)
{
MessageBox.Show(filecontent.XMLContent);
Return Ok("");
}
Model:
public class ItemXML
{
public ItemXML(string content)
{
XMLContent = content;
}
public string XMLContent { get; set; }
}
Method 3:
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem(HttpRequestMessage filecontent, string companycode)
{
var content = filecontent.Content.ReadAsStringAsync().Result;
MessageBox.Show(content);
Return Ok("");
}
Method 3 content string ("test" is the provided value): " content "------WebKitFormBoundarydu7BJizb50runvq0\r\nContent-Disposition: form-data; name=\"filecontent\"\r\n\r\n\"test\"\r\n------WebKitFormBoundarydu7BJizb50runvq0--\r\n" string"
Create a model store data to be sent to server
public class Model {
public string filecontent { get; set;}
public string companycode { get; set;}
}
Update Action
[HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]Model model) {
if(ModelStat.IsValid) {
return Ok(model); //...just for testing
}
return BadRequest();
}
On the client make sure the request is being sent properly. In this case going to use JSON.
public client = new HttpClient();
var model = new {
filecontent = "Hello World",
companycode = "test"
};
var response = await client.PostAsJsonAsync(url, model);
If using another type of client ensure that the data being sent is formatted correctly for the Web API action to accept the request.
Reference Parameter Binding in ASP.NET Web API
I have a simple web api but when I test it with Postman, the method parameter is null. I've looked and over other questions with similar title but I haven't found an answer.
Here is my controller action:
[HttpPost]
public Member Create([FromBody] string payload)
{
var s = Request.Content.ReadAsStringAsync();
if (payload == null)
{
throw new ArgumentNullException(nameof(payload));
}
Console.WriteLine(payload);
Console.WriteLine(s);
return null;
}
And here is my postman configuration:
In the headers tab I've added content-type to be application/json.
when I'm debugging this, the payload variable is allways null and the string s contains something like
Id = 98, Status = RanToCompletion, Method = "{null}", Result = ""
So what am I doing wrong?
Buda,
HttpContent.ReadAsStringAsync returns a Task<string>, hence the value of the s string.
You have to update your action to make it async:
[HttpPost]
public async Task<Member> Create([FromBody] string payload)
{
var s = await Request.Content.ReadAsStringAsync();
if (payload == null)
{
throw new ArgumentNullException(nameof(payload));
}
Console.WriteLine(payload);
Console.WriteLine(s);
return null;
}
You should wrap your string in model (object):
class Model
{
public string payload {get;set;}
}
[HttpPost]
public async Task<Member> Create([FromBody] Model model) // wrap Member in Task and add async keyword
{
var s = await Request.Content.ReadAsStringAsync(); // add await here
if (model.payload == null)
{
throw new ArgumentNullException(nameof(model.payload));
}
Console.WriteLine(model.payload);
Console.WriteLine(s);
return null;
}
If you don't want to use model, try to send only plain string, like "some payload", do not wrap it in json.
I'm looking at the documentation of WebAPI 2, and i'm severely disappointed with the way the action results are architected. I really hope there is a better way.
So documentation says I can return these:
**void** Return empty 204 (No Content)
**HttpResponseMessage** Convert directly to an HTTP response message.
**IHttpActionResult** Call ExecuteAsync to create an HttpResponseMessage, then convert to an HTTP response message.
**Other type** Write the serialized return value into the response body; return 200 (OK).
I don't see a clean way to return an array of items with custom HTTP status code, custom headers and with auto negotiated content though.
What I would like to see is something like
public HttpResult<Item> Post()
{
var item = new Item();
var result = new HttpResult<Item>(item, HttpStatusCode.Created);
result.Headers.Add("header", "header value");
return result;
}
This way I can glance over a method and immediately see whats being returned, and modify status code and headers.
The closest thing I found is NegotiatedContentResult<T>, with weird signature (why does it need an instance of controller?), but there's no way to set custom headers?
Is there a better way ?
The following code should give you everything you want:
[ResponseType(typeof(Item))]
public IHttpActionResult Post()
{
var item = new Item();
HttpContext.Current.Response.AddHeader("Header-Name", "Header Value");
return Content(HttpStatusCode.Created, item);
}
... if you really need to return an array of items ...
[ResponseType(typeof(List<Item>))]
public IHttpActionResult Post()
{
var items = new List<Item>();
// Do something to fill items here...
HttpContext.Current.Response.AddHeader("Item-Count", items.Count.ToString());
return Content(HttpStatusCode.Created, items);
}
I don't think the designers of the web-api intended for controller methods to be fiddling with the headers.
The design pattern seems to be to use DelegatingHandler, ActionFilterAttribute and the ExecuteAsync overridable method of ApiController to handle authentication and response formatting.
So perhaps your logic for message content negotiation should be handled there ?
However if you definitely need to control headers from within your controller method you can do a little set-up to make it work.
To do so you can create your own DelegationHandler that forwards selected headers from your "Inner" response headers:
public class MessageHandlerBranding : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
//If we want to forward headers from inner content we can do this:
if (response.Content != null && response.Content.Headers.Any())
{
foreach (var hdr in response.Content.Headers)
{
var keyUpr = hdr.Key.ToUpper(); //Response will not tolerate setting of some header values
if ( keyUpr != "CONTENT-TYPE" && keyUpr != "CONTENT-LENGTH")
{
string val = hdr.Value.Any() ? hdr.Value.FirstOrDefault() : "";
response.Headers.Add(hdr.Key, val);
}
}
}
//Add our branding header to each response
response.Headers.Add("X-Powered-By", "My product");
return response;
}
}
Then you register this handler in your web-api configuration, this is usually in the GlobalConfig.cs file.
config.MessageHandlers.Add(new MessageHandlerBranding());
You could also write your own custom class for the response object like this:
public class ApiQueryResult<T> : IHttpActionResult where T : class
{
public ApiQueryResult(HttpRequestMessage request)
{
this.StatusCode = HttpStatusCode.OK; ;
this.HeadersToAdd = new List<MyStringPair>();
this.Request = request;
}
public HttpStatusCode StatusCode { get; set; }
private List<MyStringPair> HeadersToAdd { get; set; }
public T Content { get; set; }
private HttpRequestMessage Request { get; set; }
public void AddHeaders(string headerKey, string headerValue)
{
this.HeadersToAdd.Add(new MyStringPair(headerKey, headerValue));
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = this.Request.CreateResponse<T>(this.StatusCode, this.Content);
foreach (var hdr in this.HeadersToAdd)
{
response.Content.Headers.Add(hdr.key, hdr.value);
}
return Task.FromResult(response);
}
private class MyStringPair
{
public MyStringPair(string key, string value)
{
this.key = key;
this.value = value;
}
public string key;
public string value;
}
}
And use it like this in your controller:
[HttpGet]
public ApiQueryResult<CustomersView> CustomersViewsRow(int id)
{
var ret = new ApiQueryResult<CustomersView>(this.Request);
ret.Content = this.BLL.GetOneCustomer(id);
ret.AddHeaders("myCustomHkey","myCustomValue");
return ret;
}