Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting a 404 with asynchronous action (Task<ActionResult>)

Very simply, I've converted some synchronous login actions to asynchronous and now they're not found, getting 404.

    public async Task<ActionResult> BetaLoginAsync()
    {            
        return View();
    }

    [HttpPost]
    public async Task<ActionResult> BetaLoginAsync(Models.Authentication.SimpleLoginModel loginModel, string returnUrl)
    {
        if (this.ModelState.IsValid)
        {
            if (await this.ValidateCredentialsAsync(loginModel))
            {
    ...

I thought I'd done all that's needed, I followed this:

http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4

The compiler is warning me that the first action method that just returns a view isn't using the await keyword, so it will run synchronously.

The problem is that when I remove the async keyword, it then doesn't compile at all. Compiler says that the type ViewResult returned from the call to View cannot be implicitly converted to type Task<ActionResult>.

Now, if I make it synchronous but leave my HttpPost post-back login validation method asynchronous, then MVC routing doesn't call it when I click the submit button, instead it posts to the synchronous version that just returns the view!!

I've worked with asynchronous actions before, I don't know why I'm struggling today.

Is this a bug when there are two action methods with the same name/overloaded??

Does anyone know what's going on?

Update 1

Okay, so this is weird.

It works when the controller subclasses AsyncController but this is not supposed to be how it works in MVC 4+.

Should I use AsyncController at ASP.NET MVC 4?

I'm running MVC 5, or supposed to be. I haven't created an MVC 3 project for ...years.

I'll need to do some more investigating and come back.

Update 2

I have an answer, but no real understanding of why. I tried Chris' suggestion of removing the Async suffix from the actions and it worked.

The strange thing is that, as above, it works when using the 'old' AsyncController from MVC 3.

I guess that's just how it is now.

like image 864
Luke Puplett Avatar asked Aug 14 '14 14:08

Luke Puplett


2 Answers

An asynchronous method must have something to wait on. Otherwise, it has no cue for when it's allowed to return its thread and will therefore run sync (hold on to the thread the entire time). The warning is just that: a warning. It's letting you know that you're adding overhead by making the method async but not actually doing any asynchronous work and, therefore, are just adding overhead for no reason.

The reason you got an error with removing async is that you're still returning a Task<ActionResult>. So if you want to remove async, you actually need to change the method definition to:

public ActionResult BetaLoginAsync()

Then, it will compile without error or warning.

Obviously the naming is awkward at that point, but your two method names must match up to allow the routing framework to do a postback to the same URL (without attribute routing).

However, it's actually pretty unheard of to see async controller actions named with Async.

It's common to follow that convention for other types of methods, because it provides a clue to the programmer that they need to await the method. However, you don't actually call controller actions methods directly, typically, which removes the benefit of the convention. It also makes traditional routing more difficult unless you like Async showing up in your URLs (which you shouldn't).

So, I would just remove the Async suffix from both methods.

like image 109
Chris Pratt Avatar answered Oct 20 '22 08:10

Chris Pratt


Chris has the right answers. When I wrote Using Asynchronous Methods in ASP.NET MVC 4 there was no established convention for naming async action methods. The suffix is just convention and has nothing to do with async (as the document states - "Async" was appended to the method name. Appending "Async" is not required but is the convention when writing asynchronous methods.- As Chris points out that's the convention, but not for MVC apps)

The reason your conversion didn't work is you missed that point, and the reason using the old AsyncController works, is the old AsyncController depended on the async suffix. Look at the source code and you can see that. Also look at the source code and you can see an AsyncController just a controller now since the controller base class supports async. AsyncController was just left in the code base for backward compatibility.

Create a new MVC 5 app, add a model, create a controller and check the async box. Examine the working async code and maybe that will help you figure out what you're doing wrong. Async MVC is very common and widely used.

like image 43
RickAndMSFT Avatar answered Oct 20 '22 07:10

RickAndMSFT