C# Web API - POST with ID only - c#

I have an endpoint that clones a record but to do this I only need to use an Id. Since I only need to use an Id I've written this endpoint:
[HttpPost]
[Route("test/clone/{id}")]
[SwaggerResponse(HttpStatusCode.OK, description: "Clone an existing record", type: typeof(int))]
public async Task<IHttpActionResult> Clone(int id)
{
var newId = _service.DoSomething(id);
return Ok(newId);
}
I then want to call this endpoint in a test but I don't know how to write this:
var result =
await WebServer.HttpClient.PostAsync($"test/clone/{id}", // WHAT GOES HERE??)
var newId = await result.Content.ReadAsAsync<int>();
Does anyone know how I can call this endpoint using HttpClient??

You can just pass null and it will send it with no body
e.g.
var result =
await WebServer.HttpClient.PostAsync($"test/clone/{id}", null)

Related

How do i call a Put-method from WEB API in separate project?

I have built a Web API that is connected to a database for persons. I am now trying to call this Web API from a separate MVC-application which is supposed to have full CRUD. So far i have managed to do so with the Get and Post-methods to create a new person and see a list of the persons currently in the database.
When trying to do a similar call for the Put-method, i get the following error:
This is how my method UpdatePerson is written in my API-application:
[HttpPut]
[Route("{id:guid}")]
public async Task<IActionResult> UpdatePerson([FromRoute] Guid id, UpdatePersonRequest updatePersonRequest)
{
var person = await dbContext.Persons.FindAsync(id);
if (person != null)
{
person.Name = updatePersonRequest.Name;
person.Email = updatePersonRequest.Email;
person.Phone = updatePersonRequest.Phone;
person.Address = updatePersonRequest.Address;
await dbContext.SaveChangesAsync();
return Ok(person);
}
And this is how i am trying to consume the API in my separate MVC-project:
[HttpGet]
public IActionResult Edit()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Edit(PersonViewModel pvm)
{
HttpClient client = new();
StringContent sContent = new StringContent(JsonConvert.SerializeObject(pvm), Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PutAsync("https://localhost:7281/api/Persons/", sContent);
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Get");
}
else
{
return NotFound();
}
}
Everything is working fine when i try to update the database through the API-app so i am not really sure what is wrong with my request. I hope that someone here can spot the issue right away or at least help me out as i am quite a beginner with WEB APIs.
I have mostly tried changing the URL in my MVC-project but the issue remains.
Are you sure you are receiving the request? It seems that your URI is
"https://localhost:7281/api/Persons/"
and your API is expecting
"https://localhost:7281/api/Persons/{id}" -> where {id} should be the guid
you need to append the guid in the URI
Looks like the request doesn't receive the correct the correct parameters, because the URI that appears in your picture seems a generic method.

Post Multiple Parameters Without DTO Class

I have an action on my web project which calls to an API
[HttpPost]
public async Task<IActionResult> ExpireSurvey(int id)
{
var token = await HttpContext.GetTokenAsync("access_token");
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var path = "/api/forms/ExpireSurvey";
var url = Domain + path;
var data = JsonConvert.SerializeObject(id);
HttpContent httpContent = new StringContent(data, Encoding.UTF8, "application/json");
var response = await client.PutAsync(url, httpContent);
return Json(response);
}
}
In the API project this is received as follows:
[HttpPut]
public IActionResult ExpireSurvey([FromBody] int surveyId)
{
_repository.ExpireSurvey(surveyId, expiryDate);
return Ok();
}
This works fine - however, say I want to pass in an int id and a DateTime variable, how do I serialise and pass them both into the HttpContent? I can do it with a DTO object, but I don't want to be setting up DTO objects when there is only two fields.
You can use anonymous types like this
var x = new { id = 2, date = DateTime.Now };
var data = JsonConvert.SerializeObject(x);
When receiving the data, you can only have one [FromBody] parameter. So that doesn't work for receiving multiple parameters (unless you can put all but one into the URL). If you don't want to declare a DTO, you can use a dynamic object like this:
[HttpPost]
public void Post([FromBody] dynamic data)
{
Console.WriteLine(data.id);
Console.WriteLine(data.date);
}
Don't overdo using anonymous types and dynamic variables though. They're very convenient for working with JSON, but you lose all type checking which is one of the things that makes C# really nice to work with.
I think it would be helpful to recognize that ASP.NET Core is REST-based and REST fundamentally deals with the concept of resources. While not an unbreakable rule, the general idea is that you should have what you're calling DTOs here. In other words, you're not posting distinct and unrelated bits of data, but an object that represents something.
This becomes increasingly important if you start mixing in things like Swagger to generate documentation for your API. The objects you create become part of that documentation, giving consumers of your API a template for follow in the development of their apps.
Long and short, I'd say embrace the concept of resources/objects/DTOs/whatever. Model the data your API works with. It will help both you as a developer of the API and any consumers of your API.
You can pass multiple parameters in as URL as below example
Parameter name must be the same (case-insensitive), If names do not
match then values of the parameters will not be set.
[HttpPost]
[Route("{surveyId}/{expiryDate}")]
public IActionResult Post(int surveyId, DateTime expiryDate)
{
return Ok(new { surveyId, expiryDate });
}
Call URL
http://localhost:[port]/api/[controller]/1/3-29-2018
Based on the answers above, I got the following code working. Hope this helps someone! (thanks to others of course for getting me on the right track)
/// <summary>
/// Post api/dostuff/{id}
[HttpPost]
[Route("dostuff/{id}")]
public async Task<IActionResult> DoStuff([FromBody]Model model, int id)
{
// Both model and id are available for use!
}
You can replace the line
var data = JsonConvert.SerializeObject(id);
with
var data = new StringContent(JsonConvert.SerializeObject((surveyId, expiryDate)), Encoding.UTF8, "application/json");
The trick is that you use a Tuple object containig your parameters as a single parameter.
You should use the Tuple type on the the server side as well. It could look like:
[HttpPost]
public Task MyWebApiMethod([FromBody] (int SurveyId, DateTime ExpiryDate) parameters)
{
int surveyId = parameters.SurveyId;
DateTime expiryDate = parameters.ExpiryDate;
// Process your parameters ...
}
I do not have Visual Studio now and sorry if there are any compilation issues etc.
you can do it with a dictionary
Dictionary<int, object> dict = new Dictionary<int, object>();
dict["id"] = 1
dict["date"] = DateTime.Now;
JsonConvert.SerializeObject(dict);

Error "405 Method Not Allow" When Calling Put method in Postman with body parameter

I was trying to call the Put method through Postman and always getting error: "405 Method Not Allow" and "Message": "The requested resource does not support http method 'PUT'."
I'm using DocumentDB and C#. Here is my code:
[Route("multilanguage/Resources/{id}/{Language}")]
[HttpPut]
public async Task<IHttpActionResult> UpdateResource(string Id, string Language, string text)
{
client = new DocumentClient(new Uri(EndPoint), AuthKey);
var collectionLink = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId);
var query = new SqlQuerySpec("SELECT * FROM MultiLanguage as m where m.id = #pmId",
new SqlParameterCollection(new SqlParameter[] { new SqlParameter { Name = "#pmId", Value = Id } }));
Document doc = client.CreateDocumentQuery<Document>(
collectionLink, query).AsEnumerable().FirstOrDefault();
List<Models.Translations> d = doc.GetPropertyValue<List<Models.Translations>>("Translations");
Models.Translations temp = d.Find(p => p.Language == Language);
temp.Content = text;
temp.LastModified = DateTimeOffset.Now;
temp.ModifiedBy = "admin";
doc.SetPropertyValue("Translations", d);
Document updated = await client.ReplaceDocumentAsync(doc);
return Ok();
}
When I call the Put method throught Postman, I call "http://localhost:XXXX/multilanguage/resources/2/En". "2" and "En" are the first two parameters in my code. And I also specify the "text" parameter value in the Postman request Body with x-www-form-urlencoded type: key = text, value = Test! This put method suppose to update the temp.Content value to "Test!". However, it always failed with the error I mentioned above.
Did I miss anything here?
The 405 error when performing a PUT request to web api is a well known topic. You can find many solutions in this or this SO question.
And for the design of you controller:
PUT are designed to have a body, just like POST and in your case
you should send all parameters in the body instead.
You should create a class which contains the objects you want to send to the server:
public class resourceClass
{
public string Id { get; set; }
public string Language { get; set; }
public string text { get; set; }
}
Then specify the route without the attribute routing and get the object from the request body
[Route("multilanguage/Resources/PutResource")]
[HttpPut]
public async Task<IHttpActionResult> UpdateResource([FromBody] resourceClass obj)
{
client = new DocumentClient(new Uri(EndPoint), AuthKey);
var collectionLink = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId);
var query = new SqlQuerySpec("SELECT * FROM MultiLanguage as m where m.id = #pmId",
new SqlParameterCollection(new SqlParameter[] { new SqlParameter { Name = "#pmId", Value = Id } }));
Document doc = client.CreateDocumentQuery<Document>(
collectionLink, query).AsEnumerable().FirstOrDefault();
List<Models.Translations> d = doc.GetPropertyValue<List<Models.Translations>>("Translations");
Models.Translations temp = d.Find(p => p.Language == Language);
temp.Content = text;
temp.LastModified = DateTimeOffset.Now;
temp.ModifiedBy = "admin";
doc.SetPropertyValue("Translations", d);
Document updated = await client.ReplaceDocumentAsync(doc);
return Ok();
}
From the client you could add an object to the PUT request of Content-Type application/json like this
var data = {
Id: clientId,
Language: clientLanguage,
text: clientText
};
Don't forget to stringify the json when adding it to the http request
data: JSON.stringify(data),
The PUT controller will then be reached at "http://localhost:XXXX/multilanguage/resources/putresource".
Check the URL for which you are posting the data, in my case the URL was incorrect because of which I got these errors, also verify that in Body you should select raw and change the Text to JSON if you are passing a JSON as a data.

Request Body is null in Post request in Web Api

I have a action method like this
[ResponseType(typeof(DiaryDeviceDTO))]
[HttpPost]
[Route("api/Device/Register")]
public async Task<IHttpActionResult> Register(DeviceRegistration deviceRegistration)
{
if (deviceRegistration == null)
{
return BadRequest("Request body is null");
}
DiaryDevice device = await _deviceBl.Register(deviceRegistration.RegistrationCode);
var deviceDto = Mapper.Map<DiaryDevice, DiaryDeviceDTO>(device);
return Ok(deviceDto);
}
When I call this api from PostMan with below request body, I get deviceRegistration object as null. I also set ContentType header as application/json
{
"ApiKey" : "apikey",
"RegistrationCode" : "123",
"ImeiNo" : "12345"
}
Then I try to read the request content as below-
string body = await Request.Content.ReadAsStringAsync();
This time I also get body = ""
But when I run my Unit test I get deviceRegistration as I wanted. So what's wrong with my code. Why my code only work for unit testing. I am using Web Api 2.2
Update / Solution
Sorry for asking this question. Actually it was my mistake. I accidentally read the request body inside Application_BeginRequest() method for logging. I move those logging codes inside Application_EndRequest() method and everything becomes ok.
Given what you've shown, this should work for the requests to api/device/register
[ResponseType(typeof(DiaryDeviceDTO))]
[HttpPost]
[Route("api/Device/Register")]
public async Task<IHttpActionResult> Register([FromBody]DeviceRegistration deviceRegistration)
{
if (deviceRegistration == null)
{
return BadRequest("Request body is null");
}
DiaryDevice device = await _deviceBl.Register(deviceRegistration.RegistrationCode);
var deviceDto = Mapper.Map<DiaryDevice, DiaryDeviceDTO>(device);
return Ok(deviceDto);
}
Note the [FromBody] attribute.

How to call a Post api with multiple parameters

How can i call a Post method with multiple parameters using HttpClient?
I am using the following code with a single parameter:
var paymentServicePostClient = new HttpClient();
paymentServicePostClient.BaseAddress =
new Uri(ConfigurationManager.AppSettings["PaymentServiceUri"]);
PaymentReceipt payData = SetPostParameter(card);
var paymentServiceResponse =
paymentServicePostClient.PostAsJsonAsync("api/billpayment/", payData).Result;
I need to add another parameter userid. How can i send the parameter along with the 'postData'?
WebApi POST method prototype:
public int Post(PaymentReceipt paymentReceipt,string userid)
Simply use a view model on your Web Api controller that contains both properties. So instead of:
public HttpresponseMessage Post(PaymentReceipt model, int userid)
{
...
}
use:
public HttpresponseMessage Post(PaymentReceiptViewModel model)
{
...
}
where the PaymentReceiptViewModel will obviously contain the userid property. Then you will be able to call the method normally:
var model = new PaymentReceiptViewModel()
model.PayData = ...
model.UserId = ...
var paymentServiceResponse = paymentServicePostClient
.PostAsJsonAsync("api/billpayment/", model)
.Result;
UserId should be in query string:
var paymentServiceResponse = paymentServicePostClient
.PostAsJsonAsync("api/billpayment?userId=" + userId.ToString(), payData)
.Result;
In my case my existing ViewModels don't line up very nicely with the data I want to post to my WebAPI. So, instead of creating an entire new set of model classes, I posted an anonymous type, and had my Controller accept a dynamic.
var paymentServiceResponse = paymentServicePostClient.PostAsJsonAsync("api/billpayment/", new { payData, userid }).Result;
public int Post([FromBody]dynamic model)
{
PaymentReceipt paymentReceipt = (PaymentReceipt)model.paymentReceipt;
string userid = (string)model.userid;
...
}
(I'd be curious to hear some feedback on this approach. It's definitely a lot less code.)

Categories