I am trying to solve an issue with webservice call in my MVC 4 project. What I am doing is that when user submits form on page, I call webservice and wait for result. Meanwhile server returns callbacks (it is returning strings containing finished steps of work on server side). What I would like to do is to display contents of "log" I am building from callback on the page and refresh it repeatedly to display the progress.
The issue I run into is, that either I will have asynchronous call of webservice in my controller in which case I am not waiting for result from webservice and that means that user will not stay on the page that should be displaying progress, or I will call it synchronously, in which case javascript on the page will not get response from controller until the final response arrives (so no updates are displayed).
My desired flow is:
User submits the form
Server is called in controller
Server sends callbacks, controller processess them by expanding "log" variable with whatever arrives
User still sees the same page, where "log" (contained in specific div) is being periodically refreshed by javascript while controller waits for final result from server
Server returns final result
Controller finishes its code and returns new view
This is my post method, which currently doesnt wait for the response and proceeds immediately further:
[HttpPost]
public async Task<ActionResult> SubmitDetails
(DocuLiveInstallationRequest submittedRequest, string command)
{
request = submittedRequest;
try
{
switch (command)
{
case "Test":
{
request.OnlyTest = true;
DocuLiveInstallationStatus installStatus
= await IsValidStatus();
if (installStatus == null)
{
ViewBag.Fail = Resources.AppStart.TestNoResult;
return View("SubmitDetails", request); ;
}
else
{
status = installStatus;
if (status.Result)
{
ViewBag.Success = Resources.AppStart.TestSucces;
ViewBag.Log = installLog;
}
TempData["installationStatus"] = installStatus;
return View("SubmitDetails", request);
}
}
case "Submit":
{
request.OnlyTest = false;
DocuLiveInstallationStatus installStatus = await Install();
if (installStatus == null)
{
ViewBag.Fail = Resources.AppStart.InstallationNoResult;
return View("SubmitDetails", request); ;
}
else
{
status = installStatus;
TempData["installationStatus"] = installStatus;
TempData["installLog"] = installLog;
return RedirectToAction("Login",controllerName:"Login");
}
}
}
ViewBag.TestFail = Resources.AppStart.SubmitFailure;
return View("SubmitDetails", request); ;
}
catch
{
return View();
}
}
this is javascript I prepared for the view:
<script type="text/javascript">
$(document).ready(function () {
//$("#submitDetails").click(function () {
var progress = 0;
//$("#submitDetails").attr('disabled', 'disabled');
var statusUpdate = setInterval(function () {
$.ajax({
type: 'GET',
url: "/AppStart/GetInstallProgress",
datatype: "application/html; charset=utf-8",
success: function (data) {
if (data && data != "") {
$("div.status-message").text(progress);
}
}
});
}, 2000);
//});
});
</script>
Currently I just display the log on the next page (at this stage of development server returns response very swiftly), but I need to call the server, display progress from callbacks while waiting for result and THEN navigate to another page (depending on the result). I feel like I am very close, but I can't get it working.
PS: I am open to other solutions than updating page content. I don't really mind how the goal will be accomplished, but updating the page is preferred by the customer.
Related
I am trying out the Kendo UI HTML Scheduler.
I was able to successfully read appointments from the database through my ASP.NET MVC appplication.
For Read : I am sending JsonResult from my ASP.NET controller.
For Update : The controller is getting a URL JSON encoded string which I deserialize and update the database and the return nothing to the caller.
When open an event to edit, make the changes and press "Save". The controller gets called and the record is updated but neither the pop-up window closes nor the scheduler gets updated.
The HTML Demo on Telerik website returns "callback()" on update and works fine but what am I missing on my code that would make the changes reflect.
**view**
<script>
$("#scheduler").kendoScheduler({
// configuration //
dataSource: {
batch: true,
transport: {
read: {
url : "http://localhost/Scheduler/Appointments",
dataType: "json"
},
update: {
Type:"POST",
url: "http://localhost/Scheduler/UpdateAppointment",
dataType: "json"
},
parameterMap: function(options, operation) {
if (operation !== "read" && options.models) {
return {models: JSON.stringify(options.models)};
}
}
},
schema: {
model: {
// my model
}
},
</script>
Controller
public JsonResult UpdateAppointment(String models)
{
if (models != null)
{
char[] charsToTrim = { '[', ']' };
string model_Trimmed = models.Trim(charsToTrim);
// Deserialize
Appointment SerializedAppointment = new JavaScriptSerializer().Deserialize<Appointment>(model_Trimmed);
Models.Entities.Appointment AppointmentToUpdate = db.Appointment.Where(x => x.TaskID == SerializedAppointment.TaskID).Single();
AppointmentToUpdate.Title = SerializedAppointment.Title;
AppointmentToUpdate.Start = SerializedAppointment.Start;
AppointmentToUpdate.End = SerializedAppointment.End;
db.SaveChanges();
}
return new JsonResult() {Data=null, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
Kendo needs to return a valid JSON format in your paramater map, so you could try this:
parameterMap: function(options, operation) {
if (operation !== "read" && options.models) {
return {models: JSON.stringify(options.models)};
}
return {};
}
Maybe this Link will also helps you...
(Copy just to prevent dead Links)
There is a breaking change with the new version of jQuery which affects the Kendo Q1 2013 version 2013.1.319
Since the empty result returned from the server in case everything is executed properly on the server side for the update and destroy requests, the error event of the dataSource is triggered because the empty result is not valid JSON.
The suggested resolutions when using the MVC Extensions are:
Use the Latest Internal Build version 2013.1.327
Change the response of the Update/Destroy actions from just serializing the ModelState to:
return Json(ModelState.IsValid ? new object(): ModelState.ToDataSourceResult());
Imaing we have a Controller with
1) An action which starts some long operation, writes something to a session and immediately renders some user-friendly message:
public ActionResult Search(string query)
{
_searchProvider.Ready += SearchResultReady;
_searchProvider.Request(query);
Session["query"] = query;
return Results();
}
private void SearchResultReady(IEnumerable<IObject> results)
{
Session["searchResult"] = results.ToList();
}
When the 'Search' is done, the result is gets saved Session.
The reason we do this is to display results when they will be ready(to be requested with ajax)
public ViewResult Results()
{
if (Session["searchResult"] == null)
return View("Wait");
var query = Session["query"] as string;
var list = Session["searchResult"] as IList<IObject>;
var model = new ResultModel(query, list);
return View("Results", model);
}
Now the problem is that on Ready event, the Session is null.
What is the proper way to to save the Contoller's state between requests
You're not going to be able to use sessions for this. Sessions are not transmitted via AJAX, so the API endpoint you're hitting never gets a session token to look up. In fact, if you're dealing with a true REST API there's no such thing as a session in the first place. HTTP is a stateless protocol.
Additionally, if you do any work inside the the controller action, the response will not be returned until the result is actually ready, negating the need to fetch it later with AJAX. Even if you implement async (which you aren't even doing here), that merely releases the thread back to the server pool so that it can field other requests until the action finishes; it does not return the response faster.
If you want to load the page first and then fetch data from a long running task, you should simply render the page and let the API endpoint do the work once the page fires off a request for it via AJAX. Implement async on that so you don't deadlock the thread, and present some loading animation to the user while they wait for the AJAX request to complete.
UPDATE
Controller Action
public ActionResult Search(string query)
{
return View();
// that's right: do nothing here, just return the view
}
JavaScript for View
var interval;
$(document).ready(function () {
var query = location.search.split('=')[1];
$.post('/do/some/work', { query: query }, function (data) {
// render data
clearInterval(interval);
});
function checkStatus() {
$.get('/check/on/status', function (data) {
// data would contain percentage value or something,
//use that to update progress bar
});
}
interval = setInterval(checkStatus, 1000);
});
That's all quick and dirty. You should find a more robust way to get the search query. Maybe even set it with a view model in your action and then return that into your view or something. Also, JavaScript should use proper namespacing so some other script doesn't run over your interval variable, etc.
I have an ajax call that fires every five seconds to update some data on the screen. It seems that the getJSON breaks after a page refresh. What appears to be happening is that if the page refreshes via F5 while the call is happening, the future calls won't work. They simply fail with no error message. The textStatus is error but errorthrown is empty. I'm calling into an Asp.Net MVC controller and no error is fired there. The subsequent getJSON calls never hit the server. It's pretty much hosed at this point until I start and stop my website again. No idea how to fix this as I can't stop the user from refreshing the page and it's a pain for me to test updates.
Here is my code:
var RealTimeActivity = (function () {
var realTimeActivity = {};
var timer = null;
var active = false;
realTimeActivity.LastUpdated = new Date();
function queueUpdate() {
timer = setTimeout("RealTimeActivity.Update();", 5000);
}
function reset() {
clearTimer();
queueUpdate();
}
function clearTimer() {
if (timer) {
clearTimeout(timer);
timer = null;
}
}
realTimeActivity.Update = function () {
try {
clearTimer();
if (active === true) {
$.getJSON("realTimeActivity/GetRealTimeData",
function (result) {
if (result.Success) {
// do stuff
} else {
// Other stuff
}
queueUpdate();
}
).fail(function (jqXHR, textStatus, errorThrown) {
console.log(textStatus + ": " + errorThrown);
reset();
})
}
} catch (ex) {
reset();
}
}
realTimeActivity.Start = function (i) {
active = true;
if (i) {
realTimeActivity.Update();
} else {
queueUpdate();
}
}
realTimeActivity.Stop = function () {
active = false;
clearTimer();
}
return realTimeActivity;
}());
EDIT:
This appears to only be reproducible in Chrome. A bug in chrome perhaps or am I missing something?
This appears to be a caching issue with Chrome and aborted requests. It will not allow me to send another request to the same URL once the request is aborted even after refreshing the page. I worked around it by making every request unique (via a timestamp).
postRequest('realTimeActivity/GetRealTimeData?u=' + (new Date).getTime(), null, function (response) {
Apparently you can also call $.ajax and just set cache to false as well which does the same thing behind the scenes.
I am currently working with mvc. I have a page with more than one button on, which does complicate things a little. However the button I am using calls a jquery function to post data to the controller. This works as expected and data reaches the action and the action does everything expected except redirect.
The function posts data to the controller, or allows the controller to see the values and build up a url to then redirect to. However the redirect doesnt work, and the function calls a success function as expected. If I put the data return into an alert the code for the page is returned in the alert window not the url.
Here is the code:
<input type="submit" value="Assign" name="Assign" onclick="doRedirect()" />
function doRedirect() {
var action = '#Url.Action("Redirect")';
//alert(action);
var opt = {
type: "POST",
data: {
Team: $('#Team option:selected').val()
},
url: action,
success: RedirectSuccess
};
jQuery.ajax(opt);
}
function RedirectSuccess(data) {
if (data != undefined) {
data;
}
}
public ActionResult Redirect(string Team)
{
var hostName = "http://localhost/test/testpage.aspx?";
var team = "&Team=" + Team;
var filterUrl = Team;
return Redirect(filterUrl);**//this doesnt work**
}
Instead of sending back a redirect result from the action, try sending back the URL you want to redirect to. On the client side, you read the response of the request, and do the redirect by setting window.location.href to the URL you get back from the server.
In your action, you could return the URL as JSON for instance:
return Json(new { url: filterUrl });
And in your success callback, you do this to redirect:
if (data !== undefined && data.url !== undefined) {
window.location.href = data.url;
}
This is what I did instead.
public string Redirect(string Team)
{
var hostName = "http://localhost/test/testpage.aspx?";
var team = "&Team=" + Team;
var filterUrl = hostname + Team;
return filterUrl;
}
function RedirectSuccess(data) {
if (data != undefined) {
window.location = data;
}
}
and on my search success
I've seen this question asked a few ways and the solutions are generally for other languages and don't apply to ASP.NET MVC 2.
I am using Jquery & Jquery forms to auto-save user data at a set interval. I still want the application to be able to time out, but the auto-saves via jquery forms keep refreshing the server.
My initial idea to fix this was pretty simple. I've already got an ActionFilter I use to see if the session expires. Well, the session won't ever expire; however, I just keep track of how many auto saves occurr based on a value in session and when it reaches a limit (specified in the web.config), it does a:
filterContext.Result = new RedirectResult("~/Account.aspx/LogOn");
Well, this doesn't work because the auto save is doing an ajaxFormSubmit to call the action in the first place. I've tried changing the action to redirect to the login page, but the same thing happens....it just doesn't do a redirect. The only thing the action can return is a Json result. In my latest version (code below) I'm setting the json return value to false and calling a redirectToLogin() function to send the page over to the login page. It doesn't work and i'm not sure why.
Any thoughts on this would be most helpful.
Excerpt of code that sets up the interval for autosaving on the view (placed just before the form is closed):
<%
double sessionTimeoutInMinutes = double.Parse(ConfigurationManager.AppSettings["SESSION_TIMEOUT_IN_MINUTES"].ToString());
double maxContiguousAutoSaves = double.Parse(ConfigurationManager.AppSettings["MAX_CONTIGUOUS_AUTO_SAVES"].ToString());
double autoSaveInterval = (sessionTimeoutInMinutes / maxContiguousAutoSaves) * 60 * 1000;
%>
<%= Html.Hidden("autoSaveInterval", autoSaveInterval) %>
<script type="text/javascript">
$(document).ready(function() {
var autoSaveFrequency = $('[id=autoSaveInterval]').val();
//alert(' Auto Save Interval in miliseconds: ' + autoSaveFrequency);
setInterval(
"initAutoSave('AutoSaveGoals', 'message')"
, autoSaveFrequency);
});
</script>
"AutoSaveGoals" goals is the name of one of my actions. It handles the post, updates certain items in session, and calls the repository.update. It is defined below:
[HttpPost]
public ActionResult AutoSaveGoals(Data data)
{
Data sessdata = Data();
sessdata.MpaGoals = data.Goals;
sessdata.MpaStatus = data.MpaStatus;
sessdata.StartPeriodDate = data.StartPeriodDate;
sessdata.EndPeriodDate = data.EndPeriodDate;
sessdata.AssociatePassword = data.AssociatePassword;
try
{
_repository.update(sessdata);
}
catch (Exception e)
{
LogUtil.Write("AutoSaveGoals", "Auto Save Goals Failed");
LogUtil.WriteException(e);
}
if (!autoLogOffUser(RouteData.GetRequiredString("action")))
return Json(new { success = true });
else
return Json(new { success = false });
}
The initAutoSave function is javascript that uses Jquery & Jquery Forms plugin. Here it is:
function initAutoSave(targetUrl, messageDivId) {
var options = {
url: targetUrl,
type: 'POST',
beforeSubmit: showRequest,
success: function(data, textStatus) {
//alert('Returned from save! data: ' + data);
if (data.success) {
var currDateAndTime = " Page last saved on: " + getCurrentDateAndTime();
$('[id=' + messageDivId + ']').text(currDateAndTime).show('normal', function() { })
}
else {
alert('redirecting to login page');
redirectToLogin();
//$('[id=' + messageDivId + ']').text(' An error occurred while attempting to auto save this page.').show('normal', function() { })
//alert('ERROR: Page was not auto-saved properly!!!!');
}
}
};
$('form').ajaxSubmit(options);
}
I try doing a javascript redirect in redirectToLogin() but it doesn't seem to get the url or something behind the scenes is blowing up. Here is how it's defined:
function redirectToLogin() {
window.location = "Account.aspx/LogOn";
}
best way to solve this is to have your code always return an Json result, i use a model called StandardAjaxResponse that has an ID, a Message and an answer answer is always false unless my code completes in the correct way and sets this to true. Any errors from try / catch are placed into the message field, so if !data.Answer and the Message is equal to not loggged in the you can then location.href to the login page, without getting the login page as your ajax response.
for example:
public class AjaxGenericResponse{
public bool Answer {get;set; }
public int Id {ge; set; } // this is for cases when i want an ID result
public string Mesage {get;set;} // this is so i can show errors from ajax
}
the controller / action
public JsonResult DoAutoSave(Data data){
var JsonResults = new AjaxGenericResponse{Answer=false};
// do code here to save etc
// no matter what always return a result, even if code is broken
return Json(model);
}
your Javascript:
$.ajax({
url:"",
dataTYpe: 'json',
success:function(data){
if(data.Answer) {
// all is good
} else {
if(data.Message === "logout') { href.location=('login'); } else { alert(data.Message); }
}
}
});
thats one solution anyway!
Stupid me. Thanks for your response minus, but I think our solutions coincided for the answer. My issue was I didn't have the right url to redirect to in the redirectToLogin method. I've made minor tweaks, and presto, its redirecting.
Javascript changes:
function redirectToLogin(url) {
window.location = url;
}
function initAutoSave(targetUrl, messageDivId) {
var options = {
url: targetUrl,
type: 'POST',
beforeSubmit: showRequest,
success: function(data, textStatus) {
//alert('Returned from save! data: ' + data);
if (data.success) {
var currDateAndTime = " Page last saved on: " + getCurrentDateAndTime();
$('[id=' + messageDivId + ']').text(currDateAndTime).show('normal', function() { })
}
else {
alert('redirecting to login page');
redirectToLogin(data.url);
//$('[id=' + messageDivId + ']').text(' An error occurred while attempting to auto save this page.').show('normal', function() { })
//alert('ERROR: Page was not auto-saved properly!!!!');
}
}
};
$('form').ajaxSubmit(options);
}
Action changes
if (!shouldAutoLogOffUser(RouteData.GetRequiredString("action")))
return Json(new { success = true, url = "" });
else
return Json(new { success = false , url = Url.Action("LogOff","Account").ToString() });
The shouldAutoLogOffUser checks a session variable that was updated by an action filter to track the # of contiguous auto saves and handles the logic to see if that value has exceeded the max # of contiguous autosaves allowed. The action filter checked the actionname for 'AutoSave' and if it found it, the counter was incremented. Otherwise the counter was reset to 0 (a non autosave post occurred).
One more random question. If this application were loaded in an IFrame and the window.location call is made, would the IFrame content be changed or the entire page (the container in essence) be changed? Our company is looking to run some of our asp.net mvc 2 apps in IFrame's via websphere portal (yeah, I know....it's not my choice).
Now this is just absurd...So, I was looking over my applications (I've got several going to QA soon) and noted that I've already solved this very question with a much better solution - it was ALL handled in an ActionFilter. I wanted this from the getgo when I asked this question, but to have already implemented it, forgot about that, AND ask again on Stack Overflow...well, I hope my memory issues helps somebody with this. Below is the full action filter code. As always, I'm open to criticism so mock it, revise it, copy it, etc, etc.
public class UserStillActiveAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
int sessionTimeoutInMinutes = int.Parse(ConfigurationManager.AppSettings["SESSION_TIMEOUT"].ToString());
int maxContiguousAutoSaves = int.Parse(ConfigurationManager.AppSettings["MAX_CONSEC_SAVES"].ToString());
int autoSaveIntervalInMinutes = int.Parse(ConfigurationManager.AppSettings["AUTO_SAVE_INTERVAL"].ToString());
string actionName = filterContext.ActionDescriptor.ActionName;
string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
HttpContext currentSession = HttpContext.Current;
LogAssociateGoalsSessionStatus(filterContext.HttpContext, actionName);
if (actionName.ToLower().Contains("autosave"))
{
int autoSaveCount = GetContigousAutoSaves(filterContext.HttpContext);
if (autoSaveCount == maxContiguousAutoSaves)
{
var result = new RedirectResult("~/Account.aspx/LogOff");
if (result != null && filterContext.HttpContext.Request.IsAjaxRequest())
{
//Value checked on Logon.aspx page and message displayed if not null
filterContext.Controller.TempData.Add(PersistenceKeys.SessionTimeOutMessage,
StaticData.MessageSessionExpiredWorkStillSaved);
string destinationUrl = UrlHelper.GenerateContentUrl(
result.Url,
filterContext.HttpContext);
filterContext.Result = new JavaScriptResult()
{
Script = "window.location='" + destinationUrl + "';"
};
}
}
else
{
RefreshContiguousAutoSaves(filterContext.HttpContext, autoSaveCount + 1);
}
}
else
{
RefreshContiguousAutoSaves(filterContext.HttpContext, 1);
}
}
private int GetContigousAutoSaves(HttpContextBase context)
{
Object o = context.Session[PersistenceKeys.ContiguousAutoUpdateCount];
int contiguousAutoSaves = 1;
if (o != null && int.TryParse(o.ToString(), out contiguousAutoSaves))
{
return contiguousAutoSaves;
}
else
{
return 1;
}
}
private void RefreshContiguousAutoSaves(HttpContextBase context,
int autoSavecount)
{
context.Session.Remove(PersistenceKeys.ContiguousAutoUpdateCount);
context.Session.Add(PersistenceKeys.ContiguousAutoUpdateCount,
autoSavecount);
}
private void LogAssociateGoalsSessionStatus(HttpContextBase filterContext, string actionName)
{
AssociateGoals ag = (AssociateGoals)filterContext.Session[(PersistenceKeys.SelectedAssociateGoals)];
bool assocGoalsIsNull = false;
bool assocGoalsInformationIsNull = false;
if (ag == null)
{
assocGoalsIsNull = true;
assocGoalsInformationIsNull = true;
}
else if (ag != null && ag.AssociateInformation == null)
assocGoalsInformationIsNull = true;
}
}
always use double quote in java script and jquery to avoid browser specific issues
like
dataTYpe: 'json' must be as "dataTYpe:"json"