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
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With