Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC async method in constructor controller

I'm trying to make dynamic menu (stored in DB), that is showing on all web app pages. Using Google I found that it is better to make menu view as a part of Master View (_Layout.cshtml). And because of that, every action method of the controller must contain data with the menu model. To avoid code duplication I found the solution to create a base controller and provide data using its constructor:

https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/views/passing-data-to-view-master-pages-cs

Also, I'm trying to use async/await possibilities and my PageService (menu) is using ToListAsync() to get data from DB. So now I have a problem, that BaseController constructor has an async method:

public class BaseController : AsyncController, IBaseController
{
    private readonly IPageService _pageService;

    public BaseController(IPageService pageService)
    {
        _pageService = pageService;
        SetBaseViewModelAsync();
    }

    private async Task SetBaseViewModelAsync()
    {
        ViewData["Pages"] = await _pageService.GetAllAsync();
    }
}

I know that this is BAD CODE, but I don't know how to design this situation properly. Maybe there is another better way to create the dynamic menu or another way to get data asynchronously?

Also, I found this article, but I don't know if I can apply its solutions because I don't know if I can handle controller instance creation:

http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html

like image 435
Dima Glushko Avatar asked Dec 22 '17 17:12

Dima Glushko


People also ask

Can I use async in constructor?

Since performing an asynchronous call to its completion in the constructor is not an option, we can still start a call in the constructor. We'd start it in the constructor, save the unsettled Promise in an instance variable, and then await for its completion in the methods that need it.

How do you call async method in controller?

The first thing to do is to add the async keyword to Action Method. If we use the async Keyword in Method, the Method must also use await Keyword. The return type of an async method must be void, Task or Task<T> we have used Task<T> in Action Method.

Can I call async method in constructor C#?

A simple answer for that: No, we can't! Currently, class constructors do not return types, and an asynchronous method should return a Task type.

Can a controller method be async?

As a quick reminder, you can make any existing controller method asynchronous by changing it to return a Callable. For example a controller method that returns a view name, can return Callable<String> instead. An @ResponseBody that returns an object called Person can return Callable<Person> instead.


1 Answers

Instead of deriving everything from a base controller (which can be a lot of extra work and testing) you can just create a controller called MenuController, create a method called Default and then call it from your Layout:

[ChildActionOnly]
public Default()
{
  var viewModel = _pageService.GetAllAsync();
  return Partial(viewModel);
}

in your layout:

@{Html.RenderAction("Default", "Menu");}

This is really the easiest and cleanest solution. The biggest PRO is that you can control the cache for the menu separate from the method calling. There are no good solution for asp.net-mvc (1-5) to run Async code in this fashion. (ActionFilters can't be async and (Render)Partials can't be async. You can still call an async method, it will just run Sync.

Render vs Non-Render Performance.

like image 177
Erik Philips Avatar answered Oct 03 '22 04:10

Erik Philips