ASP.Net Web API Http routing and non JSON responses - c#

I want to mimic behaviour of existing web service. Here is a very simplified example showing what I want to achieve.
I use ASP.Net Web API routing: it's quite simple to configure routes with it.
Requirements, part 1: query:
GET whatever.../Person/1
shall return JSON:
Content-Type: application/json; charset=utf-8
{"id":1,"name":"Mike"}
That's piece of cake:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
}
// In ApiController
[HttpGet]
[Route("Person/{id}")]
public Person GetPerson(int id)
{
return new Person
{
ID = id,
Name = "Mike"
};
}
Requirements, part 2: query:
GET whatever.../Person/1?callback=functionName
shall return javascript:
Content-Type: text/plain; charset=utf-8
functionName({"id":1,"name":"Mike"});
Any ideas how to achieve this (part 2)?

The ApiController would need to be modified to satisfy the desired behavior
Simple example based on provided code
//GET whatever.../Person/1
//GET whatever.../Person/1?callback=functionName
[HttpGet]
[Route("Person/{id:int}")]
public IHttpActionResult GetPerson(int id, string callback = null) {
var person = new Person {
ID = id,
Name = "Mike"
};
if (callback == null) {
return Ok(person); // {"id":1,"name":"Mike"}
}
var response = new HttpResponseMessage(HttpStatusCode.OK);
var json = JsonConvert.SerializeObject(person);
//functionName({"id":1,"name":"Mike"});
var javascript = string.Format("{0}({1});", callback, json);
response.Content = new StringContent(javascript, Encoding.UTF8, "text/plain");
return ResponseMessage(response);
}
Of course you would need to do proper validation on the call back as this currently open up the API for script injection.

Related

C# .NET Core 3.1 Web API Post parameter is Null

I am trying to make a post request from WPF to Web API using the following code but the request parameter is always null.
Request Model
public class Document
{
public string FileName { get; set; }
public byte[] Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
WPF Client
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"C:\Users\john.doe\Downloads\I Love Coding.pdf.pdf")
}
}
};
using (var http = new HttpClient())
{
var encodedJson = JsonConvert.SerializeObject(obj);
var conent = new StringContent(encodedJson, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.PostAsync("https://my-app.com/api/upload", conent);
response.EnsureSuccessStatusCode();
}
Web API
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromBody] Request request)
{
// request is always null when app is running in production
// https://my-app.com/api/upload
//request is not null when running on https://localhost:8080/api/upload
}
}
Please what am I missing in the above implementation?
The request parameter is not null on localhost but always null in production.
Please what am I missing in the above implementation? The request
parameter is not null on localhost but always null in production.
Well, not sure how are getting data on local server becuse, you are sending MultipartFormData means your POCO object and file buffer. As you may know we can send json object in FromBody but not the files as json. Thus, I am not sure how it working in local and getting null data is logical in IIS Or Azure.
what am I missing in the above implementation?
As explained above, for sending both POCO object and Files as byte or steam we need to use FromForm and beside that, we need to bind our request object as MultipartFormDataContent to resolve your null data on your UploadDocumentsAsync API action.
Required Change For Solution:
WPF:
In your WPF http request please update your request code snippet as following:
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"YourFilePath")
}
}
};
var httpClient = new HttpClient
{
BaseAddress = new("https://YourServerURL")
};
var formContent = new MultipartFormDataContent();
formContent.Add(new StringContent(obj.Uploader), "Uploader");
formContent.Add(new StringContent(obj.Documents[0].FileName), "Documents[0].FileName");
formContent.Add(new StreamContent(new MemoryStream(obj.Documents[0].Buffer)), "Documents[0].Buffer", obj.Documents[0].FileName);
var response = await httpClient.PostAsync("/api/upload", formContent);
if (response.IsSuccessStatusCode)
{
var responseFromAzureIIS = await response.Content.ReadAsStringAsync();
}
Note: Class in WPF side would remain same as before. No changes required.
Asp.net Core Web API:
In asp.net core web API side you should use [FromForm] instead of [FromBody]
So your controller Action would as following:
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromForm] Request file)
{
if (file.Documents[0].Buffer == null)
{
return Ok("Null File");
}
return Ok("File Received");
}
}
Note: For remote debugging I have checked the logs and for double check I have used a simple conditionals whether file.Documents[0].Buffer == null. I have tested both in local, IIS and Azure and working accordingly.
Update POCO Class in API Project:
For buffer you have used byte for your WPF project but for Web API project update that to IFormFile instead of byte. It should be as following:
public class Document
{
public string FileName { get; set; }
public IFormFile Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
Output:
If you would like to know more details on it you could check our official document here

How to make a PUT request from ASP.NET core mvc to Web API in asp.net core?

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 :)

API POST call from Console Application

How to do the REST API POST Call from the console Application ?
I want to pass the class from the Console application to the REST API. My below code is working if I have to do the GET call but not for the POST. It is hitting the API but in the Parameter it is not passing anything.
API
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
//public void Post([FromBody]string value)
//{
//}
public void Post([FromBody]Student value)
{
}
}
Console Application
static async Task CallWebAPIAsync()
{
var student = new Student() { Id = 1, Name = "Steve" };
using (var client = new HttpClient())
{
//Send HTTP requests from here.
client.BaseAddress = new Uri("http://localhost:58847/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("api/values", student);
if (response.IsSuccessStatusCode)
{
}
else
{
Console.WriteLine("Internal server Error");
}
}
}
The Same is working if I call from fiddler.
User-Agent: Fiddler
Content-Length: 31
Host: localhost:58847
Content-Type: application/json
Request Body:
{
"Id":"1",
"Name":"Rohit"
}
This is working for me.
public async Task CallWebAPIAsync()
{
var student = "{'Id':'1','Name':'Steve'}";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:58847/");
var response = await client.PostAsync("api/values", new StringContent(student, Encoding.UTF8, "application/json"));
if (response != null)
{
Console.WriteLine(response.ToString());
}
}
You are not serializing the student object.
You can try to send a StringContent
StringContent sc = new StringContent(Student)
HttpResponseMessage response = await client.PostAsJsonAsync("api/values", sc);
if this doesn't work (a long time I used StringContent).
Use NewtonSoft sterilizer
string output = JsonConvert.SerializeObject(product);
HttpResponseMessage response = await client.PostAsJsonAsync("api/values", output);
To be honest I don't know. It seems like your StringContent did not sterilize it to UTF8 which your restful API is trying to do by default. However, your console application should also do that by default.
The issue seemed to be that the restful API could not bind the byte data and therefor not assign the data to your class Student in the restful API.
What you can try to do is add following code before you make your post to API:
var encoding = System.Text.Encoding.Default;
It will tell you what is your default encoding type. It could be that UTF8 is not the default encoding for some reason.

HttpResponseMessage post API json format with C#

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.

Retrieving the response from Rest API

I am working on creating an API that call the other third party API. The third party API is an REST API and returns response in the JSON format when I call it in the web browser
[{"Acc":"IT","Cnt":"023","Year":"16"}]
I am trying to get the same response when I call the third party API from my API.
public IHttpActionResult Get(string acctID)
{
using (var client_EndPoint= new HttpClient())
{
Uri uri_EndPoint = new Uri(BaseURL_EndPoint);
client_EndPoint.BaseAddress = uri;
client_EndPoint.DefaultRequestHeaders.Accept.Clear();
client_EndPoint.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string EndPoint_URL = BaseURL_EndPoint+"api/NameCreation?Account="+acctID;
var response_EndPoint = client_EndPoint.GetAsync(EndPoint_URL).Result;
string responseString = response_EndPoint.Content.ReadAsStringAsync().Result;
return Ok(responseString);
}
}
What I have been doing is getting the response from the third party API in a string. But I am checking if there is a way I can get in the JSON format so I can return them directly. The return type of the get method is IHttpActionResult. If I am returning as string the response looks like
"[{\"Acc\":\"adm\",\"Cnt\":\"001\",\"Year\":\"16\"}]"
Any help is greatly appreciated.
Create a model to hold rest api data
public class Model {
public string Acc { get; set; }
public string Cnt { get; set; }
public string Year { get; set; }
}
Deserialize it from api
var response_EndPoint = await client_EndPoint.GetAsync(EndPoint_URL);
var models = await response_EndPoint.Content.ReadAsAsync<Model[]>();
And then return that
return Ok(models);
Full example
public async Task<IHttpActionResult> Get(string LabName) {
using (var client_EndPoint = new HttpClient()) {
//...other code removed for brevity
var response_EndPoint = await client_EndPoint.GetAsync(EndPoint_URL);
var models = await response_EndPoint.Content.ReadAsAsync<Model[]>();
return Ok(models);
}
}
you can use Newtonsoft.Json ,Just add it from nuget and add this config to webapiconfig:
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
then use
return Json(responseString)

Categories