asp.net Core View Component executed but markup not rendered
Asked Answered
D

2

9

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.
Diffident answered 29/6, 2017 at 19:5 Comment(2)
That really is perplexing. I did a quick review of the code in your question and the answer isn't obvious to me. I've been coding in Asp.Net Core pretty much all day ever day since RC2 and using VS2017 pretty much since it shipped and I find that while the tooling is lacking a bit, Asp.Net Core is vary stable. That said, your example seems to defy reason especially given where you have set breakpoints and observed execution. I will be interested to see what the answer turns out to be. Sorry I'm not more help on this one. You might try a much simpler ViewComponent, and build from there.Valerivaleria
@Valerivaleria yup! I actually meant the tooling (thanks for the correction) not the Core itself. Often times I find my VS.2017 crapping out during startup, meaning I have to delete the whole AppData\15.2 folder so that it can reset itself. That means that every time that happens (often) I find myself loosing all my tooling configuration. I have been using VS since 2002 and never saw a VS version as crappy as 2017. I too really don't see why the HTML is not being rendered at all.Diffident
L
20

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

Living answered 29/6, 2017 at 22:8 Comment(1)
if I use it without bracktes or parenthesis I get an error saying "Cannot await method group". It has to be with parenthesis. Now it got rendered. Thanks a million!Diffident
V
2

try this, your mileage might vary. :)

@if (User.Identity.IsAuthenticated)
{

    <div>User: @User.Identity.Name</div>
    @(await Component.InvokeAsync("DefaultNavbar"));

    @RenderBody()
}
else
{
    <div> show public pages</div>
    @RenderBody()
}
Veolaver answered 16/5, 2018 at 9:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.