AJAX/Spring MVC: JSON Object rendering in bracket notation in POST request - javascript

Why would a JSON be rendered in bracket notation when bound to a Web Request?
I have an application that makes 2 REST Controller calls in a row.
First, an address is read from a form, serialized, AJAXed to a validation endpoint, and returned to the UI/JS as a Response object payload. This part works fine.
$.ajax({
method: 'POST',
url: '/validate-shipping-address',
data: $shippingInfoForm.serialize()
}
#PostMapping(path = "/validate-shipping-address")
public RestResponse<Address> validateShippingAddress(
#ModelAttribute("shippingForm") ShippingForm shippingForm) {
return validateAddress(shippingForm.getAddress());
}
If the response is successful, that payload (the address) is sent directly into the AJAX call for the second endpoint. This request blows up with a 400 and never enters the actual method.
$.ajax({
method: 'POST',
url: '/shipping-stuff',
data: {
"shippingAddress": validationResponse.payload,
"shipDate": shipDate,
"csrfToken": csrfToken
}
}
#PostMapping(path = "/shipping-stuff")
public RestResponse<?> doShippingStuff(
#RequestParam(name = "shippingAddress") Address shippingAddress,
#RequestParam(name = "shipDate") #DateTimeFormat(pattern = "yyyy-MM-dd") Date shippingDate) {
doStuff(); // Never hit
}
After much analysis, the issue is that Spring MVC cannot deserialize the address passed as an Address object. In the request, I see that the address fields are rendered as address[city], address[state], etc. instead of the standard dot notation address.city, address.state (as the first request has it). If I manually access them via the request using the bracket notation as the param name, it will pull out the value. e.g. request.getParameter("address[city]");.
When I use Chrome dev tools debugger to inspect the response from the first and the object entering the second AJAX call, they look like valid JSON. The Network:Form Data section in Chrome differs though - It shows the dot notation for the first, successful call and the bracket notation for the second, unsuccessful call.
Form Data (first call):
address.firstName=John&address.lastName=Doe&address.addressLine1=123+Main+St&address.city=New+York+City&address.state=NY&address.postalCode=12345&csrfToken=XXXX
Form Data (second call): (%5B = '[' and %5D = ']')
shippingAddress%5BfirstName%5D=John&shippingAddress%5BlastName%5D=Doe&shippingAddress%5BaddressLine1%5D=123+MAIN+ST&shippingAddress%5Bcity%5D=New+York+City&shippingAddress%5Bstate%5D=NY&shippingAddress%5BpostalCode%5D=12345&shippingAddress%5BzipFour%5D=6789&shipDate=2019-05-25&csrfToken=XXXX
So there really are 2 sub-questions:
(1) Is there something in the JS handling that would cause the address to be passed in the bracket form instead of the normal JSON form? I'd think if I could get around this then Spring MVC should work like normal.
(2) Is there a way to make Spring MVC able to handle this without resorting to JSON.stringify and then parsing with Gson/ObjectMapper directly in the controller code?
I've tried all permutations I can think of involving custom wrapper objects, JSON.stringify, #RequestParam, #ModelAttribute, and bare (no annotations). I also tried stringify-ing the whole AJAX payload and using #RequestBody on a wrapper object in the controller, but then the call fails as the csrfToken is not detected.
I've read through this, this, and this, which informed the attempts above.
For now, I've worked around the issue with JSON.stringify and Gson (option 2 above), but would rather make Spring MVC do the work automatically.
Work around:
$.ajax({
method: 'POST',
url: '/commercial-checkout/shipping-stuff',
data: {
"shippingAddress": JSON.stringify(shippingAddress),
"shipDate": shipDate,
"csrfToken": csrfToken
}
});
#PostMapping(path = "/shipping-stuff")
public RestResponse<?> doShippingStuff( //
#RequestParam(name = "shippingAddress") String shippingAddressJson,
#RequestParam(name = "shipDate") #DateTimeFormat(pattern = "yyyy-MM-dd") Date shipDate) {
Address address = gson.fromJson(shippingAddressJson, AddressImpl.class);
}

As per your comment,
I also tried stringify-ing the whole AJAX payload and using
#RequestBody on a wrapper object in the controller, but then the call
fails as the csrfToken is not detected.
When you use #RequestBody you need to create corresponding POJO object to deserialise your JSON. Also you need to add content-type property in your AJAX to indicate the server that you are sending an JSON.
$.ajax({
method: 'POST',
data: 'json',
content-type: 'application/json',
url: '/commercial-checkout/shipping-stuff',
data: {
"shippingAddress": JSON.stringify(shippingAddress),
"shipDate": shipDate,
"csrfToken": csrfToken
}
});
Add a POJO as I mentioned,
public class AddressPOJO {
shippingAddress,
shipDate,
csrfToken
//getter / setters
}
Modify your controller method,
#PostMapping(path = "/shipping-stuff", consumes = "application/json")
public RestResponse<?> doShippingStuff( #RequestBody AddressPOJO addressPJO) {
// do your logic..
}

Related

How to receive Non-primitive data in WebMethod?

I am trying to pass an array of bytes to my WebMethod through JavaScript.
The problem is that my WebMethod returns with the message "The test form is only available for primitive methods as parameters."
When I change data type to string or any other kind of primitive variables the WebMethod accepts it goes on.
I'm transforming an object into a byte[], named msgpackEnvio using MessagePack(The transformation itself occurs well), and then sending to WebService.
Here's my request to WebService using jquery
$.ajax({
beforeSend: function (request) {
request.setRequestHeader("Content-Type", "application/json");
},
processData: false,
dataType: "json",
url: url,
data: msgpackEnvio,
type: "POST",
error: function (data) {
$('#upload-load').hide();
$('.bt-enviar-excel').removeAttr('disabled', 'disabled');
var msg = document.getElementsByClassName('msg')[0];
msg.style.color = 'red';
msg.innerHTML = 'Erro interno servidor!';
},
success: //some code
In fact, the code above doesn't matters at all, my problem is in the webService, and how to make it receive an array or perhaps an object.
And here's my WebMethod that should be able to receive a byte[]
[WebMethod]
public string TestMessagePack(byte[] name)
{
//my code
return "Finish";
}
You can use base64 encoding and decoding to send byte arrays as strings. Anything more than that and you might want to use post requests and JSON serialization/deserialization instead. There's not going to be a generic one size fits all conversion between complex C# objects and javascript objects which is why only primitive parameters are allowed, so for nonprimitive things you'll need a translation layer. That's normally going to mean some form of string serialization and deserialization.

How can I get a Spring #RestController to accept parameters in JSON format rather than www-form-urlencoded?

Using Spring 4.1.7 on JDK 1.8, I have an #RestController class that looks like this:
#RestController
public class ServiceAController {
public static final Logger LOG = Logger.getLogger(ServiceAController.class);
#RequestMapping(value="/rest/servicea", method=RequestMethod.POST)
public ServiceAResponse serviceA(#RequestParam(value="parmA", defaultValue="defaultParmA") String parmA,
#RequestParam(value="parmB", defaultValue="defaultParmB") String parmB,
#RequestParam(value="parmC", defaultValue="defaulParmC") String parmC) {
LOG.info("Inside Service A handler: " + parmA + " B: "+ parmB + " C: "+ parmC);
}
When I send a POST to /rest/servicea from a javascript like this, everything works, and I see the values "a", "b", and "c" printed in my log:
var data = {
"parmA": "a",
"parmB": "b",
"parmC": "c"
}
$.ajax({
type: "POST",
url: "./rest/servicea",
contentType: "application/x-www-form-urlencoded",
data: data,
dataType: "json",
success: submitAuthSuccess,
error: submitAuthFailure
})
However, when I try to change the call from the javascript to this (to change the protocol to REST rather than www-urlencode), I get the default values (defaultParmA, defaultParmB, defaultParmC) in my log:
var data = {
"parmA": "a",
"parmB": "b",
"parmC": "c"
}
$.ajax({
type: "POST",
url: "./rest/servicea",
contentType: "application/json",
data: JSON.stringify(data),
dataType: "json",
success: submitAuthSuccess,
error: submitAuthFailure
})
I think I'm missing something in the #RestController class to get it to parse the JSON rather than expecting www-urlencoded data.
I tried changing the #RequestMapping annotation on the serviceA method to add the consumes="application/json" attribute, but that had no effect.
What can I change to make this work, using JSON rather than urlencoded data in the POST body?
The #RequestParam javadoc states
Annotation which indicates that a method parameter should be bound to
a web request parameter.
This is retrieved through the various ServletRequest methods for request parameters. For example, ServletRequest#getParameterMap() which states
Request parameters are extra information sent with the request. For
HTTP servlets, parameters are contained in the query string or posted
form data.
In your second snippet, you aren't sending either. You're sending JSON in the request body.
Spring has a mechanism (it has many, and custom ones) for deserializing that into the data you expect. The standard solution is #RequestBody, assuming you have an appropriately registered HttpMessageConverter that can handle the JSON. Spring automatically registers MappingJackson2HttpMessageConverter if you have Jackson 2 on the classpath, which can correctly deserialize JSON into Java POJO types.
The documentation gives a number of examples and explains how you would use it. With JSON, you could define a POJO type with fields that correspond to the ones you send
class RequestPojo {
private String paramA;
private String paramB;
private String paramC;
// and corresponding getters and setters
}
and add a #RequestBody annotated parameter of this type to your handler method
public ServiceAResponse serviceA(#RequestBody RequestPojo pojo) {
pojo.getParamB(); // do something
return ...;
}
Jackson lets you define, through annotations or through configuration of the ObjectMapper, how to deal with absent values.
#RestController is not involved here. As its javadoc states,
Types that carry this annotation are treated as controllers where
#RequestMapping methods assume #ResponseBody semantics by default.
It looks to me like you are passing a single JSON object at this point, rather than a set of parameters, hence Spring cannot match what you're sending to any of the parameters you've provided.
Try
#RequestBody List<ListUnit> listOfUnits
instead of #RequestParam

MVC Controller Property Doesn't Recognize Parameter's Collection

I'm calling an MVC controller method and passing it an Entity object. Among other properties, Entity also has a Contacts property. With this approach, the controller gets the entity, and the right number of contacts within the entities, but all of the contacts' properties are null.
This is the original approach:
$.post('/Home/Save', $.param(entity), SaveComplete);
With the strongly-typed controller:
public ActionResult Save(Entity entity)
This causes each Entity.Contact to have null properties:
And Fiddler shows that the contacts are passed to the server.
To get the controller to recognize the Contacts property of Entity, I have to do this:
JavaScript:
$.post('/Home/Save', { entityAsJson: JSON.stringify(entity) }, SaveComplete);
Controller method:
public ActionResult Save(string entityAsJson)
{
try
{
Entity entity = JsonConvert.DeserializeObject<Entity>(entityAsJson);
// more code here
}
}
That's unfortunate, because now my controller takes a string instead of a strongly-typed entity. Is there a way to get the first option to work, or do I need to stringify the JSON?
In order to use strongly typed parameters in your controller actions and send data with ajax which gets bound correctly (including collection properties) you need to do the following things:
use the $.ajax instead of $.post because $.post does not allow to configure the contentType option
you need to send JSON. So you need to JSON.stringfy your data
set the contentType to "application/json" because this tells ASP.NET MVC to use the correct model binder for JSON
So the following code should work with your original controller action:
$.ajax({
url: '/Home/Save',
type: 'POST',
data: JSON.stringify(entity),
contentType: "application/json",
success: SaveComplete
});
In your case i don't know why are u making javascript post. If it's strongly type binding then just do the following:
(1) Wrap your controls, submit button under using block (Form).
(2) Call your action which should have your class name as an parameter.
e.g:
Public Action Result (YourClassName model)
{
Your sample Code
}
in model you will get whatever u have filled on your view...

Web Service method name is not valid

I get the following error "Web Service method name is not valid" when i try to call webmethod from javascript
System.InvalidOperationException: SaveBOAT Web Service method name is not valid.
at System.Web.Services.Protocols.HttpServerProtocol.Initialize()
at System.Web.Services.Protocols.ServerProtocol.SetContext(Type type, HttpContext context, HttpRequest request, HttpResponse response)
at System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing)
HTML Code :
<asp:LinkButton runat="server" ID="lnkAddBoat" OnClientClick="javascript:AddMyBoat(); return false;"></asp:LinkButton>
JS Code :
function AddMyBoat() {
var b = document.getElementById('HdnControlId').value;
jQuery.ajax({
type: "GET",
url: "/AllService.asmx/SaveBOAT",
data: { Pid: b },
contentType: "application/text",
dataType: "text",
success: function(dd) {
alert('Success' + dd);
},
error: function(dd) {
alert('There is error' + dd.responseText);
}
});
}
C# Code (Web method in AllService.asmx file)
[WebMethod]
public static string SaveBOAT(int Pid)
{
// My Code is here
//I can put anythng here
SessionManager.MemberID = Pid;
return "";
}
I tried all solutions found on Stack Overflow and ASP.NET site.but none of them worked for me.
It was a silly mistake.
remove Static keyword from method declaration.
[WebMethod]
public string SaveBOAT(string Pid)
{
SessionManager.MemberID = Pid;
return "";
}
In my case I had copied another asmx file, but not changed the class property to the name of the new class in the asmx file itself (Right click on asmx file -> View Markup)
In my case the error was that the Web Service method was declared "private" instead of "public"
Try using this, I think datatype should be JSON
jQuery.ajax({
type: "POST", // or GET
url: "/AllService.asmx/SaveBOAT",
data: { Pid: b },
contentType: "application/json; charset=utf-8",
dataType: "json"
success: function(dd) {
alert('Success' + dd);
},
error: function(dd) {
alert('There is error' + dd.responseText);
}
});
And in C# Code change Pid to string
[WebMethod]
public static string SaveBOAT(string Pid)
{
SessionManager.MemberID = Pid;
return "";
}
I too faced the similar issue. The solution includes checking everything related to ensuring all name, parameters are passed correctly as many have responded. Make sure that the web method name that we are calling in UI page is spelled correctly, the data, data types are correct and etc. In my case, I misspelled the web method name in my ajax call. It works fine once I found and corrected the name correctly.
For Ex: In .asmx class file, this is the method name "IsLeaseMentorExistWithTheSameName" but when I called from UI this is how I called:
var varURL = <%=Page.ResolveUrl("~/Main/BuildCriteria.asmx") %> + '/IsLeaseMentorExistWithSameName';
Notice that the word "The" is missing. That was a mistake and I corrected and so it worked fine.
As Sundar Rajan states, check the parameters are also correct. My instance of this error was because I had failed to pass any parameters (as a body in a POST request) and the asmx web method was expecting a named parameter, because of this the binding logic failed to match up the request to the method name, even though the name itself is actually correct.
[WebMethod]
public object MyWebMethod(object parameter)
If there is no parameter in the body of the request then you will get this error.
Did U add ServiceReference Class. Check this once. Based on your comment I can tell what to do
I had this issue because my soap method had a List<string> parameter. Couldn't figure out a way to make it work with the array parameter; so just converted the parameter to a &-delimited string (e.g. val1&val2&val3) and converted the parameter to an array in the service method.
In my case, one of the WebService receiving parameters was called aId. When I called it from javascript, I was sending the correct Id value, but the name of the sent variable was incorrectly called bId. So I just had to rename the WebService call, keep the correct value like before, and just change the variable name.

Getting JSON Object Result from $.post()

I'm attempting to call a web service via AJAX in a WebForms application.
My script looks something like this:
$.post('UpdateServer.asmx/ProcessItem',
'itemId=' + $(this).text(),
function (result) {
alert(result);
});
My web service looks something like this.
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class UpdateServer : System.Web.Services.WebService
{
[WebMethod]
public string ProcessItem(int itemId)
{
return new JavaScriptSerializer().Serialize(
new { Success = true, Message = "Here I am!" });
}
}
The web method is called as expected and with the expected argument. However, the argument passed to my success function (last parameter to $.post()) is of type document and does not contain the Success and Message members that I'm expecting.
What's are the magic words so that I can get back the object I'm expecting?
EDIT
On closer inspection, I can find the data I'm looking for as follows:
result.childNodes[0].childNodes[0].data:
"{"Success":true,"Message":"Server successfully updated!"}"
The reason you're seeing that odd structure of nodes that end with JSON is because you're not calling the service the necessary way to coax JSON out of ASMX ScriptServices and then returning a JSON string anyway. So, the end result is that you're returning an XML document that contains a single value of that JSON string.
The two specific problems you're running into right now are that you're manually JSON serializing your return value and you're not calling the service with a Content-Type of application/json (.NET needs that to switch to JSON serializing the response).
Once you fixed those issues, you'd also run into an "invalid JSON primitive" error due to the data parameter being URL encoded instead of a valid JSON string.
To get it working, do this on the server-side:
[ScriptService]
public class UpdateServer : System.Web.Services.WebService
{
[WebMethod]
public object ProcessItem(int itemId)
{
return new { Success = true, Message = "Here I am!" };
}
}
You could also create a data transfer object (aka ViewModel) to return instead of using an anonymous type and object, if you want.
To successfully get raw JSON out of that, do this on the client-side:
$.ajax({
url: 'UpdateServer.asmx/ProcessItem',
type: 'post',
contentType: 'application/json',
data: '{"itemId":' + $(this).text() + '}',
success: function(result) {
// This will be { d: { Success: true, Message: "Here I am!" } }.
console.log(result);
}
});
If you have a few minutes, read through the posts in the communication section of jQuery for the ASP.NET developer. You'll find a lot of that information helpful as you continue down this path.
Note: The links that helmus left were relevant. Nothing has fundamentally changed between 2.0 and present with regards to using ASMX ScriptServices to communicate via JSON. If you're interested in the truly cutting edge approach to this problem in .NET, ASP.NET Web API is the way to go.
Add this attribute to your ProcessItem method:
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
Be more explicit in your $.post call.
$.ajax({
type:'post',
url:'UpdateServer.asmx/ProcessItem',
data: {'itemId':$(this).text()}
}).done(function (result) {
alert(result);
});

Categories