Spring MVC, Rest Ajax Call and Session Scope Objects - ajax

I want to solve following issue. I have a Spring-MVC Application with Thymeleaf, with a post request (sent by a form) I trigger a simulation task, what could take several minutes. The task process big number of data and we would like to have a progress bar via JavaScript. If there are two sessions, the simulation should be triggered independently and each browser shows its progress status.
Currently we have a solution, what is not really working well all the time.
The MVC Controller gets the Post request:
#Autowired SimulatorView view; // SESSION SCOPE
#PostMapping("/view")
public String run(#ModelAttribute(CHECKS) ChecksDto checksWrapper, Model model) throws InterruptedException, ExecutionException {
view.setStatisticDto(simulate(checksWrapper)); // Can take several minutes
return "simulation/result :: simulated";
}
When I trigger the simulation on my WebGUI, a progress bar has been displayed and via JavaScript I am calling Rest Methods frequently to ask for the status of the progress.
RestController
#RequestMapping("simulation/api")
public class SimulatorApi {
#Autowired SimulatorView view; // SESSION SCOPE
#RequestMapping("/progressStream")
public double progressStream() {
return view.getProgress().progressStream();
}
#RequestMapping("/progressInvoice")
public double progressInvoice() {
return view.getProgress().progressInvoice();
}
}
My JavaScript code snippet looks like:
function registerSimulationRunEvent() {
// this is the id of the form
$("#simulatorForm").submit(function(e) {
handleSimulationStarted();
var url = location.protocol + "//" + location.host + "/fdsclient/simulation/view";
$.ajax({
type: "POST",
url: url,
data: $("#simulatorForm").serialize(), // serializes the form's elements.
success: function(data) { handleSimulationFinished(); },
error: function(xhr, error) { handleSimulationError(); }
});
e.preventDefault(); // avoid to execute the actual submit of the form.
});
}
function handleSimulationStarted() {
replaceResultPanelRunning(); // THYMELEAF FRAGMENT EXCHANGE
}
function handleSimulationFinished() {
stopResultPanelAnimation(); // STOP PROGRESS BAR ANIMATION
replaceResultPanelSimulated(); // EXCHANGE THYMELEAF FRAGMENT
}
function handleSimulationError() {
stopResultPanelAnimation();
replaceResultPanelError();
}
function replaceResultPanelRunning() {
var url = // URL;
$("#resultDiv").load(url);
startResultPanelAnimation();
}
// ANIMATION
var animationInterval = null;
function startResultPanelAnimation() {
animationInterval = setInterval(animateResultPanel,4000);
}
function stopResultPanelAnimation() {
clearInterval(animationInterval); // stop the interval
}
function animateResultPanel() {
$("#simulatorProgressLabel").animate({opacity: '0.4'}, "slow");
$("#simulatorProgressLabel").animate({opacity: '1.0'}, "slow");
}
I know using session scope for rest services is a bad thing, but I didn`t know yet what is a good and easy solution. On the other hand currently different browser can simulate independently, but not always the progress bar works (especially when trigger first time mostly doesnt work). The IE11 only works when the Developer Tools are activated. When deactivating the tool while progress, the progress bar stops to grow.
What I would like to know is, how a good solution looks like when using template engine with Spring-MVC and Thymeleaf for triggering the process and displaying the status of progress via Javascript (as JQUery). Thank you in advance.

I have done a similar thing using Jquery AJAX POST submission. You can do something like this. This will submit POST request as a JSON format to the controller and wait for a response. A progress UI component can be shown during this waiting period.
//Start Progress display
function setStatistic(){
var data = JSON.stringify(//build your ChecksDto)
if (data) {
$.ajax({
url : '/view',
headers : {
'Content-Type' : 'application/json'
},
method : 'POST',
dataType : 'json',
data : data,
success : function(data) {
if (data.status == 200) {
// Stop Progress display
// Handle success status
}
},
error : function(xhr, status, error) {
// Stop Progress display
// Handle errors here
}
});
}
}
You also need to change Controller method to retrieve ajax requests as follows,
#ResponseBody
#PostMapping("/view")
public String run(#RequestBody ChecksDto checksWrapper, Model model) throws InterruptedException, ExecutionException

At least I found the solution in another Stackoverflow Page. The magic word is setting ajax cache to false.
$.ajaxSetup ({
// Disable caching of AJAX responses */
cache: false
});

Related

Async Web API and Ajax

I have a long running job that is started from the admins web page.
The Job can only be started once, and therefore I have a singleton, that also holds the different state messages of the job.
After the admin has started the job (Ajax call works) a timer in javascript gets started, that should trigger checks of the state of the job every 10 seconds (the timer works, the calls get triggered).
My Problem is, that these calls never reach the server, until the Job is finished ... After the job has finished, all the status calls (that should have been async) are processed..
WEB API CODE:
public class CleanUpServiceToolController : ApiController
{
[HttpPost]
public async Task StartJob(StartCondition startCondition)
{
if (CleanUpServiceTool.Instance.Status == "Neu")
{
CleanUpServiceTool.Instance.Status = "I'm Busy";
await Task.Delay(60*1000);//CleanUpServiceTool.Instance.Start(startCondition);
CleanUpServiceTool.Instance.Status = "Neu";
}
}
public string GetStatus()
{
return CleanUpServiceTool.Instance.Status;;
}
}
JAVASCRIPT the javascripts are logically splitted.. but all calls go through this code
return $.ajax({
url: url
type: type,
data: (type === "PUT" || type === "POST") ? JSON.stringify(input) : input,
contentType: contentType,
dataType: dataType,
jsonpCallback: jsonpCallbackFunctionName,
timeout: timeout,
async: true,
cache: false
}).done(function (response) {
var output = dataType === "jsonp" ? response : JSON.parse(response, true);
successCallback(output);
}).fail(function (response) {
var output;
try {
output = JSON.parse(response.responseText, false);
} catch (exception) {
output = response.responseText;
}
errorCallback(response.status, response.statusText, output);
}).always(function () {
if (alwaysCallback) {
alwaysCallback();
}
});
My code makes something like this:
get status of the job/ instance (Works)
if status is "Neu" then enable the "Start Job" Button (works)
on click send ajax POST data to the server (works, server starts the Job)
Start a timer and Ask for job status (Does NOT work: The calls get stacked and only get called when the StartJob Task is finished...
To test the javascript logic I used Task.Delay, I don't want to start up the job each time during testing.
I really don't know why my "async" calls are actualy "sync" calls!
I even tried making the GetStatus async , which really doesn't make much sence, (opening a thread just to read the Property of my instance)
Any Ideas, suggestions, fixes?
Thanks

Kendo-UI DataSource.read() not always making a GET request

I do have a kendo datasource which populates a kendo template.
var remoteTemplate = kendo.template($("#remotetemplate").html(), {
useWithBlock: false });
var remoteDatasource = new kendo.data.DataSource({
transport: {
read: {
url: 'Home/RemoteData',
}
},
change: function () {
$("#remotemovies tbody").html(kendo.render(remoteTemplate, this.view()));
}
});
A GET request is made to Home/RemoteData when we call the read method remoteDatasource.read()
One more read , another request is made to Home/RemoteData
I know this is not good , but I am trying to understand this :-)
remoteDatasource.read()
So far good , however once more time I call the read() , GET request is
not happening. Why is that? remoteDatasource.read()
No Get request here no matter how many times I call after this
Also I noticed the same behaviour with fetch() method.
Can someone explain me why is this behaviour? also what is the difference between read and fetch.
The read() method is supposed to request the remote service every time.
The fetch() method only requests the remote service the first time.
Your particular read() may not be requesting the remote service because it is caching. Can you try your request as a POST or set the configuration in transport.read.cache to false?
http://docs.telerik.com/kendo-ui/api/javascript/data/datasource#configuration-transport.read.cache
It seems to optimize the behavior Kendo is limiting the number of calls to two if you call datasource.read() in successive lines. Which is fair as there is no need in practical scenario to have such a code.
Here are the two types of code i have written.
Scenario 1 : Calling the datasource.read() in response to a button click.
$("#remoteRequestBtn").click(function () {
var remoteTemplate = kendo.template($("#remotetemplate").html(), { useWithBlock: false });
var remoteDatasource = new kendo.data.DataSource({
transport: {
read: {
cache: false,
url: 'Home/RemoteData',
}
},
change: function () {
$("#remotemovies tbody").html(kendo.render(remoteTemplate, this.view()));
}
});
remoteDatasource.read();
});
Result : A Get request is called to the web api , whenever i click the button.
Scenario 2 : Calling multiple datasource.read() in response to a button click.
$("#remoteRequestBtn").click(function () {
var remoteTemplate = kendo.template($("#remotetemplate").html(), { useWithBlock: false });
var remoteDatasource = new kendo.data.DataSource({
transport: {
read: {
cache: true,
url: 'Home/RemoteData',
}
},
change: function () {
$("#remotemovies tbody").html(kendo.render(remoteTemplate, this.view()));
}
});
remoteDatasource.read();
remoteDatasource.read();
remoteDatasource.read();
remoteDatasource.read();
});
Note : I am calling the read method 4 times , Now if you ask me if this is a valid scenario :-) It is not :-)
Result : In this case i get only two GET requests (For the first and second read() , Rest of the reads are ignored )
As of now i would like to treat this as an optimization from the Kendo-UI side , unless someone comeback and correct it.
Special Thanks to JFlok & CodingWithSpike for giving right directions.
Does your change callback or any other Javascript throw an exception? Try removing your change handler function. I've seen issues in the past where if an uncaught exception is thrown while the DataSource is trying to process the server response, then the DataSource is left in a state where it thinks the previous request is still running (because it never finished due to the error) and won't do another request.

How do I prevent Ajax calls from keeping a session alive?

I'm using cookie authentication in MVC5. My web pages rely heavily on authenticated as well as unauthenticated Ajax calls every 1-5 seconds to keep data updated. Consequently, my users never log out of the site.
My ideal scenario: If a user is actively browsing or conducting actions on my site, keep the session alive. If they have left a page open after 10 minutes, I'd like their session to timeout and I’'ll use the failing Ajax calls to redirect to a login page. I think this would best be accomplished at the controller or action level.
I tried controlling the session state behavior as suggested below but the session still did not time out. After 65 seconds of hitting ReadOnly/Public once per second, I call ReadOnly/Authorized and successfully retrieve data from it.
Here is my CookieAuthentication configuration.
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(1),
});
}
My test page:
<div id="public"></div>
<div id="authorized"></div>
#section scripts{
<script>
function poll(times) {
var url = '/ReadOnly/Public';
$.ajax({
url: url,
dataType: 'json',
data: null,
cache: false,
success: function (data) {
$('#public').html(times + ' ' + data.test);
},
error: function (data) {
$('#public').html(times + ' ' + 'failed');
}
});
};
function checkAuth(times) {
var url = '/ReadOnly/Authorized';
$.ajax({
url: url,
dataType: 'json',
data: null,
cache: false,
success: function (data) {
$('#authorized').html(times + ' ' + data.test);
},
error: function (data) {
$('#authorized').html(times + ' ' + 'failed');
}
});
};
$(function () {
var times = 1;
setInterval(function () {
poll(times);
times++;
}, 1000);
setInterval(function () {
checkAuth(times);
}, 65000);
});
</script>
}
and test controller code (tried this with both the disabled and readonly options)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.SessionState;
namespace SessionTest.Controllers
{
[SessionState(SessionStateBehavior.ReadOnly)]
public class ReadOnlyController : Controller
{
[Authorize]
public ActionResult Authorized()
{
return Json(new
{
test = "ReadOnly and Authorized"
}, JsonRequestBehavior.AllowGet);
}
public ActionResult Public()
{
return Json(new
{
test = "ReadOnly and Public"
}, JsonRequestBehavior.AllowGet);
}
}
}
Maybe you need to have 2 separate web apps. One is for serving authenticated requests. Another one is for all public requests.
That's similar to how the Google Analytics script creates and maintains its own Session on Google side about your site without impacting your web application's internal session management. Otherwise, you will get stuck with the default behavior of ASP .NET the way it is handling cookies and keeps session alive.
Good luck.
I wouldn't implement a timeout in this situation. In fact I try to avoid them unless there is a fundamental and key reason why they are necessary, otherwise they just become an annoyance.
However if you do feel you need one, I would implement it in this case, by creating a separate javascript function which has a timer, and that is reset with user input. If the timer completes an ajax call is performed that executes a manual session invalidation on server side.
I would configure the listener method or class to not use session which will prevent it from being extended.
There are attributes available for both methods and controllers that provides different session modes.
More info here:
http://www.dotnet-tricks.com/Tutorial/mvc/906b060113-Controlling-Session-Behavior-in-Asp.Net-MVC4.html
Ajax calls will keep the session alive.
One approach will be to set a timeout on client side to delete cookie after some time.
I'm not sure you have anymore options.
If the calls every 5 sec are only to non-authenticated request, just keep the cookie out of the ajax request.
I think the sliding expiration is set to true by default.
I think perhaps when the call that is made to the action Public, it's made with cookie and thus extending the timeout.
public ActionResult Public()
{
return Json(new
{
test = "ReadOnly and Public"
}, JsonRequestBehavior.AllowGet);
}
If I set this below: (SlidingExpiration = false). I get the failed message.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(1.0),
SlidingExpiration = false
//Provider = new CookieAuthenticationProvider
//{
// OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
// validateInterval: TimeSpan.FromMinutes(30),
// regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
//}
});

Forms validation in Nancy not working with AJAX login requests

I'm trying to implement an extremely simple spike using Nancy as an alternative to ASP.NET MVC.
It should take a username (no password) and provide meaningful error messages on the same login page without requiring a refresh. If login was successful, the response includes the URL to navigate to.
The POCO for the response looks like this:
public class LoginResponseModel
{
public bool IsSuccess { get; set; }
public string RedirectUrl { get; set; }
public string ErrorMessage { get; set; }
}
The JS handler for the login request:
$.ajax({
url: '/login',
type: "POST",
data: { UserName: username }
}).done(function (response) {
if (response.IsSuccess) {
showSuccess();
document.location.href = response.RedirectUrl;
return;
}
showError(response.ErrorMessage);
}).fail(function (msg) {
showError("Unable to process login request: " + msg.statusText);
});
The problem I'm having is with Nancy's Forms-based authentication. I've walked through half a dozen different tutorials which all more or less do the same thing, as well as gone over the Nancy authentication demos. The one thing they all have in common is that they rely on the LoginAndRedirect extension method. I don't want to return a redirect. I want to return a result of the login attempt and let the client handle the navigation.
The IUserMapper implementation I'm using:
public class UserMapper : IUserMapper
{
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
{
// Don't care who at this point, just want ANY user...
return AuthenticatedUser {UserName = "admin"};
}
}
The relevant part of my LoginModule action:
var result = _userMapper.ValidateUser(input.AccessCode);
if (result.Guid != null) this.Login(UserMapper.GUID_ADMIN, expiry);
return Response.AsJson(result.Response);
but for subsequent requests Context.CurrentUser is always null.
If I add the following method to the Nancy.Demo.Authentication.Forms sample it reproduces the behaviour I'm seeing in my own project, leading me to believe LoginWithoutRedirect doesn't work how I expected.
Get["/login/{name}"] = x =>
{
Guid? userGuid = UserDatabase.ValidateUser(x.Name, "password");
this.LoginWithoutRedirect(userGuid.Value, DateTime.Now.AddYears(2));
return "Logged in as " + x.Name + " now <a href='~/secure'>see if it worked</a>";
};
The problem turns out to be that Context.CurrentUser with FormsAuthentication is dependent upon a cookie which isn't set if you don't return the NancyModule.Login() response.
var result = _userMapper.ValidateUser(input.AccessCode);
if (result.IsSuccess) {
this.LoginWithoutRedirect(result.Guid);
}
return Response.AsJson(result);
In this example, the LoginWithoutRedirect call returns a Response object with the cookie set. To handle this in an Ajax scenario I've had to add a AuthToken property to the LoginAjaxResponse class, then pass the cookie like so:
var result = _userMapper.ValidateUser(input.AccessCode);
var response = Response.AsJson(result);
if (result.IsSuccess) {
var authResult = this.LoginWithoutRedirect(result.Guid);
result.AuthToken = authResult.Cookies[0].Value;
}
return Response.AsJson(result);
On the client, the Ajax response handler changes to (assuming use of jQuery cookie plugin:
$.ajax({
url: '/login',
type: "POST",
data: { UserName: username }
}).done(function (response) {
if (response.IsSuccess) {
showSuccess();
$.cookie("_ncfa", response.AuthToken); // <-- the magic happens here
document.location.href = response.RedirectUrl;
return;
}
showError(response.ErrorMessage);
}).fail(function (msg) {
showError("Unable to process login request: " + msg.statusText);
});
The AuthToken is the GUID which has been encrypted and base64-encoded. Subsequent requests with this.RequiresAuthentication() enabled will first check for this auth token cookie.
If no "_ncfa" cookie is present,the UserMapper's GetUserFromIdentifier() is never called.
If the value in Context.Request.Cookies["_ncfa"] does not result in a valid GUID when base64-decoded and decrypted, GetUserFromIdentifier() is never called.
If GetUserFromIdentifier() isn't called, Context.CurrentUser is never set.
If you want the source for a working example it's on GitHub.
LoginAndRedirect is only one option, there are equivalent methods for not redirecting (LoginWithoutRedirect), or one that picks up on whether it's an AJAX request and handles it appropriately (Login). The same applies to logging out.
This is all covered, in detail, in the documentation.

Stop Duplicate Ajax Submisions?

I am wondering what is the best way to stop duplciate submissions when using jquery and ajax?
I come up with 2 possible ways but not sure if these are the only 2.
On Ajax start disable all buttons till request is done. 2 problems I see with this though is I use jquery model dialog so I don't know how easy it would be to disable those button as I not sure if they have id's. Second I if the the request hangs the user has really no way to try again since all the buttons are disabled.
I am looking into something called AjaxQueue at this time I have no clue if it is what I need or how it works since the site where the plugin is apparently down for maintenance.
http://docs.jquery.com/AjaxQueue
Edit
I think this is a spin off of what I was looking at.
http://www.protofunc.com/scripts/jquery/ajaxManager/
The only problem I see with this ajaxManager is that I think I have to change all my $.post, $.get and $.ajax ones to their type.
But what happens if I need a special parameter from $.ajax? Or that fact I like using .post and .get.
Edit 2
I think it can take in all $.ajax options. I am still looking into it. However what I am unsure about now is can I use the same constructor for all requests that will use the same options.
First you have to construct/configure a new Ajaxmanager
//create an ajaxmanager named someAjaxProfileName
var someManagedAjax = $.manageAjax.create('someAjaxProfileName', {
queue: true,
cacheResponse: true
});
Or do I have to make the above every single time?
How about setting a flag when the user clicks the button? You will only clear the flag when the AJAX request completes successfully (in complete, which is called after the success and error callbacks), and you will only send an AJAX request if the flag is not set.
Related to AJAX queuing there is a plugin called jQuery Message Queuing that is very good. I've used it myself.
var requestSent = false;
jQuery("#buttonID").click(function() {
if(!requestSent) {
requestSent = true;
jQuery.ajax({
url: "http://example.com",
....,
timeout: timeoutValue,
complete: function() {
...
requestSent = false;
},
});
}
});
You can set a timeout value for long-running requests (value is in milliseconds) if you think your request has a possibility of hanging. If an timeout occurs, the error callback is called, after which the complete callback gets called.
You could store an active request in a variable, then clear it when there's a response.
var request; // Stores the XMLHTTPRequest object
$('#myButton').click(function() {
if(!request) { // Only send the AJAX request if there's no current request
request = $.ajax({ // Assign the XMLHTTPRequest object to the variable
url:...,
...,
complete: function() { request = null } // Clear variable after response
});
}
});
EDIT:
One nice thing about this, is that you could cancel long running requests using abort().
var request; // Stores the XMLHTTPRequest object
var timeout; // Stores timeout reference for long running requests
$('#myButton').click(function() {
if(!request) { // Only send the AJAX request if there's no current request
request = $.ajax({ // Assign the XMLHTTPRequest object to the variable
url:...,
...,
complete: function() { timeout = request = null } // Clear variables after response
});
timeout = setTimeout( function() {
if(request) request.abort(); // abort request
}, 10000 ); // after 10 seconds
}
});
$.xhrPool = {};
$.xhrPool['hash'] = []
$.ajaxSetup({
beforeSend: function(jqXHR,settings) {
var hash = settings.url+settings.data
if ( $.xhrPool['hash'].indexOf(hash) === -1 ){
jqXHR.url = settings.url;
jqXHR.data = settings.data;
$.xhrPool['hash'].push(hash);
}else{
console.log('Duplicate request cancelled!');
jqXHR.abort();
}
},
complete: function(jqXHR,settings) {
var hash = jqXHR.url+jqXHR.data
if (index > -1) {
$.xhrPool['hash'].splice(index, 1);
}
}
});

Resources