Is there a way to override the serialization error message? - c#

I have a simple class for the POST request body:
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpPost]
public IActionResult Post(PostRequest request)
{
return Ok();
}
}
public class PostRequest
{
[Required]
public String String { get; set; }
}
If I send an int instead of a string, the following error message is returned:
"$.string": [
"The JSON value could not be converted to System.String. Path: $.string | LineNumber: 1 | BytePositionInLine: 13."]
}
I don't feel like exposing what happens under the hood is right. Is there a way to return a generic "The field String is invalid" message?

This error comes from System.Text.Json.JsonSerializer inside SystemTextJsonInputFormatter durring desrializing request body into the model and you can disable it like below:
builder.Services.AddControllers().AddJsonOptions(options =>
options.AllowInputFormatterExceptionMessages = false);
And you will relieve error message like this:
{
"$.string": [
"The input was not valid."
]
}

I'm not sure, but maybe this will help you. Paste this code at the very beginning of the method:
if (!ModelState.IsValid)
{
return BadRequest();
}

Related

the values field is required

Detail: I am trying to set up a simple get/post method inside asp.net controller and using postman to set if its set up correctly. I have looked for similar question on stackoverflow and they did not fixed my issue
Error: My Get method works fine but my post method giving an following error. Please see below postman:
debug: if I add a break line inside post method. it is never reaching to that break line
Code in asp.net
[ApiController]
[Route("[controller]")]
public class CoursesTakenController : Controller
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] {"value", "value" }
}
[HttpPost]
public Task<ActionResult<string>> Post([FromBody] string values)
{
return Ok(values);
}
}
I also tried this: but doesnt work
[HttpPost]
public async Task<IActionResult> Post([FromBody] string values)
{
return Ok();
}
You are posting a json object while the method expects a string. System.Text.Json (default json serializer used by ASP.NET Core) is pretty restrictive in JsonTokenType handling, so either post a string i.e. just "RandomText" (or encoded into string json object - {\"values\":\"asdasdasd\"}) or change action to accept an object:
public class Tmp
{
public required string Values { get; set; }
}
[HttpPost]
public async Task<ActionResult<string>> Post([FromBody] Tmp values)
{
...
}

How to Handle C# Data Type Model Validation before get to controller

I have this request model in C# WebAPI
public class RequestModel{
public int Code { set; get; }
}
then I have API Controller that receive that model as its request body (POST). The problem is, if i run a negative case for testing purpose. I request some json like this
{
"Code": "abcde"
}
My request model automatically set its Code value to 0. And 0 is a valid value
Is there anyway to handle this case? so I can return some warning / error before it get processed in my controller?
Thanks
The reason why you are getting 0 is because string can't be mapped to int, so it just leaves a default value (which is zero). To validate users input you can use attributes.
Try something like this:
public class RequestModel
{
[Range(1, int.MaxValue, ErrorMessage = "*Your warning*")]
public int Code { set; get; }
}
Than in your controller you can check if model state is valid and respond properly. Basically in the beginning of your method in controller you should write something like this:
if(!ModelState.IsValid)
{
// Return errors for example
}
else
{
// Something else
}
There is a more elegant way to do such kind of validation. You can simply make your field nullable (int?) and delegate all validation work to fluent validator. It will run the validation before you'll get in controller and automatically return BadRequest if something goes wrong. Example of fluent validator for your model:
public class RequestModelValidator : AbstractValidator<RequestModel>
{
public RequestModelValidator()
{
RuleFor(m => m.Code).NotNull().WithMessage("*Your msg*");
}
}
Change your model to this:
public class RequestModel
{
[Required]
public int? Code { set; get; }
}
Code will default to null when a value that can't be parsed is received. If you perform model validation it will make sure that Code is not null.
If you send a string, in a property that should naturally receive an integer, the complete object will arrive null in its controller, there will be no conversion of the string to zero, as you mentioned.
A practical example:
Class model:
public class RequestModel
{
public int Code { set; get; }
}
Your controller:
using Microsoft.AspNetCore.Mvc;
public class Test: Controller
{
[HttpPost("/test")]
public IActionResult Test([FromBody] RequestModel requestModel)
{
if (requestModel == null)
{
return BadRequest("Invalid request");
}
return Ok(requestModel);
}
}

“The JSON value could not be converted to System.String” when attempting to call controller endpoint

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 ***
}
}
}
}

How can I convert an array in JSON to a data type that my controller can handle

I have an API that is using .NET Core 3.1 Entity Framework.
I am getting data from a client side POST event that contains JSON with an array that looks like this:
"SpaceTrainees": [
"Pilot",
"Commander",
"Grunt"
]
My Controller that is handling the post event is throwing an error when it hits that array:
I am getting this error:
"$.SpaceTrainees[0]": [
"The JSON value could not be converted to System.Collections.Generic.List`1[System.Int64]. Path: $.SpaceTrainees[0] | LineNumber: 2 | BytePositionInLine: 21."
The code block in my controller that throws the error:
[HttpPost]
public async Task<ActionResult> ProcessRecruit([FromBody] CreateCadet data)
{
...
foreach (var traineeId in data.SpaceTrainees)
{ ... }
Return Ok();
}
--> where data.SpaceTrainees is of List
The model for CreateCadet contains a property:
public List<long> SpaceTrainees {get; set; }
Is there a way to make this work?
Thanks!
long is a number, SpaceTrainees is string[]
public List<string> SpaceTrainees {get; set; }
will work

Web API POST object always null

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.

Categories