2

I have a Layout with a @RenderBody section and an index page. My index page has a long running process and I want it renders the view without waiting for DoSomeAsyncStuff. The following code looks close to what I want but the problem is with my model that it's properties are null when pass to view:

public ActionResult Index()
{
    MyModel model = new MyModel();
    Task.Run(() => DoSomeAsyncStuff(model));
    return View(model);
}

private async void DoSomeAsyncStuff(MyModel model)
{
    await Task.Delay(20000);
    model.Name = "Something";
    //Assigning other model properties
}

Here in my view I get NullReferenceException and Value cannot be null errors and certainly it is because my model's properties are not still filled in the DoSomeAsyncStuff method:

<table>
<tr>
    <th colspan="3">
        @Model.Products.Select(c => c.Date).FirstOrDefault()
    </th>

</tr>

@foreach (var item in Model.Products)
{
    <tr>
        <td>
            @item.Title
        </td>
        <td>
            @item.Price
        </td>
    </tr>
}
</table>
  • 3
    Assuming your page can be rendered in some fashion without data, your only real option is to use some sort of Ajax to load in your bound markup. jQuery's [load](http://api.jquery.com/load/) function would work for that, for instance. – Tieson T. Aug 10 '17 at 06:15

2 Answers2

3

You haven't shown your model, so this will be mostly pseudo-code. First, move the long-running stuff to another action:

public ActionResult Index()
{
    var model = new MyModel();

    return View(model);
}

public async Task<ActionResult> DoSomeAsyncStuff()
{
    var model = new MyModel();
    await Task.Delay(20000);

    model.Name = "Something";
    //Assigning other model properties

    return PartialView("_InnerView", model);
}

Everything that is model-bound should be in the partial view (what I'm calling _InnerView.cshtml, here). The parent view should just have a placeholder or loading widget where your model-bound markup currently resides:

<div id="load-with-ajax">
    Please wait. Loading...
</div>

Then, somewhere in the page, after your jQuery reference (I'm assuming you're using jQuery or are willing to), add something like:

<script>
    $(function(){
        $('#load-with-ajax').load('@Url.Action("DoSomeAsyncStuff")');
    });
</script>
Tieson T.
  • 20,774
  • 6
  • 77
  • 92
  • Thanks. It looks good but when I test it never calls "DoSomeAsyncStuff". Am I missing something? –  Aug 10 '17 at 06:54
  • 1
    That's hard to answer without seeing a more complete version of what you're doing. What are you doing to verify that it "never calls" the action? Are you watching the developer console, or are you waiting for a breakpoint to be hit in Visual Studio? Either one _should_ work. – Tieson T. Aug 10 '17 at 06:58
  • @SteveCode If you were copying any of this verbatim, there was a typo in the jQuery selector that you'll need to fix. – Tieson T. Aug 10 '17 at 07:06
  • Do you see a request in the Network tab of your browser's developer console? If not, you probably have an error somewhere in your JavaScript code. – Tieson T. Aug 10 '17 at 07:11
  • You can use something like Font Awesome and it's [animation classes](http://fontawesome.io/examples/#animated). Otherwise, just replace the text or add an `` tag for an animated gif. For example: https://preloaders.net/en/free. If you want to block out the whole page, [Block UI](http://malsup.com/jquery/block/) works well. – Tieson T. Aug 10 '17 at 07:17
  • Thanks for your thorough answer again but now with usage of this ajax option I'm confused why should I still use async await keywords? Aren't them useless now? –  Aug 10 '17 at 07:54
  • And specially in the web I can't see any blocking UI like winforms, so why I should use async await keywords still? –  Aug 10 '17 at 07:55
  • I asked a new question about this confusion right now https://stackoverflow.com/questions/45608534/confused-with-async-await-in-mvc –  Aug 10 '17 at 08:41
  • In theory, using async/await allows the runtime to use it's threads more effectively, but it's not a straightforward process. Depending on what your long-running process is actually doing, you probably can get rid of the async/await bits. – Tieson T. Aug 10 '17 at 08:41
0

You need to apply async and await to your methods. This will ensure the model is populated when it is passed into your view.

public async Task<ActionResult> Index()
{
    var model = new MyModel();
    await DoSomeAsyncStuff(model);
    return View(model);
}

private async Task DoSomeAsyncStuff(MyModel model)
{
    await Task.Delay(20000);
    model.Name = "Something";
    //Assigning other model properties
}

The only other alternative is to do this in two calls.

public ActionResult Index()
{
    return View();
}

public async Task<ActionResult> PartialIndex()
{
    var model = new MyModel();
    await DoSomethingAsync(model);
    return PartialView(model);
}

private async Task DoSomeAsyncStuff()
{
    await Task.Delay(20000);

    model.Name = "Something";
    //Assigning other model properties
}

Either way in order to ensure the model is populated, you have to wait for the async method to return. The second method could potentially be tweaked to something closer to what you are looking for.

Brandon Griffin
  • 348
  • 1
  • 8
  • But by awaiting "DoSomeAsyncStuff" it has to wait until the method's execution finish then render the View. Not? Check the question's title again please. I want to render the page without waiting for data to be available in the "DoSomeAsyncStuff" long process. –  Aug 10 '17 at 06:35
  • Yes, it would. Can't really be helped though. Either you load the entire page and await the model returning a view or you load an empty page then make a second call to get a partial view and await the model in that method to hydrate the page. – Brandon Griffin Aug 10 '17 at 06:37