1

I've build an API-endpoint to fetch available languages from. In my MVC application, I have a helper to fetch the languages async. The method is defined like:

public static async Task<Languages> GetLanguagesAsync()
{
    var apiResponse = await APIHelper.GetContentGetAsync("Languages").ConfigureAwait(false);
    return apiResponse.LanguagesDataModel;
}

In my View I want to bind a dropdownlist to the list of available languages the user can select from.

@Html.DropDownListFor(m => m.Language, LanguageHelper.AvailableLanguages)

The getter is defined the following:

public static IEnumerable<SelectListItem<string>> AvailableLanguages
{
    get
    {
        var result = GetLanguagesAsync().Result;
        return new List<SelectListItem<string>>(result.Languages.Select(l => new SelectListItem<string> {Value = l.Key, Text = l.Value}));
    }
}

However, I always get an error at line var result = GetLanguagesAsync().Result; which is the most upvoted answer from here.

The exception thrown is

An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>. This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it.

As stated here the called action is marked async.

Community
  • 1
  • 1
KingKerosin
  • 3,639
  • 4
  • 38
  • 77
  • 4
    Isn't the message clear enough? You must use the `<%@ Page Async="true" %>` directive in your page. I suspect you are using an old ASP.NET MVC version because the latest versions don't need this. This has nothing to do with the `async/await` keywords. – Panagiotis Kanavos Mar 23 '16 at 08:19
  • 1
    Please, don't use `.Result`, rather use `await GetLanguagesAsync()` – Mafii Mar 23 '16 at 08:20
  • 1
    @Mafli there are no asynchronous properties – Panagiotis Kanavos Mar 23 '16 at 08:20
  • http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html – Tewr Mar 23 '16 at 08:21
  • 1
    In any case, trying to execute actions in your ViewModel properties means there is a serious design problem. The *controller* should send a complete ViewModel to the view. There should be *no* need to execute methods from inside the VM. It's a lot easier to write an asynchronous controller – Panagiotis Kanavos Mar 23 '16 at 08:21
  • @Tewr there are no asynchronous properties. People, please read the question – Panagiotis Kanavos Mar 23 '16 at 08:21
  • @PanagiotisKanavos The application is running against `.NET 4.5` using `MVC 5.2.3` and only as found in the MSDN-article, the whole action of the view is async. No call inside the view is made – KingKerosin Mar 23 '16 at 08:21
  • @KingKerosin then just do the work in the controller. You won't need to add any new directives. There is never a good reason to execute asynchronous methods in the VM itself. Which is why you wont' find many people running into this issue. – Panagiotis Kanavos Mar 23 '16 at 08:23
  • @PanagiotisKanavos I just wanted a `readonly` property to be used at different views with `LanguageHelper.Languages` without the need to have a property to be set by the `action` on each view(model) using languages as this would produce the same code over and over – KingKerosin Mar 23 '16 at 08:25
  • 5
    Do so then. You don't *have* to load that value asynchronously on *every* call, that's even worse. A better option would be to load the lookup data once eg when the application starts and cache it. You can use a static property for this, a Cache, an injected list etc. You could even use a `Lazy` to load and cache the data on first use – Panagiotis Kanavos Mar 23 '16 at 08:33

1 Answers1

1

Razor code today cannot handle asynchronous calls, even if you're wrapping those calls with synchronous blocking (which is a design mistake). The fact that the action is async is immaterial, because this is during the processing of the view, after the action has completed.

Async views have been added to ASP.NET Core; however, it should still be an uncommon use case.

In general, your options are:

  • Make the helper synchronous "all the way"; i.e., use synchronous API calls instead of asynchronous, and get rid of the sync-over-async code.
  • Add the data to your view models (possibly as part of a common VM base).

In this specific case, I agree with Panagiotis' comment that this kind of unchanging information should only be loaded once per app, not once per call. Wrapping a synchronous implementation inside a Lazy<T> would be the easiest solution.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks for the clarification. As already mentioned before, I've changed my code to fetch the data synchronously once when needed and store the result in a `Lazy>`. – KingKerosin Mar 23 '16 at 11:33