passing as parameterJSONArray to Spring Controller - spring-mvc

I have a view form that contains a table with textfield inside it.
I need to save the information of the table inside a database.
* javascript function *
function sendTableToServer()
{
var table;
table = document.getElementById('myTable');
var rowLength = table.rows.length;
var tableArray = [rowLength -1];
var JSONObject;
for (var tblRow = 1; tblRow < rowLength; tblRow++){
console.log("***** ROW CHANGE *****");
JSONObject = {
"rowIndex": "myTableRow" + tblRow,
"partNo": table.rows[tblRow].cells[0].firstChild.value,
"partName":table.rows[tblRow].cells[1].firstChild.value,
};
f24Array[tblRow] = JSONObject;
}
console.log(f24Array[rowLength-1]["rowIndex"]);
console.log(f24Array.length -1);
$.getJSON("f24BatcCreateEAF.do", {
type: "F24",
pRefForm: "DVL",
pF24Values: tableArray
}
, function(ans) {
console.log("The row is" +ans.strVar);
}
);
};
* controller *
public #ResponseBody
AjaxReturnMessage createEaf( Model model, HttpServletRequest pRequest, #RequestParam String type, #RequestBody String[] pF24Values ) throws Exception {
long eafId=0;
AjaxReturnMessage pARM = new AjaxReturnMessage();
log.info( "entering creatEaf );
try {
System.out.println("calling gF24BatchService");
eafId = gF24BatchService.createEAFRow( model, pp, type, pRefForm, pDescription );
pARM.setId( eafId );
pARM.setStrVar( "f24TableRow1" );
pARM.setCode( AjaxReturnMessage.STATUS_ERROR );
} catch ( Exception e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// return the jsp page only
return pARM;
}
When i trigger the sendTableToServer function i get an error "404".
But if i remove the pF24Values from the JSON call and he service then there is no error.
How would it be possible for me to "catch" pF24Values without having to create a new object type. is this possible?
Thanks

You are performing a GET to your controller, so that controller can only capture data via request parameters. In addition, your getJSON function is sending all the data as request parameters as well.
Therefore your controller has to capture pF24Values as a #RequestParam, not as a #RequestBody.
If you want to send complex data structures to your controller you need to do a POST with the JSON as the request body (for instance) - although you can use GET for array data but not the way you are doing it here - each row needs to have a separate request param(e.g. pF24Values:one and pF24Values:two, etc - there are a few ways to do this).

Related

BindingResult.getFieldValue() returning null in test context for a formatted value

In a spring mvc app, I submit id's and use a formatter to convert that id to an object. It works well in the container.
But in the unit test environment, I'm seeing a problem.
I mock the formatter to always return my test value, this is fine it gets injected into the ModelAttribute. But in the BindingResult, a call to result.getFieldValue("location") for example is returning null, but only in the MockMvc context.
This is the test case:
/**
* Tests the inventory update for existing inventory records.
* #throws Exception
*/
#Test
public void testUpdateExistingProductInventory() throws Exception{
logger.entry();
VariantInventory oldInventory = new VariantInventory();
oldInventory.setId(20l);
Product product = ProductBuilder.buildBasicExisting();
Location location = new Location();
location.setId(3l);
ProductVariant variant = new ProductVariant();
variant.setId(2l);
// check the formatter is working
Mockito.when(mockProductFormatter.parse(((String)Mockito.anyObject()), ((Locale)Mockito.anyObject()))).thenReturn(product);
Product p = mockProductFormatter.parse("1", null);
Assert.assertEquals(p, product);
// check the formatter is working
Mockito.when(mockLocationFormatter.parse(((String)Mockito.anyObject()), ((Locale)Mockito.anyObject()))).thenReturn(location);
Location l = mockLocationFormatter.parse("3", null);
Assert.assertEquals(l, location);
// check the formatter is working
Mockito.when(mockVariantFormatter.parse(((String)Mockito.anyObject()), ((Locale)Mockito.anyObject()))).thenReturn(variant);
ProductVariant pv = mockVariantFormatter.parse("2", null);
Assert.assertEquals(pv, variant);
// check the formatter is working
Mockito.when(mockInventoryFormatter.parse(((String)Mockito.anyObject()), ((Locale)Mockito.anyObject()))).thenReturn(oldInventory);
VariantInventory v = mockInventoryFormatter.parse("20", null);
Assert.assertEquals(v, oldInventory);
this.mockMvc.perform(MockMvcRequestBuilders.post("/ajax/products/update/inventory")
.param("product", "1")
.param("variant", "2")
.param("location", "3")
.param("status", "ACTIVE")
.param("quantityOnHand", "30.5")
.param("lowStockQuantity", "10")
.param("inventory", "20")
)
.andExpect(status().isOk());
Mockito.verify(mockInventoryService, Mockito.times(1)).updateExisting(Mockito.eq(oldInventory), Mockito.any(VariantInventory.class));
logger.exit();
}
This is the relative part of the controller:
#RequestMapping(value = "/ajax/products/update/inventory", method= RequestMethod.POST)
public #ResponseBody
AJAXResponse updateProductInventory(#ModelAttribute ProductInventoryFormWrapper formWrapper, BindingResult result,
ModelMap map) {
logger.entry();
logger.debug("Getting product data");
if (!result.hasErrors()) {
inventoryValidator.validate(formWrapper, result);
}
}
Then skipping a few items, this is the relevant validation that fails, where I am passing location as the field.
ValidationUtils.rejectIfEmptyOrWhitespace(errors, field, "required.field", new String[]{label});
The object fails to validate because of what must be a bug.
What I observe if I debug the controller is:
The object is in the FormWrapper, and the properties are there.
But in the BindingResult object, if I call 'getFieldValue('location')` which is what's being called in the spring validation code, it's returning null, and therefore the validator rejects the value.
So for some reason the binding result hasn't registered the formatted fields or something. Note that this only happens in the Unit Test, not in the container.
Does anyone know how to fix?
Quick Edit:
I've done some more debugging, and it's failing in this block of code from AbstractPropertyBindingResult. The value is okay right up until the conversionService is called to convert it. I haven't downloaded the source beyond that method, so I can't see exactly why it's failing, but somewhere in the convert method it's being turned from the proper value, to null. I presume because I'm using MockObjects, and maybe it's calling something that I haven't anticipated to return the value.
#Override
protected Object formatFieldValue(String field, Object value) {
String fixedField = fixedField(field);
// Try custom editor...
PropertyEditor customEditor = getCustomEditor(fixedField);
if (customEditor != null) {
customEditor.setValue(value);
String textValue = customEditor.getAsText();
// If the PropertyEditor returned null, there is no appropriate
// text representation for this value: only use it if non-null.
if (textValue != null) {
return textValue;
}
}
if (this.conversionService != null) {
// Try custom converter...
TypeDescriptor fieldDesc = getPropertyAccessor().getPropertyTypeDescriptor(fixedField);
TypeDescriptor strDesc = TypeDescriptor.valueOf(String.class);
if (fieldDesc != null && this.conversionService.canConvert(fieldDesc, strDesc)) {
return this.conversionService.convert(value, fieldDesc, strDesc);
}
}
return value;
}
Ok that was a tough one, so I didn't really expect anyone to answer. But here's the answer. I was right, the Mock was being called in the validation. So I had to add an additional mock method to the formatters (print):
// check the formatter is working
Mockito.when(mockInventoryFormatter.parse(((String)Mockito.anyObject()), ((Locale)Mockito.anyObject()))).thenReturn(oldInventory);
// this was added
Mockito.when(mockInventoryFormatter.print(Mockito.any(VariantInventory.class), Mockito.any(Locale.class))).thenReturn("20");

How should a C# Web API model binder provider work?

I have the following:
request url: 'endpoint/1,2,3?q=foo'
action to which the request is bound:
public object Bar([ModelBinder] List< T > ids, [FromUri] string q)
I want to map the "1,2,3" fragment to the "ids" parameter, so I created a ModelBinderProvider according to this link, which should call the proper model binder.
public class MyModelBinderProvider: ModelBinderProvider
{
public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
{
IModelBinder modelBinder = null;
if (modelType.IsGenericType && (modelType.GetGenericTypeDefinition() == typeof(List<>)))
{
modelBinder = new ListModelBinder();
}
return modelBinder;
}
}
I registered the provider in Global.asax like this:
GlobalConfiguration.Configuration.Services.Insert(typeof(ModelBinderProvider), 0, new MyModelBinderProvider());
The reason: I created this provider because I want, no matter what T is ('1,2,3' or 'one,two,three'), the bind to work.
The problem:
Let' say T is 'int'; everytime a request is sent, the 'modelType' paramater is always 'int' and not what I expect - 'List< int >', so the request is not properly handled.
The weird thing: Doing something like this works but T is specialized and therefor not what I want:
var simpleProvider = new SimpleModelBinderProvider(typeof(List<int>), new ListModelBinder());
GlobalConfiguration.Configuration.Services.Insert(typeof(ModelBinderProvider), 0, simpleProvider);
I cannot see what I'm doing wrong, why is the 'modelType' parameter not the expected value?
It is a very old question but I had an similar issue here with a legacy code.
Commas are reserved and it should be avoided although they work in some cases but if you really want to use them...
I think that is more a route issue than a model binder once the "1,2,3" is path part of the url. Assuming this I wrote a small RouteHandler that does the trick (please forgive the very simple "word to integer" translator).
The CsvRouteHandler gets the id array from URL and put it on RouteData as an array of integers. If the original array has words such as one, two or three it translates each value to int.
MvcRouteHandler
protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
var idArrayParameter = requestContext.RouteData.Values["idArray"] != null ? requestContext.RouteData.Values["idArray"].ToString() : null;
if (string.IsNullOrEmpty(idArrayParameter))
{
return base.GetHttpHandler(requestContext);
}
requestContext.RouteData.Values.Remove("idArray"); // remove the old array from routedata
// Note: it is horrible and bugged but and you probably have your own translation method :)
string[] idArray = idArrayParameter.Split(',');
int[] ids = new int[idArray.Length];
for(int i = 0; i < idArray.Length; i++)
{
if (!int.TryParse(idArray[i], out ids[i]))
{
switch (idArray[i])
{
case "one":
ids[i] = 1;
break;
case "two":
ids[i] = 2;
break;
case "three":
ids[i] = 3;
break;
}
}
}
requestContext.RouteData.Values.Add("Id", ids);
return base.GetHttpHandler(requestContext);
}
}
Route configuration:
routes.Add(
name: "Id Array Route",
item: new Route(
url: "endpoint/{idArray}",
defaults: new RouteValueDictionary(new { controller = "Test", action = "Index" }),
routeHandler: new CsvRouteHandler())
);

Is there a simple way in ASP.Net to get the current URL with some query parameters changed?

I've had several cases where I had a page with several query parameters - most recently a search results page - and needed to create a link to the same page with one or more query parameters changed in the URL. This seems like such a common use case that I feel as though there must be some simple built-in way of doing it.
Right now, I'm using a function I wrote which takes in a dictionary of parameters and values and merges them with the params and values from Request.QueryString. Parameters given with a null value are removed. It works, but I'm open to simpler methods.
Minor improvements I'd suggest:
//...
{
UriBuilder ub = new UriBuilder(Request.Url);
//...
ub.Query = string.Join("&", parameters.Select(kv => string.Format("{0}={1}", Server.UrlEncode(kv.Key), Server.UrlEncode(kv.Value))));
return ub.ToString();
}
Edit
Actually the return value should also be a Uri type but I didn't want to introduce any breaking changes.
The function I'm using now:
public string ThisPageWithParams(IDictionary<string, string> newParameters)
{
string url = Request.Url.AbsolutePath + "?";
var parameters = new Dictionary<string, string>();
foreach (string k in Request.QueryString)
{
parameters[k] = Request.QueryString[k];
}
foreach (var kv in newParameters)
{
if (newParameters[kv.Key] == null)
{
parameters.Remove(kv.Key);
}
else
{
parameters[kv.Key] = kv.Value;
}
}
url += string.Join("&", parameters.Select(kv => Server.UrlEncode(kv.Key) + "=" + Server.UrlEncode(kv.Value)));
return url;
}

Pass property from controller to Model

I am trying to pass a variable from a method in my Controller to a method in a Model. Since the method in the Model takes one argument (which was designed earlier), I cannot pass my variable as an argument to the method in the Model. And also, the method in this Model is called by other controllers too, so if I change the argument, I have to change all the controllers too, which would be a tedious task.
What I have been trying so far is- I created one MyVariableClass and declared a property. Then I instantiated that class and set the property string to the variable that I wanted to pass. Now, in my Model's method, I instantiated the same MyVariableClass again, but when I did that, the value of the variable was set to null. The code I have right now is -
public ActionResult ItemInformation( string id)
{
//Pass a string to MyVariable
MyVariableVClass params = new MyVariableClass();
params.myVariable = "abc";
//This is what My Model is taking as an argument(id), and I don't want to
//pass mYvariable along with that argument because it will break other controllers
// too which calls this method
var itemInformation = _repository.GetItemInformation(id);
return View(itemInformation);
}
and MyVariableClass
public class MyVariableClass
{
public string myVariable { get; set; }
}
and the method in My Model
public IList<Items> GetItemInformation(string itemId)
{
MyVariableClass webType = new MyVariableClass();
var _params = webType.myVariable;
//Check this variable and perform database query
if (_params =="this")
{
var query = myFirstQuery;
}
else
{
var query = mySecondQuery;
}
//return ....
}
Anybody has solution to this? Thanks in Advance!
Any reason why subclassing your model and overriding the GetItemInformation method wouldn't work? Or, even easier, why not just overload the GetItemInformation method with one that takes two strings? Your other controllers can still use the one that only takes a single string.
public IList<Items> GetItemInformation(string itemId, MyVariableClass webType)
{
var _params = webType.myVariable;
//Check this variable and perform database query
if (_params == "this")
{
var query = myFirstQuery;
}
else
{
var query = mySecondQuery;
}
//return ....
}
public IList<Items> GetItemInformation(string itemId)
{
MyVariableClass fauxType = new MyVariableClass();
fauxType.myVariable = "not this";
return GetItemInformation(itemId, fauxType);
}
Try using session variable.

Returning from Flex/ActionScript 3 Responder objects

I need to return the value from my Responder object. Right now, I have:
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
Basically, I need to know how to get the return value of ret_pr into id or anything that I return from that function. The responder just seems to eat it. I can't use a public variable somewhere else because this will be running multiple times at once, so I need local scope.
This is how I'd write a connection to the AMF server, call it and store the resulting value. Remember that the result won't be available instantly so you'll set up the responder to "respond" to the data once it returns from the server.
public function init():void
{
connection = new NetConnection();
connection.connect('http://10.0.2.2:5000/gateway');
setSessionID( 1 );
}
public function setSessionID(user_id:String):void
{
var amfResponder:Responder = new Responder(setSessionIDResult, onFault);
connection.call("ServerService.setSessionID", amfResponder , user_id);
}
private function setSessionIDResult(result:Object):void {
id = result.id;
// here you'd do something to notify that the data has been downloaded. I'll usually
// use a custom Event class that just notifies that the data is ready,but I'd store
// it here in the class with the AMF call to keep all my data in one place.
}
private function onFault(fault:Object):void {
trace("AMFPHP error: "+fault);
}
I hope that can point you in the right direction.
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
This code is never going to get you what you want. You need to use a proper result function. The anonymous function responder return value will not be used by the surrounding function. It will always return 0 in this case. You are dealing with an asynchronous call here, and your logic needs to handle that accordingly.
private function pro():void {
gateway.connect('http://10.0.2.2:5000/gateway');
var responder:Responder = new Responder(handleResponse);
gateway.call('sx.xj', responder);
}
private function handleResponse(result:*):void
{
var event:MyCustomNotificationEvent = new MyCustomNotificationEvent(
MyCustomNotificationEvent.RESULTS_RECEIVED, result);
dispatchEvent(event);
//a listener responds to this and does work on your result
//or maybe here you add the result to an array, or some other
//mechanism
}
The point there being using anon functions/closures isn't going to give you some sort of pseudo-syncronous behavior.

Resources