I'm new with ASP.NET Web API's and I'm trying to write an API method that will send emails.
This is my sendEmail controller:
[Route("sendemail")]
[HttpPost]
public bool SendEmail(EmailObj email)
{
var To = email.sendTo;
var Subject = email.subject;
var Message = email.message;
...
}
However, whenever I test it using postman, the object sent is null.
This is how my json object is built in postman:
{
"subject": "Test Message",
"message": "this is a test",
"sendTo": "sam#test.com"
}
I make sure that the type is marked as JSON on postman and tried formatting it in different ways, but it doesn't seem to work. The API recieves the email object but is always null.
Here's a screenshot of my postman in case I'm missing something.
Any help is appreciated.
Edit: Already tried adding "[FromBody]" and adding "email: {}" to the json but it doesn't work.
You can do 2 things:
public bool SendEmail([FromBody]EmailObj email)
Or change the body of the json object to:
{
"email": {
"subject": "Test Message",
"message": "this is a test",
"sendTo": "sam#test.com"
}
}
I found what was the mistake, I was declaring my email object as 'Serializable' and that was preventing the json object to get any value.
You can compare your controller with below code and you can click this link and see the output.
[RoutePrefix("api/test")]
public class TestController : ApiController
{
[Route("sendemail")]
[HttpPost]
public bool SendEmail(EmailObj email)
{
if(email !=null)
{
return true;
}
return false;
}
}
public class EmailObj
{
public string sendTo { get; set; }
public string subject { get; set; }
public string message { get; set; }
}
This is my postman image that how I have called the post api.
This is my WebApi.config file image.
Related
I've been trying to create a simple API,
I manage to make the Get work just fine but whenever I try to work with Post or Put I can't get it to work.
I'm trying to post/put a JSON and getting it as a string in my controller.
I'm using Postman and Insomnia to test (I precise I turned of SSL verification for both since I run in local).
Here is my controller:
[Route("backoffice/[controller]")]
[ApiController]
public class AddQuestionController : ControllerBase
{
private IQuestionRepository _questionRepository;
public AddQuestionController(IQuestionRepository questionRepository)
{
_questionRepository = questionRepository ?? throw new ArgumentNullException(nameof(questionRepository));
}
[ProducesResponseType((int)System.Net.HttpStatusCode.OK)]
[HttpPost]
public async Task<ActionResult> AddQuestion([FromBody] string question)
{
Question q = JsonConvert.DeserializeObject<Question>(question);
await Task.Run(() => _questionRepository.InsertOne(q));
return Ok();
}
}
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|a0b79872-4e41e975d19e251e.",
"errors": {
"$": [
"The JSON value could not be converted to System.String. Path: $ | LineNumber: 0 | BytePositionInLine: 1."
]
}
}
So then I thought it's because the Json format in postman. But then I tried the text format
and this happened:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.13",
"title": "Unsupported Media Type",
"status": 415,
"traceId": "|a0b79873-4e41e975d19e251e."
}
And every time it doesn't even reach the first line of my controller.
Can someone tell me what I did wrong here? Is it my controller? Is it my way of using Postman?
The model binder is unable to map/bind the sent data to the controller parameters
Your action expects a simple string from the request body
public async Task<ActionResult> AddQuestion([FromBody] string question)
But you sent a complex object
{ "test" : "test" }
You might have gotten a match if the property name(s) had matched
For example
{ "question" : "test" }
Since the model binder will take property names into consideration when matching parameters.
if you want to receive a raw string then you need to send a valid raw JSON string
"{ \"test\": \"test \"}"
That is properly escaped.
Another options is to use a complex object for the parameter
class Question {
public string test { get; set; }
//...other properties
}
that matches the expected data
public async Task<ActionResult> AddQuestion([FromBody] Question question) {
string value = question.test;
//...
}
The model binder will bind the data and pass it to the action parameter(s).
Reference Model Binding in ASP.NET Core
Thanks #Nkosi for identifying the issue and providing the article Model Binding in ASP.NET Core.
Since I spent a lot of time going through outdated examples on how to call an API, here's my code for reference (as of Sep-2020):
On the API project, I used the [BindProperty] attribute on the properties of the model class.
// ASP.NET CORE API - C# model
using Microsoft.AspNetCore.Mvc;
using System;
namespace MyTestAPI.Models
{
public partial class MyTest
{
[BindProperty]
public int TestId { get; set; }
[BindProperty]
public string Message { get; set; }
[BindProperty]
public Guid? CreatedBy { get; set; }
[BindProperty]
public DateTime Timestamp { get; set; }
}
}
On the API controller, the mytest class is automatically deserialized because of the property attributes of the MyTest model class:
// ASP.NET CORE API - C# controller
using Dapper;
using HangVue.API.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MyTestAPI.Models;
namespace HangVue.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpPost]
public void Post([FromBody] MyTest mytest)
{
var parameters = new DynamicParameters();
parameters.Add("#pMessage", mytest.Message, System.Data.DbType.String, System.Data.ParameterDirection.Input);
parameters.Add("#CreatedBy", mytest.CreatedBy.ToString(), System.Data.DbType.String, System.Data.ParameterDirection.Input);
string sql = "[dbo].[uspTest]";
using (var conn = new System.Data.SqlClient.SqlConnection(*** SQL_conn_string_goes_here ***))
{
var affectedRows = conn.Query(sql, parameters, commandType: System.Data.CommandType.StoredProcedure);
}
}
}
}
On the client side, I'm using Xamarin.Forms with RestSharp to invoke my API. The AccessToken is required because I'm using Azure AD B2C authentication.
// Xamarin.Forms - C# Client (iOS + Android)
using Microsoft.Identity.Client;
using RestSharp;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace YourApp.Services
{
public static class TestServices
{
public async static Task<string> NewTest(int iTestId, string sMessage, Guid? gCreatedBy, DateTime dTimestamp)
{
try
{
var mytest = new Models.MyTest
{
TestId = iTestId,
Message = sMessage,
CreatedBy = gCreatedBy,
Timestamp = dTimestamp
};
// Client --- API end-point example: https://yourAPIname.azurewebsites.net/
RestSharp.RestClient client = new RestClient(*** https://Your_API_base_end_point_goes_here ***);
// Request
RestSharp.RestRequest request = new RestSharp.RestRequest("api/test", RestSharp.Method.POST, RestSharp.DataFormat.Json);
request.AddParameter("Authorization", "Bearer " + *** Your_AccessToken_goes_here ***, RestSharp.ParameterType.HttpHeader);
request.AddHeader("Content-Type","application/json; CHARSET=UTF-8");
request.AddHeader("Accept", "application/json");
request.AddJsonBody(mytest);
// Invoke
RestSharp.IRestResponse response = await client.ExecuteAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
*** do something ***
return *** a string *** ;
}
else
{
*** do something ***
return *** a string *** ;
}
}
catch (Exception ex)
{
*** do something ***
}
}
}
}
I'm posting over a message from an Angular application and every time the post body is always coming into the function as null. A console log in the Angular application directly before the post request confirmed that the object did contain information, it seems to get lost somewhere along the way through.
public class SentMessage
{
public string MessageFrom { get; set; }
public string MessageTo { get; set; }
public string Message { get; set; }
}
// Post a new message
[Route("Conversation/AddTo")]
[HttpPost]
public IHttpActionResult AddToConversation(SentMessage message)
{
if (message is null)
{
return NotFound();
}
return Ok("Message Sent");
}
So far I've tried with and without [FromBody] on the method property as suggested in other posts, but past that I'm not sure what's going wrong.
........
Put [FromBody] on the method argument, as you've said, and make sure you're POSTing with the Content-Type of application/json.
Just modify the code
public IHttpActionResult AddToConversation([FromBody]SentMessage message)
{
if (message is null)
{
return NotFound();
}
return Ok("Message Sent");
}
When I have a return type of 'string' in my WebAPI controller, the SuccessStatusCode returns 'OK' in my MVC Controller, but when the return type is of a model named 'USER', I get this Internal Server Error. Here's my code:
WebAPI:
public class UserController : ApiController
{
OnlineCenterEntities db = new OnlineCenterEntities();
public USER GetUserInfo(string userName, string domain)
{
USER userInfo = (from u in db.USERs
where u.USER_NAME.ToUpper() == userName.ToUpper() && u.LDAP_NAME.ToUpper() == domain.ToUpper()
select u).FirstOrDefault();
return userInfo;
}
}
MVC Controller that calls the WebAPI:
public class HomeController : Controller
{
HttpClient client;
string url = "http://localhost:61566/api/user/";
public HomeController()
{
client = new HttpClient();
client.BaseAddress = new Uri(url);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<ActionResult> Index(string userName, string domain)
{
string GetUserInfoURL = String.Format("GetUserInfo?userName={0}&domain={1}", userName, domain);
HttpResponseMessage responseMessage = await client.GetAsync(url+GetUserInfoURL);
if (responseMessage.IsSuccessStatusCode)
{
var responseData = responseMessage.Content.ReadAsStringAsync().Result;
var userInfor = JsonConvert.DeserializeObject<USER>(responseData);
}
return View();
}
USER model:
public partial class USER
{
public int USER_ID { get; set; }
public string USER_NAME { get; set; }
public string FIRST_NAME { get; set; }
public string LAST_NAME { get; set; }
public string LDAP_NAME { get; set; }
public string EMAIL { get; set; }
}
In my WebAPI, if I change the return type from USER to string (and of course, change the return variable type to some string (userInfo.FIRST_NAME)), I get the SuccessStatusCode as 'OK', but as of this code, I get Internal Server Error with StatusCode: 500 (whatever that means). I have tried inserting breakpoint at every possible points, and I know that the api is returning the result fine. I simply don't understand why the following line
HttpResponseMessage responseMessage = await client.GetAsync(url+GetUserInfoURL);
gives InternalServerError error when I have the return type of USER, and return the whole USER model instead of just one string.
Please don't worry about the userName and domain parameters that I'm passing to the controllers, they are working fine!
Typically when this happens, it means it is failing to serialize the response. Once your controller returns a USER instance, somewhere WebAPI has to serialize that into the format requested by the client.
In this case the client requested "application/json". The default JsonMediaTypeFormatter uses JSON.Net to turn your C# object into json for the client. Apparently that serialization step is failing, but the response code doesn't tell you exactly why.
The easiest way to see exactly what is happening is to use a custom MessageHandler which forces the body to buffer earlier so you can see the actual exception. Take a look at this blog post for an example to force it to show you the real failure.
I have a very simple post method:
[HttpPost]
public IActionResult Post(AgreementType model)
{
var ag = _facade.AddOrUpdateAgreement(model);
return Json(ag);
}
and trying to send some test calls against it to see if it is coming through ok. It is not. I have checked the network tab in the browser as well as fiddler and the request definitely looks ok to me. (Content-Type is application/json and the body is there just fine).
I placed a break point inside the server side post method and it is getting to the method and the structure of model is ok just all the strings are null and arrays are empty.
It feels like a serialization issue, looks like I am just getting an empty (new) AgreementType model instead of the one coming up...
Edit: Here is the json and the C# Model:
json:
{
"QuestionCategories": [1],
"Id": 1,
"Name": "Name",
"ShortName": "Short Name"
}
Model:
namespace DTModels.Models
{
public class AgreementType
{
public virtual ICollection<QuestionCategory> QuestionCategories { get; set; }
public AgreementType()
{
QuestionCategories = new HashSet<QuestionCategory>();
}
public int Id { get; set; }
public string Name { get; set; }
public string ShortName { get; set; }
}
}
In your c# object QuestionCategories is a collection of QuestionCategory, but in your json you are sending a collection of int. That will not map. YourJson need to be something like
{
"QuestionCategories": [
{"prop1" : "value",
"prop2": "value"},
{"prop1": "value",
"prop2": "value"}
],
"Id": 1,
"Name": "Name",
"ShortName": "Short Name"
}
Where prop1 and prop2 are properties of QuestionCategory and my example is passing 2 objects in the collection. Also you need to set the content-type in your header to be application/json.
Figured it out. Make sure your content-length is set!
How can we support ajax post?
This the server code:
[RoutePrefix("api/Dashboard")]
public class PatientDashboardController : ApiController
{
[Route("UpdatePatientById")]
[HttpPost]
public IHttpActionResult UpdatePatientById(int? pk, string name, object value )
{
return Ok(name);
}
}
This is what I post to the server
Request URL:http://localhost/mydomain/api/Dashboard/UpdatePatientById
Request Method:POST
name:sex
value:1
pk:1093
I'm using x-editable plugin on the front end, it does the ajax post automatically. I don't think there is anything wrong with the post url.
This the error it gives me:
"No HTTP resource was found that matches the request URI 'http://example.com/mydomain/api/Dashboard/UpdatePatientById'."
MessageDetail: "No action was found on the controller 'Dashboard' that matches the request."
Web API can only receive one parameter from the body so you'll have to specify it as a type that aggregates those fields.
class PatientParameters
{
public int? Pk { get; set; }
public string Name { get; set; }
public object Value { get; set; }
}
and pass that:
public IHttpActionResult UpdatePatientById([FromBody] PatientParameters parameters) { }