0

My ASP.NET MVC _Layout.cshtml has a call to a child action synchronous version of "AdminMenu" in the LayoutController which determines what menu option to be renderred. In the child action, it calls asynchronously a remote webapi . The problem is when the app starts the menu does not show, but referesh the screen once or twice the menu appears. There is a delay in getting the response from WebApi I don't know how to get around it. I thought async method would automatically update the menu when it completes, but it doesn't.

Alternative, I try to call the aync version "AdminMenu2", I always get the following error. Thank you for your help.

    HttpServerUtility.Execute blocked while waiting for an asynchronous 
operation to complete. Description: An unhandled exception occurred during 
the execution of the current web request. Please review the stack trace for 
more information about the error and where it originated in the code. 
Exception Details: System.InvalidOperationException: HttpServerUtility.Execute 
blocked while waiting for an asynchronous operation to complete. Source Error: 
Line 50: </ul>
Line 51: <ul class="nav navbar-nav navbar-right">
Line 52:   @Html.Action("AdminMenu", new { Controller = "Layout" }) 
Line 53: 
Line 54:   <li class="dropdown-toggle">


        Source File: c:\..\..\..\..\..\..\..\Views\Shared\_Layout.cshtml Line: 52
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using Newtonsoft.Json;
using myHelpersLib;
using mycModel;

namespace myMVC.Controllers {
  public class LayoutController: BaseController {
    [ChildActionOnly]
    public ActionResult AdminMenu() {

      string viewName = string.Empty;
      WebApiUserModel thisUser = new WebApiUserModel();
      if (Session["userProfile"] != null) {
        thisUser = (WebApiUserModel) Session["userProfile"];
      } else {
        //make webapi call to webapi/user 
        var response = GetUserProfile().ContinueWith(x => {
          //do something with the result
          if (x.IsCompleted) {
            thisUser = x.Result;
          }
        });
      }

      return PartialView(thisUser);
    }

    [ChildActionOnly]
    public async Task < ActionResult > AdminMenu2() {
      WebApiUserModel thisUser = new WebApiUserModel();
      if (Session["userProfile"] != null) {
        thisUser = (WebApiUserModel) Session["userProfile"];
      } else {
        HttpClient httpClient;
        HttpResponseMessage response;

        string svcLocation = System.Configuration.ConfigurationManager.AppSettings["WebApiEndpoint"];
        httpClient = myHelpersLib.WebApiBorker.GetClient(svcLocation);
        response = await httpClient.GetAsync("user");
        WebApiUserModel userProfile = new WebApiUserModel();
        if (response.IsSuccessStatusCode) {
          string content = await response.Content.ReadAsStringAsync();
          userProfile = JsonConvert.DeserializeObject < WebApiUserModel > (content);
          if (userProfile.Role != null)
            userProfile.Role = userProfile.Role.Trim();
        }
      }

      return PartialView(thisUser);
      //return new EmptyResult();
    }

    private async Task < WebApiUserModel > GetUserProfile() {
      HttpClient httpClient;
      HttpResponseMessage response;

      string svcLocation = System.Configuration.ConfigurationManager.AppSettings["fsrWebApiEndpoint"];
      httpClient = myHelpersLib.WebApiBorker.GetClient(svcLocation);
      response = await httpClient.GetAsync("user");
      WebApiUserModel userProfile = new WebApiUserModel();
      if (response.IsSuccessStatusCode) {
        string content = await response.Content.ReadAsStringAsync();
        userProfile = JsonConvert.DeserializeObject < WebApiUserModel > (content);
        if (userProfile.Role != null)
          userProfile.Role = userProfile.Role.Trim();
      }
      return userProfile;
    }
  }
}
user266909
  • 1,841
  • 3
  • 32
  • 58
  • 4
    Child actions must be invoked synchronously. – Sirwan Afifi Sep 11 '15 at 18:10
  • OK. Thanks. I am using the AdminMenu() instead of AdminMenu2(). How do I render the delayed WebAPI response and update that part of the menu item? I tried moving the return PartialView(thisUser) into the if(x.IsCompleted) block, but the compiler complainted – user266909 Sep 11 '15 at 18:14
  • 2
    As others have said, child actions are sync only. If you need to run an async method inside of one and have no other non-async methods to choose from, see this SO question and answer http://stackoverflow.com/a/25097498/654031. It's the same thing MS uses in Identity. – Chris Pratt Sep 11 '15 at 18:33

2 Answers2

1

I thought async method would automatically update the menu when it completes, but it doesn't.

No, async doesn't change the HTTP protocol. There's still only one response for each request.

If you want to dynamically update a web page after it is shown, you'll need to use SignalR, AJAX, or some comparable technology.


As others have noted, asynchronous child actions are flat-out not supported (however, ASP.NET vNext does support them).

it calls the webapi using httpClient which only has a async GetAsync() and ReadAsStringAsync()

If you do want this as a child action, then it has to be synchronous. You can use WebClient to do a synchronous WebAPI call.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Well, to be exact, MVC 6 doesn't support async child actions either, since child actions don't exist anymore. View components have replaced child action, and view components can be run async. – Chris Pratt Sep 11 '15 at 19:24
0

Actually child actions cannot be run asynchronously (+).

The recommended approach is avoid using async code in child actions.

Support asynchronous child actions.

Sirwan Afifi
  • 10,654
  • 14
  • 63
  • 110
  • The child action is synchronous, but it calls the webapi using httpClient which only has a async GetAsync() and ReadAsStringAsync() – user266909 Sep 11 '15 at 18:24