this is i am trying to do but getting this error
Unexpected character encountered while parsing value
var searchModel = Newtonsoft.Json.JsonConvert.DeserializeObject<EmployeeSearchModel>(filter);
Model
public class EmployeeSearchModel
{
public string EmployeeNameSearch { get; set; } = null;
public string SearchFilter { get; set; } = null;
}
Error Detail
I suspect filter is not a valid JSON.
In fact, the exact error can be reproduced by the following code:
Newtonsoft.Json.JsonConvert.DeserializeObject("a");
//Error: Unexpected character encountered while parsing value: a. Path '', line 0, position 0.
coming string in filter variable
I believe the translation of what you said should be "The incoming filter variable is a string".
When receiving this error it can also mean your controller's action method has not been setup to take in a specific class to deserialize to.
For example this will fail with your error message:
public IActionResult Post([FromBody] string filter)
{
var searchModel = Newtonsoft.Json.JsonConvert.DeserializeObject<EmployeeSearchModel>(filter);
But the following will succeed because we have identified a specific object to deserialize and do not have to call Newtonsoft because .Net Core has deserialized it for us:
public IActionResult Post([FromBody] EmployeeSearchModel searchModel)
{
If (searchModel.EmployeeNameSearch == "OmegaMan")
...
So make sure your JSON incoming body is the same as the class.
Related
My .NET Core 3.1 WebApi controller PUT action is found and execute even if it should not. I have request data object with 2 properties in [FromBody] parameter. But if I call this route with absolutely different properties in the request Body, or no properties at all - it stil seems to be OK and Action is executed, just properties have default values for its types. I expected 400 Bad Request.
public class UpdateLogRequestData
{
[Required]
public int Records { get; set; }
[Required]
public int LogStatusId { get; set; }
}
Controller Action:
[HttpPut]
[Route("{logId:int}")]
public IActionResult UpdateLog(
[FromRoute] int logId,
[FromBody] UpdateLogRequestData requestData)
{
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
...
}
I tried to add [Required] attributes and ModelState validation later when I noticed the Action is executed even by "bad request" - ie request with incorrectly named properties. Properties in UpdateLogRequestData just have 0 as their values (int default value).
It is dangerous behavior since I update records in the DB. And now, if someone sends the request without Records and LogStatusId properties, database will be updated with zeroes.
Why controller doesn't check it? It's the first time I see something like this. Why no Bad Request happens in that case?
So after deep digging in the Microsoft documentation I have found that validation in Web API has been changed since version .NET Core 3. For those with the same problem here is how ti works.
Web API controllers don't have to check ModelState.IsValid if they
have the [ApiController] attribute. In that case, an automatic HTTP
400 response containing error details is returned when model state is
invalid. For more information, see Automatic HTTP 400 responses.
[Required] validation on the server. On the server, a required value is
considered missing if the property is null. A non-nullable field is
always valid, and the [Required] attribute's error message is never
displayed.
However, model binding for a non-nullable property may fail, resulting
in an error message such as The value '' is invalid. To specify a
custom error message for server-side validation of non-nullable types,
you have the following options:
Make the field nullable (for example, decimal? instead of decimal).
Nullable value types are treated like standard nullable types.
OR
Specify the default error message to be used by model binding, as
shown in the following example:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.MaxModelValidationErrors = 50;
options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
_ => "The field is required.");
});
services.AddSingleton<IValidationAttributeAdapterProvider,
CustomValidationAttributeAdapterProvider>();
See the line
options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
_ => "The field is required.");
Make those value types nullable so that they do not default to the non-null default values when omitted in the request body.
public class UpdateLogRequestData {
[Required]
public int? Records { get; set; }
[Required]
public int? LogStatusId { get; set; }
}
We are trying to resolve issues related to a security scan. It is considered a vulnerability to expose any information about underlying classes. The scanner is sending invalid data to this endpoint:
#PostMapping(value = "/accountKey", params = "update")
public String accountKeyUpdate(#Valid #ModelAttribute("accountKeyForm") AccountKeyForm key, BindingResult bindingResult, Authentication authentication)
The invalid input looks like this, where "description" is a valid key in the entity, but adding "[]" to the end of the property name in the POST data is causing the parsing error:
description[]:
The server returns the following:
{
"timestamp": "2018-04-20T14:28:36.653Z",
"status": 500,
"error": "Internal Server Error",
"message": "Invalid property 'description[]' of bean class
[com.imsweb.seerapi.account.AccountKeyForm]: Property referenced in indexed property path 'description[]' is neither an array nor a List nor a Map; returned value was []",
"path": "/accountKey/"
}
This is what appears in the log:
org.springframework.beans.InvalidPropertyException: Invalid property 'description[]' of bean class [com.imsweb.seerapi.account.AccountKeyForm]: Property referenced in indexed property path 'description[]' is neither an array nor a List nor a Map; returned value was []
at org.springframework.beans.AbstractNestablePropertyAccessor.processKeyedProperty(AbstractNestablePropertyAccessor.java:375) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:275) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:266) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:97) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:839) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.validation.DataBinder.doBind(DataBinder.java:735) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:197) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.bind.ServletRequestDataBinder.bind(ServletRequestDataBinder.java:107) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.bindRequestParameters(ServletModelAttributeMethodProcessor.java:157) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:153) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:124) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:131) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
The issue is that I cannot find a way to gracefully handle the invalid input. It looks like it happens when the #ModelAttribute is converting the POST body into an AccountKeyForm. That is before it gets inside the controller method. I would prefer to handle the error and just forward them to another page. Alternatively if the message said
"message": "Invalid property 'description[]'"
That would be fine as well.
UPDATE:
I can trap that specific exception using an #ExceptionHandler:
#ControllerAdvice
public class WebControllerAdvice {
#ExceptionHandler(InvalidPropertyException.class)
public String handleBadPropertyException() {
return "error";
}
}
That means I will just get a generic message. That will not pick up other types of exceptions that may fall through the cracks. Is there a better way?
UPDATE:
Here is the entity class. It is a simple bean with two properties.
public class AccountKeyForm {
private String _apiKey;
private String _description;
public AccountKeyForm() {
}
public AccountKeyForm(String apiKey) {
_apiKey = apiKey;
}
public AccountKeyForm(String apiKey, String description) {
_apiKey = apiKey;
_description = description;
}
public String getApiKey() {
return _apiKey;
}
public void setApiKey(String apiKey) {
_apiKey = apiKey;
}
#Size(max = 256)
public String getDescription() {
return _description;
}
public void setDescription(String description) {
_description = description;
}
}
The solution for this is indeed to wrap the exception using the ControllerAdvice, but you need to tweak the response to your needs.
So, instead of returning a String, you should return a full ResponseEntity with a httpStatus and body. The body should be populated with an ErrorResponse where you can define your domain error code if you have something like that and your custom message.
Something like the code below should work.
#ControllerAdvice
public class WebControllerAdvice {
#ExceptionHandler(InvalidPropertyException.class)
public ResponseEntity<ErrorResponse> handle(InvalidPropertyException e) {
return ResponseEntity.status(httpStatus)
.body(new ErrorResponse(errorCode, message));
}
}
public class ErrorResponse {
private final String code;
private final String message;
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
}
}
It's clearing saying the following
Property referenced in indexed property path 'description[]' is neither an array nor a List nor a Map; returned value was []
Which means that the description field which is being sent from the request is of type array/List/map, so accordingly,you have to change the Model class AccountKeyForm description
from private String _description; to private List<String> _description; or
private Map<String> _description; you will need to figure out what collection type is being sent :)
or you'll have to modify how the request is being sent and ensure that it send only String type and not of List/Map type
The former is an easier solution.
Hope it helps :)
I have an example controller:
[RoutePrefix("api/Example")]
public class ExampleController : ApiController
{
[Route("Foo")]
[HttpGet]
public string Foo([FromUri] string startDate)
{
return "This is working";
}
[Route("Bar")]
[HttpPost]
public string Bar([FromBody] DateTime startDate)
{
return "This is not working";
}
}
When I issue a GET request to: http://localhost:53456/api/Example/Foo?startDate=2016-01-01 it works.
When I POST to http://localhost:53456/api/Example/Bar I receive a HTTP/1.1 400 Bad Request error.
This is my POST data:
{
"startDate":"2016-01-01T00:00:00.0000000-00:00"
}
What am I doing wrong?
You can't post non-objects directly, you need to wrap them in side an object container when using FromBody.
[RoutePrefix("api/Example")]
public class ExampleController : ApiController
{
[Route("Foo")]
[HttpGet]
public string Foo([FromUri] string startDate)
{
return "This is working";
}
[Route("Bar")]
[HttpPost]
public string Bar([FromBody] BarData data)
{
return "This is not working";
}
}
public class BarData{
public DateTime startDate {get;set;}
}
The other way it could work is if you form-encode the value like this using the = symbol (note you are sending it as a non-object, the curly braces have been removed).
"=2016-01-01T00:00:00.0000000-00:00"
Try just POSTing:
{
"2016-01-01T00:00:00.0000000-00:00"
}
Specifying the property name would mean your endpoint would need to accept an object with a property named startDate. In this case you only want to pass a DateTime.
The submitted format of the date is important, and depends on your client library. It must look like this (quotes in the raw body payload):
"2015-05-02T00:00:00"
No braces, no property names. The format that's transmitted from your code and/or client library is going to depend on whether you're sending a javascript date, or a string representation of one. So, tweak the submissions code appropriately...
I have this Object
public class Deportista implements Serializable {
private static final long serialVersionUID = 6229604242306465153L;
private String id;
...
#NotNull(message="{field.null}")
public String getId() {
return id;
}
...
}
I have the following Controller's methods
#InitBinder(value="deportistaRegistrar")
public void registrarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> registrarInitBinder >>>>>>>>>>>>>");
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.GET)
public String crearRegistrarFormulario(Model model){
logger.info("crearRegistrarFormulario GET");
Deportista deportista = new Deportista();
model.addAttribute("deportistaRegistrar", deportista);
return "deportista.formulario.registro";
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.POST)
public String registrarPerson(#Validated #ModelAttribute("deportistaRegistrar") Deportista deportista,
BindingResult result){
logger.info("registrarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.registro";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.insertDeportista(deportista);
return "redirect:/manolo.htm";
}
Until here the Controller is able to create a form (GET) and submit (POST) a new command object, Validation code works well.
The problem is with the update.
I have the following:
#InitBinder(value="deportistaActualizar")
public void actualizarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> actualizarInitBinder >>>>>>>>>>>>>");
binder.setDisallowedFields("id");
}
Observe I have binder.setDisallowedFields("id")
public String crearActualizarFormulario(#PathVariable("id") String id, Model model){
logger.info("crearActualizarFormulario GET");
Deportista deportista = this.fakeMultipleRepository.findDeportista(id);
model.addAttribute("deportistaActualizar", deportista);
return "deportista.formulario.actualizacion";
}
#RequestMapping(value="/{id}/actualizar.htm", method=RequestMethod.POST)
public String actualizarPerson(#Validated #ModelAttribute("deportistaActualizar") Deportista deportista,
BindingResult result){
logger.info("actualizarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.actualizacion";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.updateDeportista(deportista);
return "redirect:/manolo.htm";
}
The problem is:
when the form or command has any error, the controller re-render the view and the form appear showing the error messages how is expected, but without the ID value
or
if I try to update the object, of course keeping the id value, and without any error to simply proceed to update, it fails
The following appears in the Console:
- -------- createCollections ---------------
- >>>>>>>> actualizarInitBinder >>>>>>>>>>>>>
- Skipping URI variable 'id' since the request contains a bind value with the same name.
- actualizarPerson POST
- Deportista [id=null, nombre=Manuel, ...]
- There are errors!!!!
- Error Field error in object 'deportistaActualizar' on field 'id': rejected value [null]; codes [NotNull.deportistaActualizar.id,NotNull.id,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [deportistaActualizar.id,id]; arguments []; default message [id]]; default message [The field must be not empty]
The id is null. How I can around this problem keeping the Request Scope?
I have an alternate controller which is working with #SessionAttributes and all works perfect. But since is a huge risk if the user has many tabs open in the same web browser, one for create and other for updating, all is going to be very wrong. According with Spring MVC + Session attributes and multiple tabs, request scope should be used instead of session scope. It has sense.
Sadly seems Spring is not going to fix this:
#SessionAttributes doesn't work with tabbed browsing
Addition
According with your suggestion, I have the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#RequestParam(defaultValue="") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Observe the method uses #RequestParam, my problem is how update that method to work when the URL to update has the following style
http://localhost:8080/spring-utility/deportista/1/actualizar.htm. There is no param in the URL, therefore #RequestParam is useless now.
I already have read the Spring Reference documentation:
Using #ModelAttribute on a method
Second Addition
Yes, you was right, and I did that yesterday, but I forget to share the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#PathVariable(value="id") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Since a #ModelAttribute is called always before by any handler method, the following URL fails http://localhost:8080/spring-utility/deportista/registrar.htm, the following appears on the page
HTTP Status 400 -
type Status report
message
description The request sent by the client was syntactically incorrect.
Of course because the URL does not contains the expected id. Therefore I can't create new records to later edit/see.
I can confirm, that for the following work:
http://localhost:8080/spring-utility/deportista/1/detalle.htm
http://localhost:8080/spring-utility/deportista/1/actualizar.htm
the id (1) is retrieved.
How I could resolve this?
Thank You
I have the following method skeleton in a Spring MVC application:
#RequestMapping(value = "/activateMember/{token}", method = RequestMethod.GET, produces = "text/html")
public String activateMember(#PathVariable("token") String token) {
...
}
I am trying to display an error message if the token is invalid for some reason. However I have no ModelAttribute in the method arguments and I don't really want one. But of course I can't use an Errors or BindingResults argument because of the absence of a ModelAttribute and its corresponding form.
So my question is:
what is the recommended way to display an error message given the above method signature and without introducing a ModelAttribute?
If the String you've returned from the method is a viewname (Spring default) then simply create a view for this case and do like:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
if(checkToken(token)){
doProcess();
return "userprofile";
} else {
return "badtoken"
}
}
In more complicated case you may have a hierarchy of exceptions, related to bad tokens. (Token is expired, token is just incorrect and so on). You can register an #ExceptionHandler in the same controller:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
return activate(token); // This method may throw TokenException and subclasses.
}
#ExceptionHandler(TokenException.class)
public ModelAndView tokenException(TokenException e){
// some code
return new ModelAndView("badtoken", "exception", e);
}