The Story of My Life

Diary of a noob programmer

Access dynamic data in master layout and partials in .Net Core

Few day ago, I was searching desperately to access my user context within my Partial Views; during the way I found ways to do it using ASP.NET MVC, but none worked neither existed in ASP.NET Core MVC.

I just wanted to share those few ways, although they may still disappoint you and each of which may not usable in all scenario.

In the rest of this article I will list this ways:

1. Using ViewComponent

ViewComponent is just another Model-View-Controller way of creating reusable component, that can be rendered either using @await Component.InvokeAsync(typeof(<YourComponent>)) or using <vc:[your-component-name]> </vc:[your-component-name]>.

The component contain a constructor, which can be used to initialize registered objects. and a method which is called InvokeAsyncand returns an object based on IViewComponentResult interface.

I provide a sample code which shows how to use it to provide a simple flag to it’s view, and render something conditionally. The component can reside in both the main project, or within it’s area. also like a normal controller, the MVC engine will look for it’s views in several paths.

I show how I did it.

~/Components/MenuSidebarViewComponent.cs
using System.Threading.Tasks;
using Judgel.Data;
using Judgel.Web.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace Judgel.Components
{
    // Action filters doesn't work here, you may want to create a BaseViewComponent and inherit you code from, in case you need to write some reusable codes.
    [ViewComponent(Name = "MenuSidebar")]
    public class MenuSidebarViewComponent : ViewComponent
    {
        private readonly UserManager<ApplicationUser> _userManager;

        public MenuSidebarViewComponent(UserManager<ApplicationUser> userManager)
        {
            _userManager = userManager;
        }

        public async Task<IViewComponentResult> InvokeAsync(
            int maxPriority, bool isDone)
        {
            var userId = HttpContext.User.GetUserId();
            var user = await _userManager.FindByIdAsync(userId);
            
            ViewData["IsAdmin"] = await _userManager.IsInRoleAsync(user, "Admin");

            return View(); // You can also work with model
        }
    }
}
~/Views/Shared/Components/MenuSidebar/Default.cshtml
<div class="sidebar-content">
    <div class="nav-container">
        <ul id="main-menu-navigation" data-menu="menu-navigation" data-scroll-to-active="true" class="navigation navigation-main">
            <li class=" nav-item">
                <a href="chat.html"><span data-i18n="" class="menu-title">&nbsp;</span></a>
            </li>
            
            <!--Here we used data that came from component.-->
            @if ((bool)ViewData["IsAdmin"])
            {
                <li class="has-sub nav-item">
                    <a href="javascript:void(0);"><i class="ft-home"></i><span data-i18n="" class="menu-title">System Administrator</span><span class="tag badge badge-pill badge-danger float-right mr-1 mt-1">2</span></a>
                    <ul class="menu-content">
                        <li>@Html.ActionLink("Members", "Index", "Members", null, new {@class = "menu-item"})</li>
                        <li>@Html.ActionLink("Services", "Index", "Services", null, new {@class = "menu-item"})</li>
                        <li>@Html.ActionLink("Providers", "Index", "Provider", null, new {@class = "menu-item"})</li>
                    </ul>
                </li>
            }

            <li class="has-sub nav-item">
                <a href="javascript:void(0);"><i class="ft-grid"></i><span data-i18n="" class="menu-title">Service Providers</span></a>
            </li>
        </ul>
    </div>
</div>

2. Using Server-side Scripting Delimiters

Although it may be so simple to mention, for me it was so typical, that I couldn’t even think about it… But still there is another issue too; we want to keep our codes separately.

server-side scripting delimiters (AKA ASP.NET inline expressions), or Razor delimiters, can be used to write code within our Views.

Here’s the solution:

We can define a custom class, here i named it LayoutService, and then within this class i wrote my custom method, these method do not return Views, we are inside view, we just need data.

~/LayoutService.cs
public class LayoutService
{
    private readonly UserManager<ApplicationUser> _userManager;

    public LayoutService(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    public ApplicationUser GetUser(string userId)
    {
        return _userManager.FindByIdAsync(userId).Result;
    }
}
~/Views/Shared/_Layout.cshtml
@{
    var di = GlobalObjects.GetInstance().AutofacContainer;
    LayoutService layoutService = di.Resolve<LayoutService>();

    var userId = Context.User.GetUserId(); //I am able to access context within _layout file, maybe it get filled by the base Controller, but other partials are not able to access these data.
    ApplicationUser user= layoutService.GetUser(userId);
}

Here I wanted to access my Autofac Container to do DI (Dependency Injection) operation… but you can just new your class.

I’ll talk about it in the next post.

In case you need to access these data, within partials loaded in _layout, you can just pass them as model, while rendering them…

@await Html.PartialAsync("_MenuFooter", user)

3. Using client side framework

In this method, we can create a Layout-specific controller, and try to load our data into view, using that controller, then using JavaScript, we render what is required to be rendered.

~/Controllers/LayoutController.cs
namespace Judgel.Controllers
{
    public class LayoutController : BaseAdminController
    {
        private readonly IProviderService _providerService;

        public LayoutController(IProviderService providerService )
        {
            this._providerService = providerService;
        }

        public IActionResult GetSidebarData()
        {
            var providers = _providerService.GetProviderList();
            return Json(new
            {
                Menu = new
                {
                    Provider = providers
                }
            });
        }
    }
}

~/Views/Shared/_Layout.cshtml

<script>
    // LOAD MENU
    $(document).ready(function () {
        const url = '@Url.Action("GetSidebarData", "Layout")';
        $.get(url).done(function(data) {
            const providerMenuData = data.menu.provider;
            const providerMenuContainer = $('#providerMenuContainer');
            const firmBaseUrl = '/Admin/Firm/Index';
            for (let menuItem of providerMenuData) {
                providerMenuContainer.append($(`<li><a class="menu-item" href="${firmBaseUrl}/${menuItem.id}">${menuItem.providerName}</a></li>`))
            }
        }).fail(function() {
            throw new Error("Cannot connect to the server.");
        });
    });
</script>

Note that most frameworks like jQuery, mostly added at the end of page, due to that, you cannot call the JavaScript in your partial, and you should do it at the end of your main layout. just define the place holder to access them, for example here i have a <div> named #providerMenuContainer and I use this name (ID) to access and fill it using dynamic data fetched from the service.

 

If you have any other method, feel free to share it with us.

 

Thank you for reading,

Hassan Faghihi.

Leave a Reply

Your email address will not be published. Required fields are marked *.

*
*
You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>