Home > Software design >  Can ASP.NET MVC 5 use Async methods?
Can ASP.NET MVC 5 use Async methods?

Time:12-09

ASP.NET MVC 5 with .NET Framework v4.5.2

I've got a large, existing project that I have been assigned. Some parts of it take a really long time, so I wanted to see about putting some of these "new to me" asynchronous calls in the code.

But, after looking at the examples, I don't know if what I have can do that.

Take this typical method on my controller:

public ActionResult DealerSelect()
{
    var model = RetailActivityModelData.GetCurrentDealers(null, ViewBag.Library);
    return View(model);
}

I don't really understand how to turn that into an asynchronous call unless I did something like this:

public async Task<ActionResult> DealerSelect()
{
    var model = await RetailActivityModelData.GetCurrentDealers(null, ViewBag.Library);
    return View(model);
}

But the compiler complains with:

The return type of an async method must be void, Task, Task, a task-like type, IAsyncEnumerable, or IAsyncEnumerator

compiler

I'm not sure how to change the return type without destroying the View model.

So, I went into the GetCurrentDealers method to make a basic BackgroundWorker call:

public static DealerModel GetCurrentDealers(DealerModel model, string library)
{
    m_library = library;
    if (model == null)
    {
        model = new AdminCurrentDealerModel();
    }
    using (var bg = new BackgroundWorker())
    {
        var mre = new ManualResetEvent(false);
        bg.DoWork  = delegate (object s, DoWorkEventArgs e)
        {
            // fill 3 DataTables with calls to database
        };
        bg.ProgressChanged  = delegate (object s, ProgressChangedEventArgs e)
        {
            // add data from DataTables to model
        };
        bg.RunWorkerCompleted  = delegate (object s, RunWorkerCompletedEventArgs e)
        {
            mre.Set();
        };
        bg.RunWorkerAsync();
        if (bg.IsBusy)
        {
            mre.WaitOne();
        }
    }
    return model;
}

It compiles just fine, but crashes as soon as bg.RunWorkerAsync() is called:

System.InvalidOperationException: '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.'

This is an ASP.NET MVC Razor page, so it doesn't really have that <%@ Page Async="true" %> part.

Can I even do incremental asynchronous development on this project, or does the whole thing need to be converted to one that uses Tasks?

CodePudding user response:

ASP.NET MVC 5 with .NET Framework v4.5.2

Please see my article Introduction to Async on ASP.NET. In particular, ensure you have httpRuntime.targetFramework set correctly to avoid the server-side "quirks mode".

make a basic BackgroundWorker call

You should never use BackgroundWorker on ASP.NET. And almost never use Task.Run (or StartNew). You cannot "make something asynchronous" this way.

The exception you encounter is a "safety net" exception.

I don't really understand how to turn that into an asynchronous call

Don't start at the controllers. That's actually the opposite of the easiest way to do it. Instead, start with the lowest-level code you have that performs any I/O. E.g., database queries or calls to external APIs. One at a time, make those asynchronous (using await to call asynchronous APIs), and then allow the async/await usage to grow out from there. Eventually, your controller methods will become async as the last step of this process.

Can I even do incremental asynchronous development on this project, or does the whole thing need to be converted to one that uses Tasks?

As you're converting to async, you may find it useful to (temporarily) have two copies of the code: one async (used by code that is already converted) and one sync (used by unconverted code). This is fine if you're doing an conversion-to-async that won't be interrupted by other feature work. If you need longer-term support for both async and sync code, then you can use the boolean argument hack described in my Brownfield Async article (or if you want a more modern/complex solution, use generic code generation as described on my blog).

CodePudding user response:

Your method GetCurrentDealers() isn't async so you can't call it with await.

  • Related