Grails - Send List of objects in params of redirect - grails

My problem is that i want to preview data on index view without saving data in database. For this purpose, I want to send the list of objects in params section of redirect. And on receiving action want to use that list of objects.But when i include as below
def preview(){
//some code
redirect action: "index", params:[planId:params.planId, beamsInfoList: beamsInfoList]
}
I want something like below to happen.
def index() {
//some code
try{
planInfo.beamInfo = (params.beamsInfoList==null)?planInfo.beamInfo:params.beamsInfoList //beamInfo is also list
//some code
Object[] obj = GRMUtils.calculateTotalBeamsPower(planInfo.beamInfo)
totalPlanPower = (Float)obj[0];
beamPowerMap= (Map<Integer, String>)obj[1];
AmMapUtility utility=new AmMapUtility()
output = utility.generateAMmapFromBeams(planInfo.beamInfo, GRMConstants.POWER_MAP_PAGE);
if(null==output){
flash.error = message(code: 'beammap.noinfoerror.message')
}
}catch(Exception e){
log.error "Excepton occured while loading Power Map", e
}
respond beams, model:[centerLong:output.getCenterLongitude(),centerLat:output.getCenterLatitude(),amMapImageProperty:output.getMapImages(),
amMapLinesProperty:output.getMapLines(), planId:params.planId, planInfo:planInfo, powersControlCarrier: powersControlCarrier, powersTrafficCarrier:powersTrafficCarrier,satPower: planInfo.satellite.satelliteMaxPower, totalPlanPower: totalPlanPower, gatewayPower: planInfo.gateway.gatewayAggregateEIRP,fesOutputPowerLimit:fesOutputPowerLimit, beamPowerMap: beamPowerMap,powerRangeColorMap:output.getReuseColorMap()]
}
It does not redirect to index method and not showing any errors. Both actions are in same controller. I have used flash, but its not helping either as value reflected on second request. I have tried session too, but i am getting error
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
on some DB fetch. I am stuck. I am new to grails and groovy. Please help.
Edit: i have found my list is large, that is why its not redirecting. Please help me with another alternative like how can i use request attribute if it is possible?

I think i have solved the problem. Its working now. All i need to do is to use the request setattribute as
request.beams = beams
And no need to pass the list in params of redirect, which i was earlier used to do. Instead of using redirect, i used forward as below:
request.beams = beams
forward action: "index", params:[planId:params.planId]

Related

Grails `respond` returns null

I want to send validation errors back to a different page (add), so I have this for my save action:
#Transactional(readOnly = false)
def save(AddDomainCommand command) {
if (command.validate() && session.isLoggedIn && session.publisher) {
// do some stuff
return redirect(controller: 'Widget', action: 'generate')
}
log.info("Validation failed for $command")
respond view: "add", model: [domain: command]
}
It errors with javax.servlet.ServletException: Could not resolve view with name 'save' in servlet with name 'grailsDispatcherServlet'
If I print the response from respond, I get null! So that explains why it's going to save, because that's the convention for the action's name.
I need it to go back to the view it came from (add.gsp), yet grails respond is null and thus defaulting to save.gsp.
Any ideas?
respond uses a different syntax and is used when you want to be able to support multiple client types based on mime type, e.g. JSON and/or XML for a REST client and HTML/GSP for a regular browser client. If you just want to use add.gsp to render HTML, use render:
render view: 'add', model: [domain: command]

Hide the ID in the URL

In Grails the URL like this
http://localhost:8080/MyApp/show/2
is there a way to hide or to encrypt the id part
/2
i need to do this to prevent users to access others data , for instance my ID is 3 , i could access other user's data by typing
/show/4
You can encode the url. If you replace the 2 with %32, the browser will still interpret it as the character 2. Here is a complete list of characters.
You can send POST request instead of GET - this is an easy way of hiding such a request parameters f.e. in server log files.
Or you can play with GRAILS codecs.
I would not hide the ID from the url. Why? because this would only mask the problem.
Consider having a class defined as :
class Post {
String title
String content
User user //you need this to keep track of the posts owner
//You could use your own custom class or the one used in spring security
...
}
If you use Spring Security Core, you would use a fucntion similar to:
def springSecurityService
#Secured(['ROLE_USER'])
def myFunction(Long id){
def postInstance = Post.read(id)
if(postInstance){
if (postInstance.user.id ==(long)springSecurityService.principal.id){
// springSecurityService?.principal?.id retrieves the id of the user in session
//... redirect to details of whatever you need
}else{
//... redirect because it is not the owner of the post
}
}
else{
//... Redirect or something
}
}
If you are using a simple session you would need to have a function like
def myFunction(Long id){
def postInstance = Post.read(id)
long userId = session["user_id"]
if(postInstance && userId > 0){
if (postInstance.user.id ==userId){
//... redirect to details of whatever you need
}else{
//... redirect because it is not the owner of the post
}
}
else{
//... Redirect or something
}
}
The logic is very similar. Still in my humble opinion you should use the spring Security plugin.

RedirecttoAction with error message

I have a link on a grid in my AdminUsers view
grid.Column(header: "", format: (item) => (condition ? Html.ActionLink("Impersonate", "Impersonate", "Admin", new { id = item.username }, null) : Html.Label("Impersonate"), style: "webgrid-column-link"),
In the controller, I have
public ActionResult Impersonate(string id)
{
string result = ORCA.utilities.users.setImpersonation(id);
if(result == "nocommonfields")
return RedirectToAction("AdminUsers", "Admin");
else
return RedirectToAction("terms_of_use", "Forms");
}
How can send an error message to display when I return to the AdminUsers page?
You may use TempData
if(result == "nocommonfields")
{
TempData["ErrorMessage"]="This is the message";
return RedirectToAction("AdminUsers", "Admin");
}
and in your AdminUsers action, you can read it
public ActionResult AdminUsers()
{
var errMsg=TempData["ErrorMessage"] as string;
//check errMsg value do whatever you want now as needed
}
Remember, TempData has very short-life span. Session is the backup storage behind temp data.
Alternatively, You may also consider sending a flag in your querystring and read it in your next action method and decide what error message to show.
The TempData controller property can be used to achieve this kind of functionality. Its main drawback in my opinion is that it uses the session storage in to store its contents. This means that you'll have extra work getting it to function on a web farm, or that you need to turn on sessions in the first place.
The good thing about TempData is that is exactly does what you want. Its a string based dictionary and you can put anything in it and by default get it out only once. So before calling RedirectToAction() you set your message. On the next request you check for messages and display them. By retrieving the messages they are automatically deleted at the end of the request.
As an alternative you could use cookies for transporting the message between the two requests. Essentially you could either roll your own solution, or implement a custom ITempDataProvider which transports the contents of TempData via cookies. Note that you need to properly secure cookies. MachineKey.Protect() can help you if you are rolling your own.
I was facing the same problem you did and created a solution for it called FlashMessage. Perhaps this could save you some work. It's available on NuGet as well. Usage is simple: you simply queue a message before you call RedirectToAction() as follows:
if(result == "nocommonfields")
{
FlashMessage.Warning("Your error message");
return RedirectToAction("AdminUsers", "Admin");
}
In your view you include the following statement to render any previously queued messages:
#Html.RenderFlashMessages()

setting cookies when render type is "contentType: text/json"

Is it possible to set cookies on response when the return render type is set as json?
I can set cookies on the response object when returning with a standard render type and later on, I'm able to get it back on the subsequent request. However, if I were to set the cookies while rendering the return values as json, I can't seem to get back the cookie on the next request object. What's happening here?
These two actions work as expected with 'basicForm' performing a regular form post to the action, 'withRegularSubmit', when the user clicks submit.
// first action set the cookie and second action yields the originally set cookie
def regularAction = {
// using cookie plugin
response.setCookie("username-regular", "regularCookieUser123",604800);
return render(view: "basicForm");
}
// called by form post
def withRegularSubmit = {
def myCookie = request.getCookie("username-regular");
// returns the value 'regularCookieUser123'
return render(view: "resultView");
}
When I switch to setting the cookie just before returning from the response with json, I don't get the cookie back with the post.
The request starts by getting an html document that contains a form and when doc load event is fired, the following request is invoked via javascript with jQuery like this:
var someUrl = "http://localhost/jsonAction";
$.get(someUrl, function(jsonData) { // do some work with javascript}
The controller work:
// this action is called initially and returns an html doc with a form.
def loadJsonForm = {
return render(view: "jsonForm");
}
// called via javascript when the document load event is fired
def jsonAction = {
response.setCookie("username-json", "jsonCookieUser456",604800); // using cookie plugin
return render(contentType:'text/json') { 'pair'('myKey': "someValue") };
}
// called by form post
def withJsonSubmit = {
def myCookie = request.getCookie("username-json");
// got null value, expecting: jsonCookieUser456
return render(view: "resultView");
}
The data is returned to the server as a result of the user pressing the 'submit' button and not through a script. Prior to the submit of both 'withRegularSubmit' and 'withJsonSubmit', I see the cookies stored in the browser (Firefox) so I know they reached the client.
I realized what the problem is -- the cookie plugin doesn't set a path for the cookie so it's stored with "server/controller/action" whereas on the subsequent request when I'm asking for the cookies, the plugin returns the cookies associated with the new request's path.
Tweaking the plugin code so the cookies are stored with uniform paths helped.

HOW? Controller return nothing/current view

SHORT:
How do I make a controller return the current view or just simply do nothing?
LONG:
I have a partial view where i've created an imageslider.
It contains a link which sends a request to a controller to get the next image (using ajax).
The controller fetches the next image, stores it in ViewData and sends back a partial view (the one above).
Now, what I do today is that when the controller reaches the last image it re-return the very same image (by refetching it), but still creates a new view, that is, the client/browser re-parses the "same" data.
This seems somewhat non-optimal.
What I'd like to do is that when controller reaches the last image it should simply do nothing.
If I return null then the view is updated with empty contents.
I want the view/client/browser to retain whatever it has and the controller to simply do nothing.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetNextImage(...)
{
if(Request.IsAjaxRequest())
{
if(CURRENT_IMAGE != LAST_IMAGE)
{
Image image = GetNextImage(...);
var partialViewResult = new PartialViewResult();
partialViewResult.ViewName = "ImageSlide";
partialViewResult.ViewData.Model = image;
return partialViewResult;
}
else
{
// DO NOTHING, HOW?
}
}
return RedirectToAction("Error", "Home");
}
You can return an EmptyResult if you want it to do nothing...
return new EmptyResult();
If you're using the AjaxHelper you can avoid the update by supplying an unsuccessful status code (e.g. 404 or whatever is most appropriate), that'll stop it replacing your div as the javascript in MicrosoftMvcAjax.js explicitly checks for a successful response before updating any elements:-
this.ControllerContext.HttpContext.Response.StatusCode = 404;
return new EmptyResult();
Ultimately the best way to avoid it is to design the partial view so it avoids the problem in the first place (like you mention yourself).
I ran into this problem today. I wanted to find a solution for how to deal with double-clicks on the client side trying to reenter the controller action on the server side while it was still processing. If a user entered that action, I wanted it to just ignore the request and do nothing on the browser side.
Solution looks like this:
public async Task<ActionResult> MyAction()
{
if(!CanEnterAction(nameof(MyAction))) return new HttpStatusCodeResult(204);
try
{
// Do long running stuff
return ValidActionResult();
}
finally
{
ExitedAction(nameof(MyAction));
}
}
Returning a status code of 204 basically does nothing to the page displayed in the browser. The actual result eventually makes it back to the browser when the action is complete.
This question is old, but I wasn't able to find an answer anywhere on StackOverflow. I figured it had to be possible since a FileResult doesn't really affect the current page, either, other than saving a file.
I would use
return new HttpStatusCodeResult(204);
this way you would stay on the same page and there is no post back.
Here is the defination
The HTTP 204 No Content success status response code indicates that the request has succeeded, but that the client doesn't need to go away from its current page
Assuming that you are using MicrosoftMvcAjax, you could send back a JavascriptResult that alerts the user that they have reached the end of the slider. If the response is javascript rather than content, the MicrosoftMvcAjax handler executes it instead of replacing the DOM contents.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetNextImage(...)
{
if(Request.IsAjaxRequest())
{
if(CURRENT_IMAGE != LAST_IMAGE)
{
Image image = GetNextImage(...);
var partialViewResult = new PartialViewResult();
partialViewResult.ViewName = "ImageSlide";
partialViewResult.ViewData.Model = image;
return partialViewResult;
}
else
{
return JavaScript( "alert('No more images');" );
}
}
return RedirectToAction("Error", "Home");
}
Of course, you'd probably want to be more creative and use the jQuery dialog plugin or something rather than an alert.
Ok, I've got it.
Not a solution to my question but still solves the problem.
I'll simply not show the "Next" link when the view shows the last image.
Can't believe I didn't think of it earlier...
Thanks for your efforts guys

Resources