Access result of a back ground task started in another action method - c#

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, ...

Related

Parse JSON from C# to AngularJS

I have an array of file names:
[HttpPost]
public JsonResult GetJSONFilesList()
{
string[] filesArray = Directory.GetFiles("/UploadedFiles/");
for (int i = 0; i < filesArray.Length; i++)
{
filesArray[i] = Path.GetFileName(filesArray[i]);
}
return Json(filesArray);
}
I need this in AngularJS as a list of objects so I can ng-repeat it out and apply filters. I'm unable to figure out how to get the JSON from the MVC controller to AngularJS.
I've tried the following to make it visible to the view for angular to grab, but I don't know how to make the ng-init see the function to return the list. It erros on "SerializeObject(GetJSONFilesList())" saying it doesn't exist in current context.
<div ng-controller="MyController" data-ng-init="init(#Newtonsoft.Json.JsonConvert.SerializeObject(GetJSONFilesList()),
#Newtonsoft.Json.JsonConvert.SerializeObject(Model.Done))" ng-cloak>
</div>
EDIT:
I've tried using http.get.
Test one:
alert('page load');
$scope.hello = 'hello';
$http.get('http://rest-service.guides.spring.io/greeting').
then(function (response) {
$scope.greeting = response.data;
alert($scope.greeting);
});
alert($scope.hello);
The alert in the http.get never fires, the other alerts do however.
Test two:
$http({
url: '/Home/testHello',
method: 'GET'
}).success(function (data, status, headers, config) {
$scope.hello = data;
alert('hi');
});
[HttpPost]
public string testHello()
{
return "hello world";
}
This causes the angular to break and nothing in the .js works.
Test three
alert('page load');
$scope.hello = 'hello';
$scope.GetJSONFilesList = function () {
$http.get('/Home/testHello')
.success(function (result) {
$scope.availableFiles = result;
alert('success');
})
.error(function (data) {
console.log(data);
alert('error');
});
alert('hi');
};
alert($scope.hello);
[HttpPost]
public string testHello()
{
return "hello world";
}
Alerts nothing from within it, other alerts work.
Fixed:
After some googling, I've found that using .success and .error are deprecated and that .then should be used. So by using .then this resulted in the C# being hit via debug.
Then after using console.log on the returned value found that to have anything be returned I needed to return the value from C# using "return Json(myValue, JsonRequestBehavior.AllowGet); "
And by viewing the object in the console in Chrome by using console.log, I could see my values were in the data part of the returned object.
It was stored in data as an array (as I was passing an array).
I could then get the data out of there by assigning the returned value.data to a scope and could call that in the view {{result[1]}} etc.
return Json(filesArray, JsonRequestBehavior.AllowGet);
$scope.fileList;
$http.get("/Home/GetFileList").then(function (result) {
console.log(result)
$scope.fileList = result.data;
})
Imagine that you divide your front end in three layers (MVC or MVVM) whatever you want.
When you need info from server, the best practice is to separate the logic that makes the request and the logic that manipulates the data.
More info about how to make the request you can find it reading about REST APIS in Consuming a RESTful Web Service with AngularJS.
Normally one of the layers requires the use of services and you can have your controllers and your services (the place where you get the raw data from the server and you make the request. For that you need to use the $http service from angularjs.
$http: The $http service is a core AngularJS service that facilitates communication with the remote HTTP servers via the browser's XMLHttpRequest object or via JSONP.
So basically it shows you how to make get, post and put requests. One example from the documentation is :
// Simple GET request example:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
Pay attention to the url because there is the place where you let your request knwow which method is going to be hit on the server to take the action. If your request is succesful, then you can use the parameter called response. From there, you can do whatever you want. If you decide to make that request part from your controller, you can assign it directly to a variable on your scope. Pay attention if you need to serialize the data. Something like
$scope.myResponseName = response.name ;
The first documentation link from above shows this example which does exactly what I tell you.
angular.module('demo', [])
.controller('Hello', function($scope, $http) {
$http.get('http://rest-service.guides.spring.io/greeting').
then(function(response) {
$scope.greeting = response.data;
});
});
After all the mentioned above, pay attention to what you want to display. Are you going to display the elements of an object array? The use on your HTML the ng-repeat directive. Are you going to display just a variable (No array nor object) then you use need to use an angular expression {{ }}
In summary:
By making an HTTP request, hit the correct method on server.
Make sure you are sending the JSON correctly and that the data is correct.
Retrieve the data on your response.
Assign the data to a variable on your scope and serialize the data if needed.
Display the data correctly depending if it is within an array, if it´s an object or if its just a variable.
I hope the explanation makes sense and check the documentation if you need more info.
You can build your viewmodel so that it contains the data you'd like to serialize and then pass it to angularJS in your view as follows:
<div ng-controller="MyController" data-ng-init="init(#JsonConvert.SerializeObject(myArrayData),
#Newtonsoft.Json.JsonConvert.SerializeObject(Model.Done))" ng-cloak>
and then in your angular controller have a function to receive the data as follows:
$scope.init = function (myArrayData) {
//do something with data
};
The above assumes you're trying to pass data from mvc to angularjs on page load. If you're trying to hit a controller and get data back to angularjs upon some event such as a button click, then you can write an angularjs function similar to the following (this will be an asynchronous request):
app.controller('MyController', function ($scope, $http, $window) {
$scope.ButtonClick = function () {
var post = $http({
method: "POST",
url: "/SomeController/SomeAjaxMethod",
dataType: 'json',
data: { path: $scope.Path},
headers: { "Content-Type": "application/json" }
});
post.success(function (data, status) {
//do something with your data
});
post.error(function (data, status) {
$window.alert(data.Message);
});
}
}
and your controller action would look something like:
[HttpPost]
public JsonResult SomeAjaxMethod(string path)
{
string[] filesArray = Directory.GetFiles(path);
for (int i = 0; i < filesArray.Length; i++)
{
filesArray[i] = Path.GetFileName(filesArray[i]);
}
return Json(filesArray);
}
other answers say to use .success in the angular function, .success and .error are deprecated, instead .then should be used.
Working result:
MVC:
public JsonResult GetFileList()
{
//form array here
return Json(myArray, JsonRequestBehavior.AllowGet);
}
The function needs to be of type JsonResult, and the returned value of Json using JsonRequestBehavior.AllowGet.
AngularJS:
$scope.fileList;
$http.get("/Home/GetFileList").then(function (result) {
console.log(result)
$scope.fileList = result.data;
})
This is in my AJS controller, using .then instead of .success. If you use console.log the result returned from the mvc controller and view it in the browser inspect you'll see the object with lots of other info and the values you want are in the .data section of the object.
So to access the values you need to do result.data. In my case this gives me and array. I assign this to a scope. Then in my view I can access the values by doing {{fileList[1]}} etc. This can also be used in ng-repeat e.g:
<div ng-repeat="file in fileList">
{{fileList[$index]}}
</div>
Each value in the array in the repeat can be accessed using $index which is the number of the repeat starting at 0.

Calling a C# async WebMethod using jQuery's $.ajax() hangs endlessly

The Issue:
I'm attempting to call a C# Web Method from jQuery to update a user's profile.
When I implement async and await, the call is made to the Web Method but the call never completes. Chrome forever shows the response as "(Pending)", and the Timing tab shows that the call is "Stalled".
Any input is greatly appreciated.
I've Tried:
Not using async and await
It works! However this entirely defeats the purpose of what I'm trying to achieve.
Changing Task<bool> to void:
"An asynchronous operation cannot be started at this time."
(Yes, my page is marked Async="true")
Googling and searching SO:
I've found a few similar questions but the resulting answers were either "Just make it synchronous instead!" (which entirely defeats the purpose) or they were MVC solutions that I'd rather not employ in my current project.
Code:
$.ajax({
type: "POST",
url: "Profiles.aspx/updateProfileName",
data: JSON.stringify({
profileName: $input.val(),
customer_profile_id: cp_id
}),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: callSuccessful,
error: callFailed
});
[WebMethod]
public static async Task<bool> updateProfileName(string profileName, string customer_profile_id)
{
User user = (User) HttpContext.Current.Session["user"];
if (profileName.Trim().Length == 0) return false;
int customerProfileId = int.Parse(customer_profile_id);
CustomerProfileViewModel profile = new CustomerProfileViewModel();
profile.profile_name = profileName;
profile.customer_profile_id = customerProfileId;
profile.customer_id = user.customerId;
bool profileUpdated = await ExampleApi.UpdateProfile(profile);
return profileUpdated;
}
I'm both sorry and relieved that I'm able to post a solution to my own question such a short time after asking it, but I've come up with a solution for the time being. Although, I'm not going to accept my own answer as I'd still like some input if available.
I've re-factored my Web Method to be a standard public static bool instead of an async. Instead of including the await, it now uses Task.Run() to call the async await function:
public static bool updateProfileWebMethod(string profileName, string customer_profile_id)
{
User user = (User) HttpContext.Current.Session["user"];
if (profileName.Trim().Length == 0) return false;
int customerProfileId = int.Parse(customer_profile_id);
CustomerProfileViewModel profile = new CustomerProfileViewModel();
profile.profile_name = profileName;
profile.customer_profile_id = customerProfileId;
profile.customer_id = user.customerId;
//CALL THE ASYNC METHOD
Task.Run(() => { updateProfileName(profile); });
return true;
}
public static async void updateProfileName(CustomerProfileViewModel profile)
{
bool profileUpdated = await ExampleApi.UpdateProfile(profile);
}
Pretty sad after 2 years nobody has provided an answer. Basically:
bool profileUpdated = await ExampleApi.UpdateProfile(profile).ConfigureAwait(false);
This configures the task so that it can resume on a different thread than it started on. After resuming, your session will no longer be available. If you don't use ConfigureAwait(false), then it waits forever for the calling thread to be available, which it won't be because it is also still waiting further back in the call chain.
However, I run into a problem where the return value doesn't get sent. the asp.net engine that calls the WebMethod is supposed to await the result if Async=true, but that doesn't seem to happen for me.
In this line>
bool profileUpdated = await ExampleApi.UpdateProfile(profile);
This method must also be static and asynchronous, in addition to all others that follow the call stack.
Another important point is to mark your ajax call with async: true,

Handle concurrent requests on a action method .net MVC

There is a ASP.NET MVC Action Method that redirects to a specific page, but also another method redirecting to another page. If both methods were hit at same time - how can I instruct C# to redirect only to first method URL. It shows that first method is hitting in debugger but second method hit after and redirects to last hit URL.
How can I redirect only to first hit method?
i have a ajax request that get hit every 5 secends.and hit signout method if not authenticated.
function Authenticate() {
ajax = $.ajax({
type: "POST",
url: "/EmployeePortal/Dashboard/Authenticate",
data: "q=123",
success: function (isAuthenticated) {
if (!isAuthenticated || count > 360) {
window.location.replace("/PortalAccount/SignOut");
} else {
count++;
}
}
});
}
also i have a link to sign out manually that hit the same method.but with a id to identify it separately.
public virtual ActionResult SignOut(string id)
{
object obj = new object();
lock (obj)
{
if (!id.IsEmptyOrNull())
{
ClearSession();
return Redirect(ConfigSettings.Current.CurrentConfigSetting.SignOutRedirectUrl);
}
}
Thread.Sleep(1000);
ClearSession();
ActionResult loginAction = MVC.PortalAccount.Login();
return RedirectToAction(loginAction);
}
Problem is sometimes when i hit the signout manually. The 5 seconds one will hit the same time and it will redirect to wrong page. how can i avoid that

Handing Events over session in ASP.NET MVC

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.

Updating page content using JS during asynchronous operation

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.

Categories