Reputation: 7449
The MVC application that I am building has a portion of the page (implemented as a partial) that uses a ViewModel loaded from the ViewData. I have created an abstract controller that all my controllers inherit from (the IoC system I am using makes a base controller the best option). At the moment, I am setting the data in the OnActionExecuting override.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
ViewData["OperationStatusVM"] = OperationsStatusBuilder.Get(Operations);
base.OnActionExecuting(filterContext);
}
The problem is that the OperationsStatusBuilder method is fairly long running. I am trying to put together an approach to populate the ViewModel without blocking the rest of the request.
My first thought is to start the operation via a Task in OnActionExecuting, then set the actual value in OnActionExecuted.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RequestContext.HttpContext.Items["OperationTask"]
= Task<OperationsStatusVM>.Factory.StartNew(() => OperationsStatusBuilder.Get(Operations));
base.OnActionExecuting(filterContext);
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
var task = filterContext.RequestContext.HttpContext.Items["OperationTask"] as Task<OperationsStatusVM>;
ViewData["OperationStatusVM"] = task.Result;
base.OnActionExecuted(filterContext);
}
I am still ironing out the kinks, but it seems like I might be missing an opportunity to use something that is built into MVC. My main requirement is that the ViewData must be set when the page is rendered.
Does this approach make sense? Is this a good way to solve this problem, or is there a better approach?
Thanks, Erick
Upvotes: 0
Views: 261
Reputation: 16928
It sounds like you're describing Asynchronous Controllers in MVC? While this will free up IIS to respond to other requests while your long running request is processed, if you want other methods in the same Action to run while your OperationsStatusBuilder is executed, you'll have to cue them all using some asynchronous code and wait for all of them to return their results before returning the result to the view to be rendered. The best result will be seen if you have multiple cores on server hosting this site. Here's an example of one I used in an app to pull documents out of a database asynchronously.
The GetFileResult
method (which is flagged as a nonaction elwhere in my controller) is what actually ran my logic. The reason I pass the task back to the result is that errors raised in the Async method never made it to the result method. By passing the task as a parameter and calling task.Wait()
as the first line of my result method I was able to cause it to rethrow the exceptions and let the site handle/show rather than silently ignoring them.
Note you can create multiple tasks if you have multiple tasks that are long running to have them run at the same time, and increment the OutstandingOperations once for each. Your result should only get the result when the number of outstanding operations reaches 0 and they've all completed.
public void FileAsync(Guid id)
{
AsyncManager.OutstandingOperations.Increment();
Action getFileAsync = () =>
{
FileResult fileResult = GetFileResult(id, false);
AsyncManager.Parameters["result"] = fileResult;
AsyncManager.OutstandingOperations.Decrement();
};
var task = Task.Factory.StartNew(getFileAsync);
AsyncManager.Parameters["task"] = task;
}
public ActionResult FileCompleted(FileResult result, Task task)
{
task.Wait();
if (result == null)
return HttpNotFound();
return result;
}
Upvotes: 1
Reputation: 20004
"The problem is that the OperationsStatusBuilder method is fairly long running. I am trying > to put together an approach to populate the ViewModel without blocking the rest of the request."
It's one request. As in the client is getting the merged result of your view + partial view as one document. What you're describing of doing sounds like a socket. Anyways, I think the only way to do this is send down the page, and then use AJAX after the fact to request the partial view and update the page.
Or am I confused?
Additionally:
You don't have to store a partial views model inside a ViewData dictionary. You can store it inside your main model and then call your partial like this: @Html.Partial(Model.MyPartialViewModel)
Upvotes: 0