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?
System.Text.Json.JsonException: ‘The JSON value could not be converted to System.String This issue is more because of the new .NET /ASP.NET Core 3.1 framework serializer which has removed the dependency on JSON.NET and uses it’s own JSON serializer i.e ‘ System.Text.Json ‘.
System.Text.Json doesn’t deserialize non-string values like Int, Boolean and other primitives into string properties. System.Text.Json.JsonException: ‘The JSON value could not be converted to System.String
This issue is more because of the new .NET /ASP.NET Core 3.1 framework serializer which has removed the dependency on JSON.NET and uses its own JSON serializer i.e ‘ System.Text.Json ‘. There are known limitations in the System.Text.Json serializer which are as per specification and design.
System.Text.Json doesn’t deserialize non-string values like Int, Boolean and other primitives into string properties. Any non-string value conversion produce JsonException with the following message, System.Text.Json.JsonException: ‘The JSON value could not be converted to System.Int32.
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 ***
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With