As far as I understand the Partial pages have only the cshtml code, while the ViewComponent have only the cs code.
Perhaps my approach is wrong, but I'm trying to render a complete page (cshtml + cshtml.cs) in another.
Real example.
Visual Studio 2017, ASP.NET Core 2.
NavBar.cshtml:
#page
#model MyProject.NavBarModel
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link" asp-page="/Machines">#Resources.Machines</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">#Resources.Orders</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" asp-page="/RunningOrders">#Resources.Running <span class="badge badge-primary">#ViewData["running"]</span></a>
<a class="dropdown-item" asp-page="/OpenOrders">#Resources.Opened <span class="badge badge-primary">#ViewData["running"]</span></a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" asp-page="/SuspendedOrders">#Resources.Suspended <span class="badge badge-danger">#ViewData["running"]</span></a>
</div>
</li>
<li class="nav-item"><a class="nav-link" asp-page="/Tables">#Resources.Lists</a></li>
<li class="nav-item"><a class="nav-link" asp-page="/About">#Resources.About</a></li>
</ul>
</div>
NavBar.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using MyProject.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MyProject
{
public class NavBarModel : PageModel
{
private readonly MyContext _context;
public NavBarModel(MyContext context)
{
_context = context;
}
public List<Order> Orders { get; set; }
public async Task OnGetAsync()
{
Orders = await _context.Orders.ToListAsync();
ViewData["open"] = _context.Orders.Where(x => x.State == OrderStates.Open).Count();
ViewData["running"] = _context.Orders.Where(x => x.State == OrderStates.Running).Count();
ViewData["suspened"] = _context.Orders.Where(x => x.State == OrderStates.Suspended).Count();
}
}
}
_Layout.cshtml:
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top mb-2">
<a asp-page="/Index" class="navbar-brand">Production Assistant</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
#Html.Partial("NavBar", new NavBarModel())
</nav>
<div class="container body-content">
#RenderBody()
</div>
</body>
In this example I'm trying to add a Badge in the Navbar. Because the _Layout file has no code behind I had to put that in a separate Page (NavBar).
The problem is that when I try to render the page it asks for the constructor parameters. I'm afraid I've broken the dependency injection in this way.
Related
We have a starting page that is declared like this.
#page "/"
#inherits PageBase
#using GroupTool.Blazor.Core
#using Microsoft.Extensions.Configuration
#using GroupTool.Blazor.Extensions
#inject IConfiguration _configuration
#using Microsoft.AspNetCore.Authorization
#attribute [Authorize]
#inject DialogService _dialogService
<div class="container linkpagecontainer">
<div class="col-md-12 linkpagerowdiv">
<div class="row linkpagerow">
<div class="col col-md-5 d-none d-sm-flex flex-column grouplistbox groupboxleft">
The MainLayout used with this page is like this.
#inherits LayoutComponentBase
#using Microsoft.AspNetCore.Components.Authorization
#inject ILocalStorageService localStore
#inject NavigationManager NavigationManager
#using Blazored.LocalStorage;
#using Wur.GroupTool.Blazor.Core.Infrastructure;
<AuthorizeView>
<Authorized>
<div class="page mainpage">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4 auth">
#if (DataLoaded)
{
<div class="main-header"><h2>#CourseDetails.Name #CourseCode Period: #Year-#PeriodNumber</h2></div>
}
<LoginDisplay />
</div>
<div class="content px-4 maincontent">
<RadzenNotification />
<RadzenTooltip />
<RadzenDialog />
#Body
</div>
</div>
</div>
</Authorized>
<NotAuthorized>
<div class="page">
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
</div>
<div class="content px-4">
#Body
</div>
</div>
</div>
</NotAuthorized>
</AuthorizeView>
As you can see a NavMenu is added which is like this.
<div class="top-row pl-4 navbar navbar-dark navbarheader">
<a class="navbar-brand" href=""><img src="css/images/WUR_W.png" height="20"/> GroupTool</a>
<button class="navbar-toggler" #onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="#NavMenuCssClass" #onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-people" aria-hidden="true"></span> Groups
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="linkgroups">
<span class="oi oi-link-intact" aria-hidden="true"></span> Link Groups
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="schedule">
<span class="oi oi-calendar" aria-hidden="true"></span> Schedule
</NavLink>
</li>
</ul>
</div>
<footer class="footer">
<div class="container-fluid">
© #DateTime.Today.Year WUR<br />version #System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()
</div>
</footer>
#code {
private bool collapseNavMenu = false;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
Now the problem is that when I go to the startpage without a query string the menu shows like this.
When however I add a querystring like f.i. so.
https://localhost:44381/?CourseId=13890
The menu show up without the default set.
What do I need to do to get that working?
==EDIT==
I think it's a bug in Blazor. Take a look here: https://code-maze.com/query-strings-blazor-webassembly/ The article explains how to use query strings in Blazor but notice what happens to the menu in the images on the page. Exactly my problem.
==EDIT==
Reported a bug on Github: https://github.com/dotnet/aspnetcore/issues/31312
If I've understood your question correctly, what you want is to change
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
to
<NavLink class="nav-link" href="/" Match="NavLinkMatch.All">
this will allow the navlink matching to work with queryparameters
My goal is to get some data passed to common layout code. Code snippets of what I have but the value of linkedCyclones is not cascading to the lower component NavLinkedCyclones.
Not sure who to pass data into a layout component. I thought the cascade would do the trick but I'm either doing it wrong or it doesn't work the same way with layouts.
The LinkedSetIds is always null in the NavLinkedCyclones.razor
I welcome any suggestions.
NavCycloneMenu.razor
<CascadingValue Value="#linkedCyclones" Name="LinkedSets">
<AuthorizeView Roles="Admin, Tier2, Tier3">
<Authorized>
<li class="nav-item px-3">
<NavLink class="nav-link" href="CycloneCalculator/GasMix">
<span class="oi oi-beaker" aria-hidden="true"></span> Gas Mix
</NavLink>
</li>
</Authorized>
</AuthorizeView>
<AuthorizeView>
<Authorized>
<li class="nav-item px-3">
<NavLink class="nav-link" href="CycloneCalculator/CycloneSizing">
<span class="oi oi-calculator" aria-hidden="true"></span> Cyclone Sizing
</NavLink>
</li>
</Authorized>
</AuthorizeView>
</CascadingValue>
#code {
private int[] linkedCyclones { get; set; }
protected override void OnInitialized()
{
linkedCyclones = new int[] { 101, 202, 303 };
}
}
GasMix.razor
#layout CycloneLayout;
<div>
<h1>Gas Mix</h1>
</div>
CycloneLayout.razor
#inherits LayoutComponentBase
<div class="sidebar">
<NavCycloneMenu />
</div>
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
<a class="top-row-text" href="https://www.example.com/" target="_blank">About</a>
</div>
<div class="content px-4">
<NavLinkedCyclones />
#Body
</div>
</div>
NavLinkedCyclones.razor
<fieldset>
<legend>Linked Cyclones</legend>
#for (int i = 0; i < LinkedSetIds.Length - 1; i++)
{
<img class="img-linked-cyclone" src="images/CycloneLogoNoWhite.png" alt="Cyclone"> Cyclone #(i+1)
<div class="oi oi-arrow-thick-right" aria-hidden="true"></div>
<div class="oi oi-arrow-thick-right" aria-hidden="true"></div>
<div class="oi oi-arrow-thick-right" aria-hidden="true"></div>
}
<img class="img-linked-cyclone" src="images/CycloneLogoNoWhite.png" alt="Cyclone"> Cyclone #LinkedSetIds.Length
</fieldset>
#code{
[CascadingParameter(Name = "LinkedSets")]
public int[] LinkedSetIds { get; set; }
}
I try create dynamic menu but i have problem with adding parameters in link to the controller and action, can i add parameter to url.content? or there are the other way please give your insight it will very helpfull
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Menu Example</a>
</div>
<ul class="nav navbar-nav">
#if (Session["Menu"] != null)
{
var MenuMaster = (List<Accountstatement.Models.MenuModel>)Session["Menu"];
var groupByMenu = MenuMaster.GroupBy(x => x.MainMenuName).ToList();
foreach (var MenuList in groupByMenu)
{
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">#MenuList.Key<span class="caret"></span></a>
<ul class="dropdown-menu">
#foreach (var SubMenuList in MenuList)
{
<li>#SubMenuList.SubMenuName</li>
}
</ul>
</li>
}
}
</ul>
this is the part the url.content is, how can i put the parameter inside url.content?
<li>#SubMenuList.SubMenuName</li>
Use #Html.Raw with Url.Content and pass parameters like below:
#Html.Raw(Url.Content("~/"+#SubMenuList.ControllerName +"/"+ #SubMenuList.ActionName?param=" + param.value))
I have created a very simple ASP.NET Core 3.1 WebApp that uses Razor pages rather than MVC. I want to add a dynamic dropdown list to the top menu bar which is shared by all other pages. I've added the necessary HTML to _Layout.cshtml so I get a nice dropdown box along with my other menu content (Home, About, etc) - see below.
What are my options for adding the code that populates the dropdown in my _Layout.cshtml file?
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index"><img src="images/Logo.png" alt="logo" height="50"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li>
<li class="nav-item dropdown ml-auto">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownTZ" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Whisky
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Gin</a>
<a class="dropdown-item" href="#">Vodka</a>
</div>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
#RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
XXX © 2020 - v1.1.30.1 - your use of this website is subject to our <a asp-area="" asp-page="/TermsConditions">Terms & Conditions</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
#RenderSection("Scripts", required: false)
</body>
</html>
A reasonable solution is to create a View Component and then add it to _Layout.cshtml. I found a good step-by-step guide by Peter Kellner (Progress) which helped me on my way. Essentially my solution involved:
Creating a folder under Pages called 'Components'
Creating a subfolder under 'Components' called 'TimeZoneControl'
Adding a class called 'TimeZoneControlViewComponent.cs' in 'TimeZoneControl'
Adding a Razor View called 'Default.cshtm' in 'TimeZoneControl'
Modifying my _Layout.cshtml to include my TimeZoneControl View Component
Adding #addTagHelper *, MyWebApp to the end of _ViewImports.cshtml where MyWebApp is the name of my Visual Studio webapp project.
In terms of code:
TimeZoneControlViewComponent.cs - ViewComponents support dependency injection so it is possible to put code into the constructor to support database access as described in Microsoft Docs
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace C19QuarantineWebApp.Pages.Components.TimeZoneControl
{
public class TimeZoneControlViewComponent : ViewComponent
{
public TimeZoneControlViewComponent() { }
public IViewComponentResult Invoke(string timeZoneId)
{
var timeZones = new List<string>();
timeZones.AddRange(new[] {"GMT", "CET", "IST"});
return View("Default", timeZones.ToArray());
}
}
}
Default.cshtml
#model string[]
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownTZ" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Time zones
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
#foreach(var tz in Model) {<a class="dropdown-item" href="#">#tz</a>}
</div>
_Layout.cshtml - replace the original dropdown (see question) with
<vc:time-zone-control time-zone-id="xxx">
</vc:time-zone-control>
In context this gives:
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
</li>
<li class="nav-item dropdown ml-auto">
<vc:time-zone-control time-zone-id="xxx">
</vc:time-zone-control>
</li>
</ul>
</div>
I would be interested to receive comments on this solution. Is there an easier and more elegant way to do this?
I would say for a better Razor solution not using view components (MVC) I would create an class and use #inject on your menu page. Fewer steps. See below.
Build your MenuService Class. Here is the example:
public class MenuService
{
private readonly CoreContext _dbContext;
public MenuService(CoreContext dbContext)
{
_dbContext = dbContext;
}
public IEnumerable<tblMenu> GetMenuMaster()
{
return _dbContext.tblMenu.AsEnumerable();
}
public IEnumerable<tblMenu> GetMenuMaster(string UserRole, bool isAdmin)
{
if (isAdmin == false)
{
var result = _dbContext.tblMenu.Where(m => m.UserRole != "Admin" ).ToList();
return result;
}
else
{
var result = _dbContext.tblMenu.Where(m => m.Something == `UserRole).ToList();`
return result;
}
}
}
Then finally, AddTransient to startup.cs:
services.AddTransient<MenuService, MenuService>();
Now in the _Layout.cshtml you can add the menu:
#using System.Security.Claims;
#inject Solution1.Services.MenuService menus
<!DOCTYPE html>
<html>
...rest of your _Layout.cshtml page here
Now within your HTML for the menu...customize as needed.
#foreach (var subMenu in menus.GetMenuMaster(role1.Value, admin))
{
<a class="dropdown-item" asp-page="./pathtopage" asp-page-handler="List" asp-route id="#subMenu.Program"><i class="#subMenu.Class" title="history"></i>#subMenu.DisplayName
</a>
}
I'm struggling to share a properties value across components in Blazor. Basically I have a button with an onclick event this sets a value on a function depending on the value another cshtml page should react to the value and it doesn't. The issue is that because both cshtml pages inherit the same function they have their own instance of the function. Here's my code so far:
the Function:
public class MenuFunctions : BlazorComponent, IMenuFunctions
{
public bool CollapseNavMenu
{
get ; set;
}
public void ToggleNavMenu()
{
CollapseNavMenu = !CollapseNavMenu;
}
}
The main button on NavMenuToggleComponent.cshtml:
#inherits MenuFunctions
<div class="pl-4 navbar navbar-dark">
<button class="navbar-toggler navbar-brand main-button" title="MENU" onclick=#ToggleNavMenu>
MENU
</button>
</div>
my NavMenu.cshtml file:
#inherits MenuFunctions
<div class="#(CollapseNavMenu ? "collapse" : null)" onclick=#ToggleNavMenu>
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="home">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="frontpage">
<span class="oi oi-home" aria-hidden="true"></span> Front Page
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match=NavLinkMatch.All>
<span class="oi oi-account-login" aria-hidden="true"></span> Login
</NavLink>
</li>
</ul>
</div>
putting it all together in my MainLayout.cshtml
#inherits BlazorLayoutComponent
<NavMenuToggleComponent></NavMenuToggleComponent>
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4">
About
<NavLink class="nav-link pull-right" href="logout">
<span class="oi oi-account-logout" aria-hidden="true"></span> Logout
</NavLink>
</div>
<div class="content px-4">
#Body
</div>
</div>
so when I click the button in NavMenuToggleComponent.cshtml I want the CollapseNavMenu to react in the NavMenu.cshtml file
If I remove the use of the interface, then inject the components onto the pages as opposed to Inherit and then add the following into the startup:
services.AddSingleton<MenuFunctions>();
the pages will load, but I still get the same issue.
The solution here would be to bring the shared property to the place where it is common, in this case the MainLayout.cshtml and not make the components dependent on a common interface.
So the MainLayout.cshtml would hold the state in a new property and that property is bound to the two components.
You should not use DI for component classes, only for service providing classes and alike.