Aspnetcore.docs: Add section on synchronous tasks to "View components"

Created on 16 Sep 2018  ยท  18Comments  ยท  Source: dotnet/AspNetCore.Docs

What if I don't have any async work to perform? How should I write my InvokeAsync method?

Warning CS1998 This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListViewComponent : ViewComponent
    {
        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = new List<Item>{ ... };
            return View(items);
        }
    }
}

Document Details

โš  Do not edit this section. It is required for docs.microsoft.com โžŸ GitHub issue linking.

P2 Source - Docs.ms

Most helpful comment

@guardrex Thanks for the link. Once we figure this out, let's add an example to the doc. Others are likely wondering the same thing.

All 18 comments

Possibly like this ...

public async Task<IViewComponentResult> InvokeAsync(int maxPriority, bool isDone)
{
    var items = new List<Item>{ ... };
    return await Task.Run(() => View(items));
}

... but that might :boom: or get my picture up in the Async Programmer's Hall of Shame! :smile: I haven't had to do that in a real app.

@spottedmahn Why not make a synchronous method instead? Something like this:

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListViewComponent : ViewComponent
    {
        public IViewComponentResult Invoke(
            int maxPriority, bool isDone)
        {
            var items = new List<Item>{ ... };
            return View(items);
        }
    }
}

@guardrex nope, don't do that ๐Ÿ˜„

@scottaddie IViewComponentHelper method sig demands a Task<T> and an InvokeAsync name.

https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.iviewcomponenthelper

@pranavkm How can a synchronous ViewComponent be called without IViewComponentHelper or resorting to Task.Run with an async ViewComponent?

@guardrex Thanks for the link. Once we figure this out, let's add an example to the doc. Others are likely wondering the same thing.

How can a synchronous ViewComponent be called without IViewComponentHelper or resorting to Task.Run with an async ViewComponent?

Task.FromResult(someResult)

~One small additional update ... add Result to the call to get the output that way ...~ (no ... don't do this)

@Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true }).Result

[EDIT] Invoke it with the same Razor syntax ... @await Component.InvokeAsync( ... ) (no .Result).

In general, making a blocking call (.Result or .Wait()) may result in thread starvation and deadlock your server. I wouldn't recommend using it.

@spottedmahn Looks like we'll have to leave this in the realm of workaround; therefore, not documented. However, this issue is attached to the topic, so readers who scroll to the bottom will see it.

Sorry, I hadn't paid attention to the original issue: you can write a ViewComponent with a synchronous Invoke method:

C# public class PriorityListViewComponent : ViewComponent { public IViewComponentResult Invoke(int maxPriority, bool isDone) { var items = new List<Item>{ ... }; return View(items); } }

should work

@pranavkm How is it called in Razor with @Component (IViewComponentHelper)? It seems to want a Task<T> and a method named InvokeAsync.

https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.iviewcomponenthelper

MVC handles invoking a synchronous ViewComponent.Invoke method. The same way you can have sync and async actions or page handlers, and not have to worry about how it gets invoked.

It seems a bit strange to call the dev's method (with a synchronous method signature) using the @await Component.InvokeAsync( ... ); syntax, but it :tada: _Just Works_:tm: ๐ŸŽˆ .

I'll get this documented within a few days. Thanks everyone!

Cross-ref the discussion for the invocation changes: https://github.com/aspnet/Mvc/issues/3973

It seems a bit strange to call the dev's method (with a synchronous method signature) using the @await Component.InvokeAsync( ... );

๐Ÿ‘


Based upon the comments above I've got this working:

public Task<IViewComponentResult> InvokeAsync()
{
    var model = new MyModel
    {
        Name = "Hello world"
    };
    //have to cast to IViewComponentResult otherwise I get a compile error:
    //Error CS0029  Cannot implicitly convert type
    //'System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.ViewComponents.ViewViewComponentResult>'
    //to 
    //'System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IViewComponentResult>'
    //ViewComponent Sync Test C:\Users\mdepouw\source\repos\Experiments\ViewComponent Sync Test\ViewComponent Sync Test\Views\Shared\Components\Poc\PocViewComponent.cs
    var result = (IViewComponentResult)View(model);
    return Task.FromResult(result);
}

//doesn't work
//InvalidOperationException: Method 'InvokeAsync' of view component 
//'ViewComponentSyncTest.Views.Shared.Components.MyViewComponent.PocViewComponent'
//should be declared to return Task<T>
//public IViewComponentResult InvokeAsync()
//{
//    var model = new MyModel
//    {
//        Name = "Hello world"
//    };
//    return View(model);
//}

Full source: PocViewComponent.cs

@await Component.InvokeAsync(nameof(PocViewComponent).Replace("ViewComponent", ""))

Full source: Index.cshtml

Demo site: https://viewcomponentsync.azurewebsites.net/

Thanks to everyone! ASP.NET Core team rocks ๐Ÿ‘๐Ÿ‘ ๐Ÿ†

cleaner version with Task.FromResult<T>(T result)...

public Task<IViewComponentResult> InvokeAsync()
{
    var model = new MyModel
    {
        Name = "Hello world"
    };
    var result = View(model);
    return Task.FromResult<IViewComponentResult>(result);
}

Based upon the PR, I see I can do the following:

@await Component.InvokeAsync(nameof(PocSyncViewComponent).Replace("ViewComponent", ""))
public class PocSyncViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        var model = new MyModel
        {
            Name = "hello world sync"
        };
        return View(model);
    }
}

Full source, Demo app


Feels like magic ๐Ÿงโ€โ™‚๏ธ in that I'm telling it to call InvokeAsync() but the runtime calls Invoke()... not sure I'm a fan of that...


Thanks again ๐Ÿ™

@pranavkm See the comment from @spottedmahn above. This seems like an area that could use some polishing. Is there already an open issue on this in the aspnet/Mvc repo or elsewhere?

This will be โœจ auto-closed โœจ by the PR.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

YeyoCoder picture YeyoCoder  ยท  3Comments

danroth27 picture danroth27  ยท  3Comments

madelson picture madelson  ยท  3Comments

Raghumu picture Raghumu  ยท  3Comments

sonichanxiao picture sonichanxiao  ยท  3Comments