Dynamic menus from database in MVC - asp.net-mvc

I have read some similar topics here and on the web, but I don't think I have seen one that would classify this as a duplicate, so I am going to go ahead and post it. I am currently loading my dynamic menus from the database like so:
public void LoadMenus()
{
var dbContext = new ContentClassesDataContext();
var menus = from m in dbContext.Menus
where m.MenuName != "Home" && m.MenuGroup == "RazorHome" && m.RoleID == "Facility"
orderby m.Sequence, m.MenuName
select m;
var html = "";
if (menus.Any())
{
html += "<span/>";
foreach (var menu in menus)
{
html = html + $"<a href='{menu.URL}'>{menu.MenuName}</a><br/>";
}
html += "<hr>";
}
Session["Menus"] = html;
}
LoadMenus() is in my controller class, so I am not able (to my knowledge) to use Razor syntax. I would prefer to load the menus from the view instead, so that I am able to use #Html.ActionLink(linkText, actionName, controllerName). Loading the HTML the way I am currently doing it will generate different link text depending on the current controller, so the links are not always correctly rendered. Is it possible to access the database from the view? Or perhaps to just pass in the content from the database from the controller to the view and then render the menu that way?

You should keep your html in the cshtml views.
You should pass the data through the viewmodel and not through the session.
1)
In the controller, get the menu data (in this example we fetch some fake data).
Create a viewmodel that can hold the menu data and pass it to the view, as shown below:
public class HomeController : Controller
{
public ActionResult Index()
{
var menu = GetMenu();
var vm = new ViewModel() {Menu = menu};
return View(vm);
}
private Menu GetMenu()
{
var menu = new Menu();
var menuItems = new List<MenuItem>();
menuItems.Add(new MenuItem() { LinkText = "Home" , ActionName = "Index", ControllerName = "Home"});
menuItems.Add(new MenuItem() { LinkText = "About", ActionName = "About", ControllerName = "Home" });
menuItems.Add(new MenuItem() { LinkText = "Help", ActionName = "Help", ControllerName = "Home" });
menu.Items = menuItems;
return menu;
}
}
2)
This is the viewmodel
public class ViewModel
{
public Menu Menu { get; set; }
}
This view is an example of how you could render the menu data as a html menu
#model WebApplication1.Models.ViewModel
<ul id="menu">
#foreach (var item in #Model.Menu.Items)
{
<li>#Html.ActionLink(#item.LinkText, #item.ActionName,
#item.ControllerName)</li>
}
</ul>
3)
This is the example menu classes used (representing your entities from the dbcontext)
public class Menu
{
public List<MenuItem> Items { get; set; }
}
public class MenuItem
{
public string LinkText { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
}
Here are some links to get you started:
http://www.codeproject.com/Articles/585873/Basic-Understanding-On-ASP-NET-MVC
http://www.asp.net/mvc/overview/getting-started/introduction/getting-started

Related

How to render singly Linked list in MVC View page

As I am trying to create a TreeView folder structure in MVC. I have class file something like the below.
Class File
public class TreeViewFolder
{
public string FolderPath { get; set; }
public string FolderName { get; set; }
public List<TreeViewFolder> MyTreeList { get; set; }
}
I need to render that above list in MVC View. I have no idea how to render single linked list data in MVC View. Any help will be appreciated.
Thanks
You could create an extension method that uses recursion to build <ul> and <li> elements to show the hierarchy of folders
public static class FolderTreeExtensions
{
public static MvcHtmlString FolderTree(this HtmlHelper helper, TreeViewFolder folder)
{
return MvcHtmlString.Create(TreeLeaf(folder));
}
// Recursive function
private static string TreeLeaf(TreeViewFolder folder)
{
StringBuilder html = new StringBuilder();
TagBuilder div = new TagBuilder("div");
div.InnerHtml = folder.FolderName;
html.Append(div.ToString());
if (folder.MyTreeList != null)
{
foreach (TreeViewFolder subFolder in folder.MyTreeList)
{
html.Append(TreeLeaf(subFolder));
}
}
TagBuilder item = new TagBuilder("li");
item.InnerHtml = html.ToString();
TagBuilder container = new TagBuilder("ul");
container.InnerHtml = item.ToString();
return container.ToString();
}
}
Then in your controller, initialize and populate and instance of TreeViewFolder, and in the view
#model TreeViewFolder
....
#Html.FolderTree(Model)
Then style the elements to suit your requirements
Note: either add a using statement in the view or add a reference to the <namespaces> section of web.config

MVC 5 Multiple Models

Is it possible to have multiple #Model functions within one page?
I have a Views/Shared/_layout.cshtml page which has the following code to pull the navigation from an SQL Server 2012 database:
#model IEnumerable<WebApplication1.navigation_V>
<ul class="nav sf-menu clearfix">
#foreach (var item in Model) {
<li>#Html.MenuItem(item.title_TV, item.url_TV, "Home")</li>
}
</ul>
This works fine on all views within the Views/Home folder, however the View/Accounts/Login.cshtml file has the following code:
#model WebApplication1.Models.LoginViewModel
Now I get the following error when trying to view the Account/Login page:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[WebApplication1.navigation_V]', but this dictionary requires a model item of type 'WebApplication1.Models.LoginViewModel'.
When writing the code I am not getting any red underline squiggles, the error only fires when trying to access the Account/Login page. This navigation function must be viewable on all pages, what I'm dreading next is actually getting the rest of the page content from the database in to these pages.
Any help would be much appreciated :-)
For further information I have included more code.
WebApplication1Entities.cs
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
namespace WebApplication1
{
public partial class WebApplication1Entities : DbContext
{
public WebApplication1Entities()
: base("name=WebApplication1Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<navigation_V> navigation_V { get; set; }
}
}
navigation_V.cs
using System;
using System.Collections.Generic;
namespace WebApplication1
{
public partial class navigation_V
{
public int navigation_ID { get; set; }
public string title_TV { get; set; }
public string url_TV { get; set; }
}
}
Controllers/HomeController.cs:
public static class MenuExtensions
{
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string text,
string action,
string controller
)
{
var li = new TagBuilder("li");
var routeData = htmlHelper.ViewContext.RouteData;
var currentAction = routeData.GetRequiredString("action");
var currentController = routeData.GetRequiredString("controller");
if (string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) &&
string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase))
{
li.AddCssClass("active");
}
li.InnerHtml = htmlHelper.ActionLink(text, action, controller).ToHtmlString();
return MvcHtmlString.Create(li.ToString());
}
}
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
private WebApplication1Entities db = new WebApplication1Entities();
public ActionResult Index()
{
return View(db.navigation_V.ToList());
}
}
}
Views/Shared/Layout.cshtml
#model IEnumerable<WebApplication1.navigation_V>
<ul class="nav sf-menu clearfix">
#foreach (var item in Model) {
<li>#Html.MenuItem(item.title_TV, item.url_TV, "Home")</li>
}
</ul>
I hope this make things a little clearer.
Instead of rendering navigation IEnumerable in layout you can write child action in your Home controller like this:
[ChildActionOnly]
public ActionResult Navigation()
{
var navigationModel = ..
// your code to retrieve IEnumerable<WebApplication1.navigation_V>
// from DB
return View(navigationModel);
}
View for this action is Views/Home/Navigation.cshtml:
#{ Layout = null; } // preventing recursive rendering
#model IEnumerable<WebApplication1.navigation_V>
<ul class="nav sf-menu clearfix">
#foreach (var item in Model) {
<li>#Html.MenuItem(item.title_TV, item.url_TV, "Home")</li>
}
</ul>
And code for _layout.cshtml, replace your old navigation code with this one:
#{ Html.RenderAction("Navigation", "Home"); }

How to hide id in the url (MVC3)

Problem
In my project i decided to imlement a custom menu provider using a db stored entity "Section".
So the section is mapped to the following Model:
public class TopMenuItemModel : BaseTrivitalModel
{
public TopMenuItemModel()
{
ChildItems = new List<TopMenuItemModel>();
}
public int ItemId { get; set; }
public string RouteUrl { get; set; }
public string Title { get; set; }
public string SeName { get; set; }
public IList<TopMenuItemModel> ChildItems { get; set; }
}
And the view for the model:
#model TopMenuModel
<nav id="main-nav">
#T("HomePage")
#foreach (var parentItem in Model.MenuItems)
{
#parentItem.Title
}
</nav>
My Default route is:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "Trivital.Web.Controllers" }
);
Controller for the menu:
public class CommonController : BaseTrivitalController
{
...
public ActionResult TopMenu()
{
var sections = _sectionService.GetCollectionByParentId(0, true);
var model = new TopMenuModel();
model.MenuItems = sections.Select(x =>
{
var item = new TopMenuItemModel()
{
ItemId = x.Id,
Title = x.GetLocalized(s => s.Title, _workContext.WorkingLanguage.Id, true, true),
SeName = x.GetSeName(),
RouteUrl = "",
};
return item;
})
.ToList();
return PartialView(model);
}
}
}
Now I have a SectionController where I have an ActionResult method:
//section main page
public ActionResult Section(string seName)
{
var section = _sectionService.Get(1);
if (section == null || section.Deleted || !section.Published)
return RedirectToAction("Index", "Home");
//prepare the model
var model = PrepareSectionPageModel(section);
return View(model);
}
My current Route for the Section (that gives me host/sectionSeName-id):
routes.MapLocalizedRoute(
"section", // Route name
"{SeName}"+ "-" + "{sectionId}", // URL with parameters
new { controller = "Sections", action = "Section" },
new { sectionId = #"\d+" }
);
Now I need to get my Url looks like this (without id, just the section name):
host/sectionSeName
Is there anyway to hide the Id in the url to make the urls look SEO-friendly, but available for the controller?
You can try utilizing the urlMappings in your web.config. Specify something like the following:
<urlMappings enabled="true">
<add url="~/somedirectory/" mappedUrl="~/somedirectory/1/"/>
</urlMappings>
Though, I don't think anything will work unless each section has it's own unique name. Otherwise you'll have some conflicting URLs.
You may also want to consider doing some custom work as well using IIS's rewrite module:
http://www.iis.net/learn/extensions/url-rewrite-module/using-the-url-rewrite-module
The company I work for uses this for it's KB article system, which is similar to your situation, and it works pretty well. (folder/id)

read implicit return type in Razor MVC View

I'm kind of new to razor MVC, and I'm wondering how can I read the values I return in the view?
My code is like this:
public ActionResult Subject(int Category)
{
var db = new KnowledgeDBEntities();
var category = db.categories.Single(c => c.category_id == Category).name;
var items = from i in db.category_items
where i.category_id == Category
select new { ID = i.category_id, Name = i.name };
var entries = from e in db.item_entry
where items.Any(item => item.ID == e.category_item_id)
select new { ID = e.category_item_id, e.title };
db.Dispose();
var model = new { Name = category, Items = items, Entries = entries };
return View(model);
}
Basically, I return an anonymous type, what code do I have to write to read the values of the anonymous type in my view?
And if this is not possible, what would be the appropriate alternative?
Basically, I return an anonymous type
Nope. Ain't gonna work. Anonymous types are emitted as internal by the compiler and since ASP.NET compiles your views into separate assemblies at runtime they cannot access those anonymous types which live in the assembly that has defined them.
In a properly designed ASP.NET MVC application you work with view models. So you start by defining some:
public class MyViewModel
{
public string CategoryName { get; set; }
public IEnumerable<ItemViewModel> Items { get; set; }
public IEnumerable<EntryViewModel> Entries { get; set; }
}
public class ItemViewModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public class EntryViewModel
{
public int ID { get; set; }
public string Title { get; set; }
}
and then you adapt your controller action to pass this view model to the view:
public ActionResult Subject(int Category)
{
using (var db = new KnowledgeDBEntities())
{
var category = db.categories.Single(c => c.category_id == Category).name;
var items =
from i in db.category_items
where i.category_id == Category
select new ItemViewModel
{
ID = i.category_id,
Name = i.name
};
var entries =
from e in db.item_entry
where items.Any(item => item.ID == e.category_item_id)
select new EntryViewModel
{
ID = e.category_item_id,
Title = e.title
};
var model = new MyViewModel
{
CategoryName = category,
Items = items.ToList(), // be eager
Entries = entries.ToList() // be eager
};
return View(model);
}
}
and finally you strongly type your view to the view model you have defined:
#model MyViewModel
#Model.Name
<h2>Items:</h2>
#foreach (var item in Model.Items)
{
<div>#item.Name</div>
}
<h2>Entries:</h2>
#foreach (var entry in Model.Entries)
{
<div>#entry.Title</div>
}
By the way to ease the mapping between your domain models and view models I would recommend you checking out AutoMapper.
Oh, and since writing foreach loops in a view is kinda ugly and not reusable I would recommend you using display/editor templates which would basically make you view look like this:
#model MyViewModel
#Model.Name
<h2>Items:</h2>
#Html.DisplayFor(x => x.Items)
<h2>Entries:</h2>
#Html.DisplayFor(x => x.Entries)
and then you would define the respective display templates which will be automatically rendered for each element of the respective collections:
~/Views/Shared/DisplayTemplates/ItemViewModel:
#model ItemViewModel
<div>#item.Name</div>
and ~/Views/Shared/DisplayTemplates/EntryViewModel:
#model EntryViewModel
<div>#item.Title</div>

ASP.NET MVC and navigation

I'm trying to learn ASP.NET MVC, and I want to have menus highlighted on the item that is currently selected. I know I did this before in web forms (although I don't actually remember how at the moment, but somehow with the sitemap). But how can this be done in MVC?
It seems like such a basic thing it should be simple to do in MVC? Sure, I can do it by adding CSS rules that are coupled between a body id and an li id from the menu (#home #homeli [style as current]), but it seems that would quickly become unwieldy, especially if there are also a lot of sub menus besides the main navigation (in several of the sub pages I have a sub menu in a contentplaceholder. BTW, I guess that's the only way to do it in MVC? In web forms the sub menus too could be handled by the sitemap, but I haven't seen a way to do this in MVC...)
Any suggestions?
Here is a tutorial that provides a very clean way to achieve this kind of menu:
http://www.dev102.com/2009/04/14/creating-a-tabbed-menu-control-for-aspnet-mvc/
The magic bit figuring out whether or not a menu item is active happens in the helper method that renders the items:
public static class MyHtmlHelper
{
public static string TabbedMenu(this HtmlHelper helper, IEnumerable<MenuTab> tabs)
{
var route = helper.ViewContext.RequestContext.RouteData;
//This is the current controller
var controller = route.GetRequiredString("controller");
var action = route.GetRequiredString("action");
var menu = "\n\n<ul id=\"menu\">";
foreach (var tab in tabs)
{
//if the menu controller and action match current controller and action, mark it as selected
if (controller == tab.Controller && action == tab.Action)
menu += "\n\t<li>" + helper.ActionLink(tab.Text, tab.Action,
tab.Controller, new { #class = "selected" }) + "</li>";
else
menu += "\n\t<li>" + helper.ActionLink(tab.Text,
tab.Action, tab.Controller) + "</li>";
}
menu += "\n</ul>\n\n";
return menu;
}
}
MenuTab class:
public class MenuTab
{
private MenuTab(string text, string action, string controller)
{
Text = text;
Action = action;
Controller = controller;
}
public static MenuTab Create(string text, string action, string controller)
{
return new MenuTab(text, action, controller);
}
public string Text { get; private set; }
public string Action { get; private set; }
public string Controller { get; private set; }
}
Usage:
<%= Html.TabbedMenu(new List<MenuTab> {
MenuTab.Create("Home", "Index", "Home"),
MenuTab.Create("About", "About", "Home"),
MenuTab.Create("Services", "Services", "Home"),
MenuTab.Create("Pricing", "Pricing", "Home"),
MenuTab.Create("Contact", "Contact", "Home")
}) %>
Menu item per Controller
#{
var currentController = (string)ViewContext.RouteData.Values["controller"];
Func<string, object> htmlAttributesFactory =
controller => currentController == controller ? new {#class = "selected"} : null;
Func<string, string, MvcHtmlString> menuItemFactory =
(title, controller) =>
Html.RouteLink(
title,
new {controller},
htmlAttributesFactory(controller));
}
#menuItemFactory("Home", "Home")
#menuItemFactory("Pending", "Pending")

Resources