ViewComponents are not async
Asked Answered
E

1

9

I am trying to use the ViewComponents.InvokeAsync() feature, but somehow this is not async at all. It is waiting for the component code to render. http://docs.asp.net/en/latest/mvc/views/view-components.html

My code is very much similar to the one explained in above example. I am using the layout page which comes when you create new application in MVC 6.

I thought that ViewComponent.InvokeAsync() method will render asynchronously with respect to the main page. But it does not. In order to achieve this, we need to use AJAX as explained here.

Euterpe answered 16/3, 2016 at 0:27 Comment(2)
When you say, "It is waiting," what do you mean? That is, what does the "it" mean?Evenings
The page renders only when the component is rendered. I am expecting the page to finish rendering and then the component should be rendered asynchronously. Also, I would like to have a spinner (gif) which is in place for component until it is not rendered.Euterpe
E
26

Server Side Async is not Client Side Async

Server side async does not do partial page rendering in the web browser. The following code will block until GetItemsAsync returns.

public async Task<IViewComponentResult> InvokeAsync()
{
    var items = await GetItemsAsync();
    return View(items);
}

And this code will block until the itemsTask completes.

public async Task<IViewComponentResult> InvokeAsync()
{
    var itemsTask = GetItemsAsync(maxPriority, isDone);

    // We can do some other work here,
    // while the itemsTask is still running.

    var items = await itemsTask;
    return View(items);
}

Server side async lets us do additional work on the server while we wait for some other server side task to complete.

AJAX View Component

To partially render the page in the web browser, we need to use client side AJAX. In the following example, we use AJAX to call /Home/GetHelloWorld and render in in the body.

~/HelloWorldViewComponent.cs

public class HelloWorldViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        var model = new string[]
        {
            "Hello", "from", "the", "view", "component."  
        };

        return View("Default", model);
    }     
}

~/HomeController.cs

public class HomeController : Controller
{
    public IActionResult GetHelloWorld()
    {
        return ViewComponent("HelloWorld");
    }
}

~/Views/Shared/Components/HelloWorld/Default.cshtml

@model string[]

<ul>
    @foreach(var item in Model)
    {
        <li>@item</li>
    }
</ul>

~/wwwroot/index.html

<body>
<script src="js/jquery.min.js"></script>
<script>
    $.get("home/GetHelloWorld", function(data) {
        $("body").html(data);
    });
</script>
</body>

localhost:5000/index.html

A unordered list that shows the string array.

Evenings answered 16/3, 2016 at 0:55 Comment(8)
Hi Shaun, I know what async does in C#. In above code, it is fine that the rendering of the component would be blocked. But I am looking for the rest of the page to get rendered first and then the component. I was thinking I can achieve this with @await component.InvokeAsync(). Please correct me hereEuterpe
Yeah. You cannot do that. the @await is still server side not client side. Think of it like the await in C# code. It just means that we can do other things on the server between starting a task and awaiting its completion. It does not talk to the client at all.Evenings
Okays, misunderstood ViewComponent.InvokeAsync. So, can I call components using AJAX ? any samples would really help. What I am trying to achieve is: I have a view component which I want to use in my XYZ page, but I want that view component to render asynchronously.Euterpe
Yes. It does look like it is possible to consume a View Component with AJAX. E.g. https://mcmap.net/q/600573/-viewcomponent-alternative-for-ajax-refreshEvenings
Thanks this helps.Euterpe
Could you use a viewcontroller for a form submit, when the user clicks submit it calls a controller that returns a viewcontroller, that way you don't have to refresh the whole page????Cowie
Should the controller return IViewComponentResult instead of IActionResult?Titian
There is a partial render possible here but it's linear. Remember that the Response HTML leaving to the user is a stream and isn't all sent in a single moment. You can't render a later part then an earlier one, directly into source, but you can render linearly over time. You could start slow operations early and provide a TaskCompletionSource, and let slower items in the page await the resulting Task, so they can load in parallel and be ready as soon as possible. If they're very long you could precache with sameDixon

© 2022 - 2024 — McMap. All rights reserved.