How to pass data from Controller to _Layout.cshtml - asp.net-mvc

I show list of city in _Layout. I use a ViewBag for pass data to this,
#{
var cities = (List<CustomCityViewModel>)ViewBag.Cities;
}
<ul class="list-items">
#foreach (var city in cities)
{
<li>
<span>
#if (city.Id == null)
{
<a href="#Url.Action("Search","Home")">
#city.Name
</a>
}
else
{
<a class="city-search-item" data-name="#city.Name" data-id="#city.Id">
#city.Name
<i class="fa fa-angle-left"></i>
</a>
}
</span>
</li>
}
</ul>
I should set viewbag value in every action.
How can I send data so that it is not assigned inside each action?

//create a base Model class
public class BaseModel
{
public BaseModel(){
//here init base data to get in _layout
//sete data to cities
}
public IList<City> cites{set;get;}
}
// all other Model extend from BaseModel
// in your _Layout use BaseModel
#model BaseModel
#{
var cities =Model;
}
<ul class="list-items">
#foreach (var city in cities)
{
<li>
<span>
#if (city.Id == null)
{
<a href="#Url.Action("Search","Home")">
#city.Name
</a>
}
else
{
<a class="city-search-item" data-name="#city.Name" data-id="#city.Id">
#city.Name
<i class="fa fa-angle-left"></i>
</a>
}
</span>
</li>
}
</ul>

Related

Everything displays correctly except for the list of products of a category

I am making a webpage using ASP.NET Core MVC. It's an online store. The issue I have is that everything displays correctly except for the list of products of a category.
Category and Product have a one-to-many relationship in the SQLite database I am working with. When I list the category, id, name, description.., works superb, but the products in that category don't show.
This is the code where I have the issue. #Model.Products is the correct call, but I tried everything, from transforming it ToList(), ToString(), or searching for the products using a foreach loop:
#model Packt.Shared.Category
#{
ViewData["Title"] = "Category Details - " + Model.CategoryName;
}
<h2>Category Details</h2>
<hr/>
<div class=dl-horizontal>
<dt>Category ID</dt>
<dd>#Model.CategoryID</dd>
<dt>Category NAme</dt>
<dd>#Model.CategoryName</dd>
<dt>Category description</dt>
<dd>#Model.Description</dd>
<dt>List of products of the category</dt>
<dd>#Model.Products</dd> <!-Here it doesnt show a thing, not even with ToString() nor ToList(), also tried with Async->>
</div>
The button that launchs the code is the View button in the Index.cshtml razor page. How do I call the list of Products in the Category (1-to-many relationship)?:
#model NorthwindMvc.Models.HomeIndexViewModel
#{
ViewData["Title"] = "Radical Change";
string currentItem = "";
}
<div id="categories" class="carousel slide" data-ride="carousel"
data-interval="3000" data-keyboard="true">
<ol class="carousel-indicators">
#for (int c = 0; c < Model.Categories.Count; c++)
{
if (c == 0)
{
currentItem = "active";
}
else
{
currentItem = "";
}
<li data-target="#categories" data-slide-to="#c"
class="#currentItem"></li>
}
</ol>
<div class="carousel-inner">
#for (int c = 0; c < Model.Categories.Count; c++)
{
if (c == 0)
{
currentItem = "active";
}
else
{
currentItem = "";
}
<div class="carousel-item #currentItem">
<img class="d-block w-100"
src="~/images/category#(Model.Categories[c].CategoryID).jpeg"
alt="#Model.Categories[c].CategoryName" />
<div class="carousel-caption d-none d-md-block">
<h2>#Model.Categories[c].CategoryName</h2>
<h3>#Model.Categories[c].Description</h3>
<p>
<a class="btn btn-primary"
href="/Home/CategoryDetail/#Model.Categories[c].CategoryID">View</a>
</p>
</div>
</div>
}
</div>
<a class="carousel-control-prev" href="#categories"
role="button" data-slide="prev">
<span class="carousel-control-prev-icon"
aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#categories"
role="button" data-slide="next">
<span class="carousel-control-next-icon"
aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<div class="row">
<div class="col-md-12">
<h1>Radical Change - Hairdresser</h1>
<p class="lead">
We've had #Model.VisitorCount visitors this month.
</p>
<form asp-action="ProductsThatCostMoreThan" method="get">
<input name="price" placeholder="Enter product price" />
<input type="submit" value="Submit" />
</form>
<form asp-action="Customers" method="get">
<input name="country" placeholder="Select a country" />
<input type="submit" value="Submit" />
</form>
<h2>Productos</h2>
<div id="newspaper">
<ul>
#foreach (var item in #Model.Products)
{
<li>
<a asp-controller="Home"
asp-action="ProductDetail"
asp-route-id="#item.ProductID">
#item.ProductName costs
#item.UnitPrice.Value.ToString("C")
</a>
</li>
}
</ul>
</div>
</div>
</div>
First of all, you could not use #Model.Products to show all products' info on view.Then, your action code should use Include method to retrieve the navigation properties' value.
Finally,refer to my simple demo shown below.
1.Assume that you have below models:
public class Category
{
[Key]
public long CategoryID{ get; set; }
public string CategoryName{ get; set; }
public string Description{ get; set; }
public virtual ICollection<Product> Products{ get; set; }
}
public class Product
{
[Key]
public long ProductID{ get; set; }
public string ProductName{ get; set; }
public long CategoryId{ get; set; }
public virtual Category Category{ get; set; }
}
2.For the Home/CategoryDetail, you need to use Include method to retrieve data:
public async Task<IActionResult> CategoryDetail(long? id)
{
if (id == null)
{
return NotFound();
}
var category = await _context.Category
.Include(s=>s.Products)
.FirstOrDefaultAsync(m => m.CategoryID == id);
if (category == null)
{
return NotFound();
}
return View(category);
}
3.To show Products info on Category/CategoryDetail razor view, you need to use #foreach:
#foreach(var item in Model.Products)
{
<h3>#item.ProductName</h3>
}

MVC with Bootstrap Navbar - Set Selected Item to Active

I'm learning Bootstrap and can't get the selected item into an "active" state. The active state remains on the default item. The newly selected/clicked item changes to active briefly, but reverts back. I've read all the posts and still can't get this code to work. I'm using MVC5 and JQuery 2.1.
EDIT:
If I change the li's hrefs to href="#", then the active class gets applied properly. What's happening when a new view gets loaded? I think Sebastian's response is close, but gets messy with Areas.
Markup
<div class="navbar-wrapper">
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-header">
<a class="navbar-toggle" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="navbar-brand" href="#">Test</a>
</div>
<div class="btn-group pull-right">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-user"></i>Login
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>Profile</li>
<li class="divider"></li>
<li>Sign Out</li>
</ul>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active">Home</li>
<li>About</li>
<li>Students Sample</li>
<li class="dropdown">
Dropdown <b class="caret"></b>
<ul class="dropdown-menu">
<li>Admin</li>
<li>Another action</li>
<li>Something else here</li>
<li class="divider"></li>
<li>Separated link</li>
<li>One more separated link</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<!-- /container -->
</div>
<!-- /navbar wrapper -->
Script
<script type="text/javascript">
$(function () {
$('.navbar-nav li').click(function () {
$(this).addClass('active').siblings().removeClass('active');
});
});
</script>
EDIT: Here's what I ended up doing with the help of the posted answers and some research.
public static string MakeActive(this UrlHelper urlHelper,string action, string controller, string area = "")
{
string result = "active";
string requestContextRoute;
string passedInRoute;
// Get the route values from the request
var sb = new StringBuilder().Append(urlHelper.RequestContext.RouteData.DataTokens["area"]);
sb.Append("/");
sb.Append(urlHelper.RequestContext.RouteData.Values["controller"].ToString());
sb.Append("/");
sb.Append(urlHelper.RequestContext.RouteData.Values["action"].ToString());
requestContextRoute = sb.ToString();
if (string.IsNullOrWhiteSpace(area))
{
passedInRoute = "/" + controller + "/" + action;
}
else
{
passedInRoute = area + "/" + controller + "/" + action;
}
// Are the 2 routes the same?
if (!requestContextRoute.Equals(passedInRoute, StringComparison.OrdinalIgnoreCase))
{
result = null;
}
return result;
}
You have to check in your controller or view which menu item is active based on the current url:
I have an extension method similar to this:
public static string MakeActiveClass(this UrlHelper urlHelper, string controller)
{
string result = "active";
string controllerName = urlHelper.RequestContext.RouteData.Values["controller"].ToString();
if (!controllerName.Equals(controller, StringComparison.OrdinalIgnoreCase))
{
result = null;
}
return result;
}
You can use it in your view like this:
<!-- Make the list item active when the current controller is equal to "blog" -->
<li class="#Url.MakeActive("blog")">
....
</li>
The JavaScript isn't working because the page is getting reloaded after it runs. So it correctly sets the active item and then the page loads because the browser is following the link. Personally, I would remove the JavaScript you have because it serves no purpose. To do this client side (instead of the server side code you have), you need JavaScript to set the active item when the new page loads. Something like:
$(document).ready(function() {
$('ul.nav.navbar-nav').find('a[href="' + location.pathname + '"]')
.closest('li').addClass('active');
});
I recommend adding an id or other class to your navbar so you can be sure you have selected the correct one.
Simplest thing to do is send a ViewBag parameter from your controllers like following;
public ActionResult About()
{
ViewBag.Current = "About";
return View();
}
public ActionResult Contact()
{
ViewBag.Current = "Contact";
return View();
}
In the cshtml page do the following;
<ul class="nav navbar-nav">
<li class="#(ViewBag.Current == "About" ? "active" : "")">#Html.ActionLink("About", "About", "Home")</li>
<li class="#(ViewBag.Current == "Contact" ? "active" : "")">#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
Courtesy to #Damith in here
Simply you can do this in any view
<ul class="nav navbar-nav">
#Html.NavigationLink("Link1", "Index", "Home")
#Html.NavigationLink("Link2", "Index", "Home")
#Html.NavigationLink("Link3", "Index", "Home")
#Html.NavigationLink("Links with Parameter", "myAction", "MyController", new{ id=999}, new { #class= " icon-next" })
</ul>
After you add this method to a new class or existing HtmlExtensions class
public static class HtmlExtensions
{
public static MvcHtmlString NavigationLink(this HtmlHelper html, string linkText, string action, string controller, object routeValues=null, object css=null)
{
TagBuilder aTag = new TagBuilder("a");
TagBuilder liTag = new TagBuilder("li");
var htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(css);
string url = (routeValues == null)?
(new UrlHelper(html.ViewContext.RequestContext)).Action(action, controller)
:(new UrlHelper(html.ViewContext.RequestContext)).Action(action, controller, routeValues);
aTag.MergeAttribute("href", url);
aTag.InnerHtml = linkText;
aTag.MergeAttributes(htmlAttributes);
if (action.ToLower() == html.ViewContext.RouteData.Values["action"].ToString().ToLower() && controller.ToLower() == html.ViewContext.RouteData.Values["controller"].ToString().ToLower())
liTag.MergeAttribute("class","active");
liTag.InnerHtml = aTag.ToString(TagRenderMode.Normal);
return new MvcHtmlString(liTag.ToString(TagRenderMode.Normal));
}
}
I believe you have the selection backward. You're adding the class, then removing it from the siblings, and I think doing the remove second is causing the issue. Can you try reversing this to be:
<script type="text/javascript">
$(function () {
$('.navbar-nav li').click(function () {
$(this).siblings().removeClass('active');
$(this).addClass('active');
});
});
</script>
Your javascript function should work fine... The issue is that your links route to a controller and reload the entire page. In order to avoid this behavior you could render your body content as a partial view, that way the navbar elements do not reload. You shouldn't have to write a function to handle dom events - that is what javascript is for.
To see what I mean, change your code:
<li>About</li>
<li>Students Sample</li>
to:
<li>About</li>
<li>Students Sample</li>
Just add this JQuery coded :
<script>
$(document).ready(function () {
$('body').find('a[href="' + location.pathname + '"]')
.addClass('active');
});
</script>
With this simplified version of the code from here, you can use a tag helper to mark the anchor 'active' if the controller & action match (or just the controller if no action is supplied).
The nav:
<li class="nav-item px-2">
<a class="nav-link" asp-active-route asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item px-2">
<a class="nav-link" asp-active-route asp-controller="Car" asp-action="Add">Add Car</a>
</li>
The tag helper class
[HtmlTargetElement(Attributes = "asp-active-route")]
public class ActiveRouteTagHelper : TagHelper
{
[HtmlAttributeName("asp-controller")] public string Controller { get; set; }
[HtmlAttributeName("asp-action")] public string Action { get; set; }
[HtmlAttributeNotBound] [ViewContext] public ViewContext ViewContext { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (IsActive())
{
output.AddClass("active", HtmlEncoder.Default);
}
output.Attributes.RemoveAll("asp-active-route");
base.Process(context, output);
}
private bool IsActive()
{
var currentController = ViewContext.RouteData.Values["Controller"].ToString();
var currentAction = ViewContext.RouteData.Values["Action"].ToString();
var active = false;
if (!string.IsNullOrWhiteSpace(Controller) && !string.IsNullOrWhiteSpace(currentController))
{
active = Controller.Equals(currentController, StringComparison.CurrentCultureIgnoreCase);
if (active && !string.IsNullOrWhiteSpace(Action) && !string.IsNullOrWhiteSpace(currentAction))
{
active = Action.Equals(currentAction, StringComparison.CurrentCultureIgnoreCase);
}
}
return active;
}
}
Don't forget to add the tag helper to your _ViewImports.cshtml
#addTagHelper *, My.Awesome.Web
Future improvement would be to also check areas and possibly wrap the entire nav in a control/helper so you only have to compare the routes once and flag the item
You can use Bootstrap's nav-pills to change the color of active menu item.
Bootstrap's nav-pills to change the color of active menu item
Here is a simple solution that works - store class name in TempData:
In each action of the controller add one line:
// add "active" class to nav-item TempData["Home"] = "active";
In Layout view nav item:
<a class="nav-link #TempData["Home"]">Home</a>
Thus only one nav-item will get class "active"

MVC: Displaying table data not working as expected

I have this table in database:
I'm trying to display the the values (ModuleID and DateEntered) in the browser as table.
I'm using viewbag to pass the value to my view, which is not quite the right way as I get just one row right now. What I'm doing right now is
public ActionResult Index()
{
var modules = (from entries in _db.Modules
orderby entries.DateEntered
select entries);
ViewBag.entries = modules.ToList();
return View();
}
How can I get all rows from the table in above picture and pass it to view?
In my view I currently have:
#using BootstrapSupport
#model Sorama.DataModel.SIS.ModuleStock.Module
#{
ViewBag.Title = "Index";
Layout = "~/Views/shared/_BootstrapLayout.basic.cshtml";
}
<table class="table table-striped">
<caption></caption>
<thead>
<tr>
<th>
Module ID
</th>
<th>
Date Entered
</th>
</tr>
</thead>
<tr>
#foreach (var entry in ViewBag.entries)
{
<td>#entry.ModuleId</td>
<td>#entry.DateEntered</td>
}
<td>
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
Action
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>#Html.ActionLink("Details", "Details")</li>
#if (Request.IsAuthenticated && HttpContext.Current.User.IsInRole("Admin"))
{
<li class="divider"></li>
<li>#Html.ActionLink("Edit", "Edit")</li>
<li>#Html.ActionLink("Delete", "Delete")</li>
}
</ul>
</div>
</td>
</tr>
</table>
This shows the values of entire row and not just (ModuleID and DateEntered)
This is what I get in browser.
To sum up, I want to get all the rows from table but only specific columns.
Which is not happening at current situation.
Suggestions?
You misreading your results. There are 3 records with 2 fields each displayed in one row. You have single <tr> in your view and you insert values from ViewBag in <td> tags. You should put new <tr> for each of your record in ViewBag.
It should look more like this:
#foreach (var entry in ViewBag.entries)
{
<tr>
<td>#entry.ModuleId</td>
<td>#entry.DateEntered</td>
<td>
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
Action<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>#Html.ActionLink("Details", "Details")</li>
#if (Request.IsAuthenticated && HttpContext.Current.User.IsInRole("Admin"))
{
<li class="divider"></li>
<li>#Html.ActionLink("Edit", "Edit")</li>
<li>#Html.ActionLink("Delete", "Delete")</li>
}
</ul>
</div>
</td>
</tr>
}
Try this
public ActionResult Index()
{
ABCList abc=new ABCList();
var modules = (from entries in _db.Modules
orderby entries.DateEntered
select new ABC {
id=entries.id,
ModuleTypeId=entries.ModuleTypeId,
ModuleId=entries.ModuleId,
DataEntered=entries.DataEntered
});
abc.settings = modules.ToList();
return View();
}
public class ABC
{
public long Id{ get; set; }
public long ModuleTypeId{ get; set; }
public string ModuleId{get;set;}
public DateTime DataEntered{ get; set; }
}
public class ABCList{
public List<ABC> Settings { get; set; }
}
View
#model ABCList
#foreach (var entry in Model.Settings)
{
<tr>
<td>#entry.ModuleId</td>
<td>#entry.DateEntered</td>
<td>
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
Action<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>#Html.ActionLink("Details", "Details")</li>
#if (Request.IsAuthenticated && HttpContext.Current.User.IsInRole("Admin"))
{
<li class="divider"></li>
<li>#Html.ActionLink("Edit", "Edit")</li>
<li>#Html.ActionLink("Delete", "Delete")</li>
}
</ul>
</div>
</td>
</tr>
}

How do I call a partial view that expects a IEnumerable collection in a view that gets a model object?

I am trying to build a version of WordPress in MVC4. Currently I am working on the page view.
I've decided that in this view I should also include a menu of all the other pages that have been created.
This is my method for the showPage view:
public ActionResult showPage(string myTitle)
{
var query = from a in db.Pages
where a.title == myTitle
select a;
PageModels item = new PageModels();
item = query.FirstOrDefault<PageModels>();
if (item != null)
{
return View(item);
}
else
{
item.content = "No page with title :" + myTitle + " found.";
return View(item);
}
}
This is my method for the partial I am trying to render:
public ActionResult List()
{
return View(db.Pages.ToList());
}
This is how my view looks like:
#model Dynamic_Web_Pages.Models.PageModels
#{
ViewBag.Title = "Page Preview";
}
#Html.Partial("_ListPartial", new IEnumerable<Dynamic_Web_Pages.Models.PageModels>)
<div class="content">#Html.Raw(Model.content)</div>
Finally this is my partial:
#model IEnumerable<Dynamic_Web_Pages.Models.PageModels>
<div class="dropdown">
#foreach (var page in Model)
{
if(page.parent == 0)
{
<div class="btn-group">
<a class="btn dropdown-toggle" id="#Html.DisplayFor(modelItem => page.id)" role="button" data-toggle="dropdown" data-target="#" href="/#Html.DisplayFor(modelItem => page.title)" >#Html.DisplayFor(modelItem => page.title)
<b class="caret"></b>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="#Html.DisplayFor(modelItem => page.id)">
#foreach (var child in Model)
{
if (child.parent == page.id)
{
<li><a class="child" href="/#Html.DisplayFor(modelItem => child.title)" >#Html.DisplayFor(modelItem => child.title)</a></li>
}
}
</ul>
</div>
}
}
</div>
I get the following error in my view:
Cannot create an instance of the abstract class or interface 'System.Collections.Generic.IEnumerable<Dynamic_Web_Pages.Models.PageModels>'
What should be the second argument of the #Html.Partial be?
Your view accepted a single instance of PageModels
#model Dynamic_Web_Pages.Models.PageModels
#{
ViewBag.Title = "Page Preview";
}
and yet you are passing that in your partial that accepts an IEnumerable and so you got that exception.
Now based on our conversation in your comments, you can load the partial using jquery:
<script>
$("#navmenu").load('#Url.Action("List")');
</script>
where you replace this code:
#Html.Partial("_ListPartial", new IEnumerable<Dynamic_Web_Pages.Models.PageModels>)
// with this
<div id="navmenu"></div>
remove new IEnumerable
and just keep it as
Html.Partial("_ListPartial") in the view
So you should return PartialView instead of view see below code
public ActionResult List()
{
return PartialView (db.Pages.ToList());
}

Knockout and MVC Collection Binding

Im trying to get started with Knockout in MVC and managed to get the normal binding/mapping working, however I am stuck when it comes to collections. So far I have the following code:
public class TestViewModel
{
public TestViewModel()
{
Persons = new List<Person>();
}
public List<Person> Persons { get; set; }
}
public class Person
{
public Person()
{
}
public string Name { get; set; }
public string Surname { get; set; }
public List<string> Children { get; set; }
}
cshtml code:
<h2>People</h2>
<div data-bind="template: { name: 'person-template', foreach: persons }"></div>
<script type="text/html" id="person-template">
<ul>
<li>
<div><span data-bind="text: name"/> <span data-bind="text: surname"/> has <span data-bind='text: children().length'/></div>
<ul data-bind="foreach: children">
<li><span data-bind="text: $data"> </span></li>
</ul>
</li>
</ul>
</script>
<script type="text/javascript">
var TestModel = function(model) {
var self = this;
self.persons = ko.observableArray(ko.utils.arrayMap(model.Persons, function(person) {
var per = new Person(person);
return per;
}));
};
var Person = function(person) {
var self = this;
self.Name = ko.observable(person.Name);
self.Surname = ko.observable(person.Surname);
self.Children = ko.observableArray(person.Children);
};
$(function() {
var data = #(Html.Raw(Json.Encode(Model)));
ko.applyBindings(new TestModel(data));
});
</script>
The problem I am having is not sure if the mapping is done well or if there is a problem with the output for the template.
Thanks
Issue was with case-sensitivity. This is the correct template
<script type="text/html" id="person-template">
<ul>
<li>
<div><span data-bind="text: Name"/> <span data-bind="text: Surname"/> has <span data-bind='text: Children().length'/></div>
<ul data-bind="foreach: Children">
<li><span data-bind="text: $data"> </span></li>
</ul>
</li>
</ul>
</script>

Resources