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.
Related
I'm currently writing a MVC C# application. Everything works just fine. I have a bit of functionality, where I fill up a Bootstrap modal box using an Ajax call, but the new page gets cached, despite my efforts to prevent that.
On my main page I have the following actionhandler to fill up the modal box:
function setExtraPermsOrAtts(appID){
$.ajax({
cache:false,
url: "/Group/_modifyAppPermissionsOfGroup?appID=" + appID
}).done(function (result) {
$("#addApplicationBody").html(result);
$('#modal-add-application').modal('show');
});
}
This gets caught by the following method:
public ActionResult _modifyAppPermissionsOfGroup(int? appID = 0)
{
if (appID != 0)
{
ViewBag.selectedAppID = appID;
Session["selectedGroupAppID"] = appID;
ViewBag.modifyPermsLater = true;
}
Group group = (Group)Session["currentGroup"];
return View(group);
}
Another thing that might be relevant is the point where it 'goes wrong'. The resulting View in the Modalbox, has a few radio buttons, depending on the content of the database. There I do a razor statement to get the DB value:
bool valueOfRadButtons = BusinessLogic.Domain.GroupIS.getExistingGroupPermission(
Model.LoginGroupID, myItem.ApplicationPermissionID).LoginPermissionState;
Does anyone know where I'm going wrong? Is it the Ajax call? The ActionResult method in the Controller? Or the inline razor statement? I know the data gets saved properly, cause I see so in the DB
You can specify that the response shouldn't be cached like this:
Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
Response.Cache.SetValidUntilExpires(false);
Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore();
It can be more easy if you make your own attribute and decorate the action with it as shown here.
Having some trouble with the "GET" part of the Edit View, and can't really find anything online. So far, this is my POST section:
[HttpPost]
public ActionResult Edit(ContactsEditViewModel viewModel)
{
if (ModelState.IsValid)
{
var Contacts = TheContactContext.tblContacts.Find(viewModel.ID);
Contacts.Company = viewModel.Company;
Contacts.Contact = viewModel.Contact;
Contacts.Contact2 = viewModel.Contact2;
Contacts.Email1 = viewModel.Email1;
Contacts.Email2 = viewModel.Email2;
Contacts.IsSupplier = viewModel.IsSupplier;
Contacts.Telephone = viewModel.Telephone;
Contacts.Link = viewModel.Website;
Contacts.Notes = viewModel.Notes;
TheContactContext.SaveChanges();
return RedirectToAction("~/Contacts/Index");
}
return View(viewModel);
}
I've only ever done this using EntityFramework and letting it scaffold everything, so it's the first time using Viewmodels to do it personally.
Any help in whether my POST action is correct, and some guidance on the GET action would be appreciated :)
I believe you're on the right track with POST. GET is much more simplier:
public ActionResult Create()
{
return View(new ContactsCreateViewModel() { ... your initial settings, may be contained within constructor of view model directly ... });
}
The GET request requests server to provide empty form, which is filled by user, and filled data are sent back via POST, and processed within your provided function.
EDIT
If you are talking about Edit, then it is similar with one more step:
public ActionResult Edit(int id)
{
var data_model = TheContactContext.tblContacts.Get(id); // get model probably from database
var view_model = new ContactsCreateViewModel() {
Company = data_model.Company,
...
}; // copy all data into view model
return View(view_model); // and display view
}
When your page loads for a first time it sends GET request and retrieves model with collection of items from Db. After you've updated some of these items your app sends post request (most likely using Ajax) containing json data. You update your database in controller method and now it's time to refresh your page data. Simplest way is to use ajax.
$.ajax({
url: "http://" + location.host + "/CTRL/Action",
type: "POST",
data: yourdata,
}).done(function (html) {
location.reload(); (or other method to update UI)
}).fail(function (err) {
alert(err.statusText);
});
It's for client side. Server side is smth like :
lock (obj)
{
try
{
update database here...
}
catch(Exception ex)
{
return new HttpStatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable, ex.Message);
}
return new HttpStatusCodeResult(System.Net.HttpStatusCode.OK, "Update completed");
}
For example, if the user tries to access a Main action and enters mySite/Main in the address bar, I would like force that to route to mySite/beforeMain, which would then automatically redirect to Main after some processing. How can I do this with route mapping, or is this possible in other ways? I don't want to have query string parameters in the URL, or use TempData/etc. in case cookies are disabled.
Perhaps you could use Url Rewrite.
Alternatively you could use RedirectToAction:
public ActionResult Main()
{
if(Request.QueryString["redirected"].ToString() != "true")
{
return RedirectToAction("beforeMain", "mySite");
}
return View();
}
Then your beforeMain action could be:
public ActionResult BeforeMain()
{
//do some stuff
return RedirectToAction("Main", new { redirected = "true" });
}
Then when you hit main the second time it will skip the redirect and you wont end up in a loop.
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.
I have a page. In that page some data is shown based on a service call.
This service call may take more than one minute.
So my Index action method have made like that don’t call this long service call.
But this service call is called though an Ajax call on page load.
This model is working
I would like to have a modification to this.
I would like to call this service call in different thread using Task.Factory.StartNew in index action itself. Let that thread be working in background even though the view is returned. And a separate Ajax call I should be able to get the result of the service thread.
The challenge here is how I can access the result of the tread started in Index action method in an Ajax action method?
You could have the Index action (the one that is starting the task) generate an unique number that will be associated to this task (could be a guid) and store an entry into the cache associated to this number. Then return the number to the view.
The task will then be running silently in the background and could update the entry you stored into the cache (with information such as the progression of the task or if you cannot implement this simply indicate whether the task has finished or not). Once the task finishes, remove the entry from the cache.
The view itself could send AJAX requests at regular intervals to another controller action and pass the id of the task. The action will look for the corresponding entry in the cache using this key and return to the view information about the running task. The view itself could then update the UI.
Let's have an example, shall we?
public ActionResult Index()
{
var taskId = Guid.NewGuid().ToString();
var policy = new CacheItemPolicy
{
Priority = CacheItemPriority.NotRemovable,
// Adjust the value to some maximum amount of time that your task might run
AbsoluteExpiration = DateTime.Now.AddHours(1)
};
MemoryCache.Default.Set(taskId, "running", policy);
Task.Factory.StartNew(key =>
{
// simulate a long running task
Thread.Sleep(10000);
// the task has finished executing => we could now remove the entry from the cache.
MemoryCache.Default.Remove((string)key);
}, taskId);
return View((object)taskId);
}
and then you could have another controller action that will be called by the view with an AJAX call to notify the progression of the task:
[HttpPost]
public ActionResult TaskProgress(Guid taskId)
{
var isTaskRunning = MemoryCache.Default.Contains(taskId.ToString());
return Json(new { status = isTaskRunning });
}
and finally you could have the Index view:
#model string
<div id="status">Task with id #Model has been started and running</div>
<script type="text/javascript">
// start continuous polling at 1s intervals
window.setInterval(function() {
$.ajax({
url: '#Url.Action("TaskProgress", new { taskId = Model })',
type: 'GET',
cache: false,
success: function(result) {
if (!result.status) {
// the task has finished executing => let's notify the user
$('#status').html('The task has finished executing');
}
}
});
}, 1000);
</script>
Of course this is just an oversimplified example. In a real world scenario you will have view models, use a complex model for the cache instead of just a simple string where you could hold information about the task and the result of the task if this task need to yield some result after it has finished executing, ...