I am learning to create a RESTful API with a client, but am struggling with passing user input to the post. My controller is fine as I can send data to db (tested with Swagger) but on the client side the debugger is giving me an error on my PostAsJsonAsync. I think it probably has to do with the routing. Here is my post code from my client:
static async Task AddAsync(ForumPost fp)
{
try
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:7656/");
client.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP POST
ForumPost thePost = new ForumPost() {
Subject = fp.Subject,
Message = fp.Message};
HttpResponseMessage response = await client.PostAsJsonAsync("post", thePost);
if (response.IsSuccessStatusCode)
{
Uri uri = response.Headers.Location;
Console.WriteLine("URI for new resource: " + uri.ToString());
}
else
{
Console.WriteLine(response.StatusCode + " " + response.ReasonPhrase);
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.ReadLine();
}
}
and the relevant bit of the controller
[HttpPost]
// POST: api/Forum
[Route("post")]
public void PostNewMessage (string subject, string message)
{
if (ModelState.IsValid)
{
ForumPost p = new ForumPost(subject, message);
db.ForumPosts.Add(p);
db.SaveChanges();
}
}
I have looked around at various different but similar questions here on SO but struggling to understand. I have tried putting placeholders in the route but maybe I implemented it incorrectly? (that's if that is even the correct way to be thinking!) If anyone could help me out on this I would appreciate it.
When your Web API action parameters are simple types like strings, the parameter binding mechanism assumes they are coming from the query string. To infer that the values should come from the request body, just use your ForumPost class directly as your parameter instead of the individual string values:
[HttpPost]
// POST: api/Forum
[Route("post")]
public void PostNewMessage(ForumPost p)
{
if (ModelState.IsValid)
{
db.ForumPosts.Add(p);
db.SaveChanges();
}
}
Also note that ForumPost needs a parameterless constructor in order for the framework to know how to create an instance. Define it like this and you should be good:
public class ForumPost
{
public string Subject { get; set; }
public string Message { get; set; }
}
Related
I need to save the changes I make in my model through API call in my database. I have checked my API is working fine when I am running it individually on Web. But its giving me an error StatusCode: 405, ReasonPhrase: 'Method Not Allowed'. I am trying to send and object and trying to see whether the request made was completed or not. When I am trying to debug it, it is not sending hit on my API controller.
Here is my model class:
public class Customer
{
[Required]
public Guid CustomerId { get; set; }
public int Age { get; set; }
public int Phone { get; set; }
}
PUT Method in API:
[HttpPut]
[Route("api/[controller]/{customer}")]
public IActionResult EditCustomer(Customer customer)
{
var cust = _customerData.EditCustomer(customer);
if (cust == string.Empty)
{
return Ok();
}
else
{
return new StatusCodeResult(StatusCodes.Status500InternalServerError);
}
}
The method I am using in project to call API:
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(apiBaseUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")
);
var sum = await client.PutAsJsonAsync("api/Customer/", customer);
if (sum.StatusCode == System.Net.HttpStatusCode.OK)
{
return RedirectToActionPermanent(actionName: "SingIn");
}
else
{
TempData["msg"] = "There is an error";
return View();
}
where baseaddress= {https://localhost:44398/}
EditCustomer Method
public string EditCustomer(Customer customer)
{
try
{
var pro = _customerContext.Customer.Where(e => e.CustomerId == customer.CustomerId).FirstOrDefault();
pro.Age = customer.Age;
pro.Phone = customer.Phone;
pro.Name = customer.Name;
_customerContext.Entry(pro).State = EntityState.Modified;
_customerContext.SaveChanges();
}
catch(Exception e)
{
return e.Message;
}
return string.Empty;
}
You need to fix your action route by removing {Customer}, since you send customer in request body, not as a route value
[Route("~/api/Customer")]
and request
var sum = await client.PutAsJsonAsync("/api/Customer", customer);
or better fix the acttion route name to meaningfull
[Route("~/api/EditCustomer")]
and
var sum = await client.PutAsJsonAsync("/api/EditCustomer", customer);
AsJsonAsync sometimes causes problems
try this code
var json = JsonSerializer.Serialize(customer);
//or if you are using Newtonsoft
var json = JsonConvert.SerializeObject(customer);
var contentData = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PutAsync("/api/Customer", contentData);
if (response.IsSuccessStatusCode)
return RedirectToActionPermanent("SingIn");
else
{
TempData["msg"] = "There is an error";
return View();
}
but IMHO I would prefer to use
client.PostAsync("/api/EditCustomer", contentData);
instead of Put.
and added [FromBody] to action
[HttpPost("~/api/EditCustomer")]
public IActionResult EditCustomer([FromBody] Customer customer)
I am no pro in web APIs but I suspect it could be due to the fact that the API expects customer to be in request URL.
Try and change the API route to [Route("api/[controller]")]
This could've been a comment but I don't have enough reputation :)
I used the HttpResponseMessage Post method to let the mobile terminal verify the account password. I used the following CODE to run successfully, but the POST format must be run like this.
'{"ID":"xxx","Password":"xxx"}'
It need two ' can run, I don't know why.
I can't request a service using the normal POST format on iOS or Android.
The format I want is {"ID":"xxx","Password":"xxx"},without '
[HttpPost]
public HttpResponseMessage Post([FromBody] string DATA)
{
using (appapidataEntities entities = new appapidataEntities())
{
//string controllerName = ControllerContext.RouteData.Values["controller"].ToString();
JObject jo = JObject.Parse(DATA);
string id = jo["ID"].ToString();
string password = jo["Password"].ToString();
var user = entities.USERs.Where(x => x.ID == id && x.Password == password).FirstOrDefault();
var result = new
{
message = "failure"
};
var result2 = new
{
message = "success"
};
if (user == null)
{
return Request.CreateResponse(HttpStatusCode.OK, result);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK, result2);
}
}
}
public partial class USER
{
public string ID { get; set; }
public string Password { get; set; }
}
}
Please have someone with experience to help me, thank you very much.
As #Nkosi said, the correct way to receive a complex object is using a class/object (also called 'model binding')
In your models add this class. This class will be the 'contract' between the service with any external application who calls the service. Usually, any client (service, app or frontend) also has a class with this contract to call the service.
public class LoginViewModel {
public string ID { get; set; }
public string Password { get; set; }
}
Now, modify the controller as follow
[HttpPost]
public HttpResponseMessage Post([FromBody] LoginViewModel DATA) {
using (appapidataEntities entities = new appapidataEntities())
string id = DATA.ID;
string password = DATA.Password;
// rest of the code
}
}
Make sure the device is sending the data the service is waiting (maybe adding a breakpoint if you are debugging from Android Studio before to make the request) and add a breakpoint in your controller to verify that the variable DATA has the correct values.
I am trying to consume/call an MVC Web API controller method, which will be used to upload a file. I am struggling to call it from my MVC controller.
Here's my code for the API Controller
public class ImportController : ApiController
{
[HttpPost]
public bool PutImportFile(byte[] fileToBeImported, string nameOfTheFileToBeImported)
{
// I am doing file saving stuff here
}
}
I have tested the file saving part by changing the method to HttpGet and its working when I called it directly from the browser. I removed the parameters for that.
However, I am not able to figure out how to call it from a client.
I have tried below.
public class ImportFileModel
{
public byte[] FileToBeImported { get; set; }
public string NameOfTheFileToBeImported { get; set; }
}
The below code will accept a file from the browser uploaded by user and post it to the API controller to save the file.
[HttpPost]
public async Task<JsonResult> Upload()
{
byte[] file;
string fileName = string.Empty;
if (Request.Files.Count > 0)
{
try
{
fileName = Request.Files[0].FileName;
using (MemoryStream ms = new MemoryStream())
{
Request.Files[0].InputStream.CopyTo(ms);
file = ms.ToArray();
}
//To do: get url from configuration
string url = "http://localhost:(port)/api/Import/PutImportFile";
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(url);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/bson"));
ImportFileModel request = new ImportFileModel
{
FileToBeImported = file,
NameOfTheFileToBeImported = fileName
};
MediaTypeFormatter bsonFormatter = new BsonMediaTypeFormatter();
var result = await client.PostAsync(url, request, bsonFormatter);
HttpResponseMessage response = result.EnsureSuccessStatusCode();
}
}
catch (Exception ex)
{
// exception handling here
}
}
return Json(true, JsonRequestBehavior.AllowGet);
}
It ends up in an exception at the last line.
HttpResponseMessage response = result.EnsureSuccessStatusCode();
Throwing 404 not found error.
I have also tried the same from a console application using HttpWebRequest. It also throws the same error.
Your Web API method PutImportFile is setup to receive two values, not a single model; hence, your HttpClient call is not recognized (no matching route found). Change your Web API method to receive a model:
public class ImportController : ApiController
{
[HttpPost]
public bool PutImportFile(ImportFileModel fileInfo)
{
//Your code to save the file...
}
}
When I try to pass a value to my Web API using a Windows Forms Client like this:
SessionModel s = new SessionModel()
{
SessionID = "123456"
};
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:49584/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", txtToken.Text.Trim());
try
{
HttpResponseMessage response = await client.PostAsJsonAsync("api/account/depositaccounts", s);
response.EnsureSuccessStatusCode(); // Throw if not a success code.
if (response.IsSuccessStatusCode)
{
MessageBox.Show("Results", "Success!", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
catch (HttpRequestException ex)
{
MessageBox.Show(ex.Message);
}
}
And the API Controller code is this:
[HttpPost]
[Authorize]
[Route("DepositAccounts", Name = "DepositAccounts")]
public HttpResponseMessage GetDepositAccounts(Models.AuthenticationBindingModel model)
{
var sessionId = model.SessionID;
}
When the model comes in the SessionID property is null. I confirmed that is not null on the client side.
When use Fiddler to create the Json request on the other hand it works. Am I missing something on the client side?
Thanks.
** EDIT: changed the property 'SessionId' to 'SessionID' on the client side. Same issue though.
Change the API signiture to be GetDepositAccounts(dynamic model) or use the same class on both client and server side.
Apparently on the client side I was declaring my class with a constructor like this:
class SessionModel
{
public SessionModel()
{
}
public String SessionId { get; set; }
}
And it resulted in some extra data being added to the fields in the json request ('k__BackingField'). Although there are lots of articles on how to remove that from the response I couldn't figure out how to remove it from the request. Turns out declaring the class on the client side without the constructor removes that 'k__BackingField' text and then the request works.
class SessionModel
{
public String SessionID { get; set; }
}
Not sure the reasoning behind this, just that it happens. If anyone wants to comment on the 'why' that would be great!
Im trying to figure out how to use Web API. I have gone through some tutorials and now Im trying to set up my web service.
I have a really hard time trying to figure out why it cant find my methods. To me it just seems like random (the tutorials worked fine).
During my experiments sometimes the get method returns "method not allowed".
This is my service:
public class ContentFilesController : ApiController
{
[Route("api/contentfile/{id}")]
[HttpGet]
public IHttpActionResult GetContentFiles(int count)
{
if (_contentFiles == null)
GenerateContentFileList();
List<ContentFile> files = new List<ContentFile>();
int i = 0;
while(true)
{
ContentFile cf = _contentFiles[i];
if(!_filesOutForProcessing.Contains(cf))
{
files.Add(cf);
i++;
}
if (i == count)
break;
}
return Ok(files);
}
[HttpPost]
[Route("api/contentfile/{files}")]
public IHttpActionResult Post([FromBody] List<ContentFile> files)
{
return Ok();
}
}
Edit:
This is the code I am using to call the service:
static async Task TestAsync() {
using (var client = new HttpClient()) {
client.BaseAddress = new Uri("http://localhost:46015/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync("api/contentfile/1");
if (response.IsSuccessStatusCode)
{
var contentfiles = await response.Content.ReadAsAsync<List<ContentFile>>();
}
}
}
static async Task ReportTest()
{
List<ContentFile> files = new List<ContentFile>()
{
new ContentFile(){Path="hej"},
new ContentFile(){Path="då"}
};
using(var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:46015");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync<List<ContentFile>>("api/contentfile", files);
if(response.IsSuccessStatusCode)
{
}
}
}
Where do you start looking?
Im going crazy here.
Thanks!
Edit: to clarify the error, the problem with both client methods are that the HttpResponseMessage has response.IsSuccessStatusCode false and the StatusCode = MethodNotAllowed or MethodNotFound.
Problems with the GET method
For the HTTP Get method, there is a problem with your routing.
You have declared the GET route as this:
[Route("api/contentfile/{id}")]
but then the method parameter is declared as this:
public IHttpActionResult GetContentFiles(int count)
When using Attribute-based routing, the parameter names have to match.
I made a very simple reproduction of your code (obviously I don't have your classes but the infrastructure will be the same)
In the WebAPI project
public class ContentFile
{
public int ID { get; set; }
}
public class ContentFilesController : ApiController
{
[Route("api/contentfile/{count}")] //this one works
[Route("api/contentfile/{id}")] //this one does not work
[HttpGet]
public IHttpActionResult GetContentFiles(int count)
{
var files = new List<ContentFile>();
for (int x = 0; x < count; x++)
{
files.Add(new ContentFile(){ID=x});
}
return Ok(files);
}
}
In the client project
public class ContentFile
{
public int ID { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:51518/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("api/contentfile/1").Result;
var data = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(data);
Console.ReadKey();
}
}
}
So the code is not quite identical to yours but it's pretty much the same code. Running the WebAPI project and then the client gives me:
[{"ID":0}]
Problems with the POST method
In the case of the POST method, you are declaring a route parameter, but this is never sent as part of the route, it's a POST body:
[HttpPost]
[Route("api/contentfile/{files}")] //{files} here tells routing to look for a parameter in the *Route* e.g api/contentfile/something
public IHttpActionResult Post([FromBody] List<ContentFile> files)
So the simple fix is to remove {files} from the Route template for this one.
Hope this helps.