Search code examples
asp.net-coreasp.net-core-mvcasp.net-core-viewcomponent

asp.net Core View Component executed but markup not rendered


I have an ASP.NET Core 1.1 web application developed with VS.2017 and I decided to put some of the view functionality in a view component (have done others before).

This view component fetches a Dictionary collection of permissions associated to a user ID and displays them in a nice table. When I put it as part of the page (not VC) it works. But when I use it as a view component the component's HTML is never rendered.

I placed a breakpoint in the view component and it triggers, I see the View(granted) return statement return a populated list so up until there execution is as expected.

Then I placed a breakpoint in the ViewComponent's default.cshtml code section at the top the @{ some code here } and that breakpoint triggers as well, so the view component's Default.cshtml file is found. This Default.cshtml has some markup to render a table, therein within the table I have a @foreach() statement and when do a "Run to cursor" to that precise location -the loop that iterates- it triggers as well so it is iterating through the collection.

But after all that the main view looks as if the view component isn't there, none of the HTML found in the Default.cshtml is rendered even though it was found and executed. What am I missing here? so far my impression has been that VS.2017 (with all its updates) is not very stable.

Default.cshtml

@using ACME.AspNetCore.Permissions.Infrastructure.Authorization
@model Dictionary<Permission, string>
@{ 
    ViewBag.UserName = "xxx";
    Model.Add(Permission.Permission1, "test");
}

<h1>Component Output</h1>
<div class="well well-lg">
<table class="table table-hover">
    <thead>
        <tr>
            <th>Permission</th>
            <th>Description</th>
            <th class="text-center">Status</th>
            <th class="text-center">Revoke it!</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var dictentry in Model)
        {
            <tr>
                <td>@dictentry.Key.ToString()</td>
                <td>@dictentry.Value</td>
                <td class="text-center"><span class="glyphicon glyphicon-ok" style="color:green;"></span></td>
                <td class="text-center"><a asp-action="RevokePermission" asp-route-id="@ViewBag.UserName" asp-route-pid="@dictentry.Key.ToString()"><span class="glyphicon glyphicon-thumbs-down" style="color:red;"></span></a></td>
            </tr>
        }
    </tbody>
    <tfoot><p class="alert alert-success"><span class="glyphicon glyphicon-eye-open"></span> Granted permissions</p></tfoot>
</table>
</div>

GrantedPermissionsViewComponent.cs

 [ViewComponent(Name = "GrantedPermissions")]
 public class GrantedPermissionsViewComponent : ViewComponent {
     private readonly ApplicationDbContext _context;
     public GrantedPermissionsViewComponent(ApplicationDbContext context) : base()
    {
        _context = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(string emailOrUserId)
    {
        string id;
        Guid UID;
        if (Guid.TryParse(emailOrUserId, out UID))
        {   // THE PARAMETER WAS A GUID, THUS A USER ID FROM IDENTITY DATABASE
            id = emailOrUserId;
        }
        else
        {   // THE PARAMETER IS ASSUMED TO BE AN EMAIL/USERNAME FROM WHICH WE CAN DERIVE THE USER ID
            id = _context.Users.Where(u => u.Email == emailOrUserId.Trim()).Select(s => s.Id).FirstOrDefault();
        }

        Dictionary<Permission, string> granted = GetOwnerPermissions(id);
        return View(granted);            
    }

    private Dictionary<Permission, string> GetOwnerPermissions(string userId) 
    {
        Dictionary<Permission, string> granted;
        granted = _context.UserPermissions.Where(u => u.ApplicationUserId == userId)
                                                .Select(t => new { t.Permission })
                                                .AsEnumerable() // to clients memory
                                                .Select(o => new KeyValuePair<Permission, string>(o.Permission, o.Permission.Description()))
                                                .ToList()
                                                .ToDictionary(x => x.Key, x => x.Value);

        return granted;
    }
 }

So why on earth is it triggering on the component's code as well as on the component's view (default.cshtml) and yet it does not render the HTML code found therein?

Component invokation in the main view:

@{await Component.InvokeAsync<GrantedPermissionsViewComponent>(
    new { emailOrUserId = ViewBag.UserName });
}

NOTE

  • The InvokeAsync is actually executing synchronously (per warning) because I could not find a way to have GetOwnerPermissions to await on anything... But that is not the problem.

Solution

  • The problem lies in how you are invoking the ViewComponent.

    If you use @{ ... } it means you want to execute code and not render to output.

    If you use parenthesis instead of brackets, the result gets rendered to output. @( ... )

    In your case, you don't even need the parenthesis. Try invoking it has following:

    @await Component.InvokeAsync("GrantedPermissions", new { emailOrUserId = ViewBag.UserName })
    

    More info here