Where should I put this code? - asp.net-mvc

Here's the code, written to be written in a controller:
CategoryRepository categoryRepo = new CategoryRepository();
var categories = categoryRepo.FindAllCategories();
ViewBag.Categories = categories;
Now I'd like to use this to create a nice list of Categories dynamically.
<div id="leftnavigationbar">
<ul>
#foreach (var category in ViewBag.Categories)
{
//Create li here.
}
<!-- ActionLink goes: Text, ActionName, Controller -->
<li>#Html.ActionLink("Libros", "Index", "Home")</li>
<li>#Html.ActionLink("Peliculas, Musica & Juegos", "Index", "Anuncios")</li>
<li>#Html.ActionLink("Computadoras", "Index", "Usuarios")</li>
<li>#Html.ActionLink("Bienes Raices", "Index", "Ayuda")</li>
<li>#Html.ActionLink("Bolsa de Trabajo", "Index", "Contacto")</li>
<li>#Html.ActionLink("Deportes y Fitness", "Index", "Contacto")</li>
<li>#Html.ActionLink("Electronicos y Celulares", "Index", "Contacto")</li>
</ul>
</div>
Right now I'm writing this code to the _Layout.cshtml file. But I'd like to know where to write this so it runs always, sort of like a MasterPage.
Any suggestions?
Edit:
It appears that my initial intent isn't possible.
#foreach (var category in ViewBag.Categories)
{
<li>#Html.ActionLink(category.Name, "Index", "Home")</li>
}
Any suggestions on how accomplish what I'm trying to do? Just pull a list of categories and render them using a foreach loop. Then have it placed somewhere so it's viewable on all pages.

As always you start by creating a view model:
public class CategoryViewModel
{
public string CategoryName { get; set; }
}
Then in your controller you fill the view model:
public ActionResult Index()
{
var categories = categoryRepo.FindAllCategories();
var model = MapModelToViewModel(categories);
return View(model);
}
And finally your strongly typed view could use a display template. Obviously the view will be strongly typed to IEnumerable
<ul>
#Html.EditorForModel()
<!-- ActionLink goes: Text, ActionName, Controller -->
<li>#Html.ActionLink("Libros", "Index", "Home")</li>
...
</ul>
And your display template (~/Views/Home/DisplayTemplates/CategoryViewModel.cshtml):
#model YourApp.Models.CategoryViewModel
<li>#Model.CategoryName</li>
As you can see with strongly typed views and display templates you don't even need to write loops in your views.
As an alternative to display templates you could use Html.Action or Html.RenderAction helers. Phil Haack wrote a nice blog post about them. I suspect that you were forced to use the ugly untyped ViewBag because this code is situated in your master template and you don't have a view model. No problem, with Html.Action you could have a specific controller that will do this:
public class CategoriesController: Controller
{
public ActionResult Index()
{
var categories = categoryRepo.FindAllCategories();
var model = MapModelToViewModel(categories);
return View(model);
}
}
And then have a corresponding partial view which will take care of rendering the categories (~/Views/Categories/Index.cshtml):
#model IEnumerable<YourApp.Models.CategoryViewModel>
#Html.DisplayForModel()
And finally use the same display template.
Now in your master page you could simply include this child action:
<ul>
#Html.Action("index", "categories")
<!-- ActionLink goes: Text, ActionName, Controller -->
<li>#Html.ActionLink("Libros", "Index", "Home")</li>
...
</ul>

You're looking for something called a 'partial view'.

Related

Object reference not set to an instance of an object in MVC5

I have an MVC5 app, and in the HomeController, I have an ActionResult defined like this:
public ActionResult BlogRSS()
{
var model = new BlogModel();
string strFeed = "http://webmysite.com/feed";
using (XmlReader reader = XmlReader.Create(strFeed))
{
SyndicationFeed rssData = SyndicationFeed.Load(reader);
model.BlogFeed = rssData;
}
return View(model);
}
Then, for this ActionResult, I created a partial view named BlogRSS, which looks like this:
#model MyApp.Models.BlogModel
#{
if (Model.BlogFeed != null)
{
<ul>
#foreach (var post in Model.BlogFeed.Items.ToList().Take(3))
{
<li><a href='#post.Links.First().Uri' target='_blank'>#post.Title.Text</a></li>
}
</ul>
}
}
And my model is defined simply like this:
public class BlogModel
{
public SyndicationFeed BlogFeed { get; set; }
}
So, the point is that I want to call that partial view in my _Layout.cshtml file, but when the website opens I get the error message specified in the title. I guess it is not calling my BlogRSS method at all. I'm calling it in the _Layout.cshtml like this:
<div class="col-md-4">
Blog
<br />
#Html.Partial("BlogRSS")
</div>
How can I solve the problem, and make sure that the corresponding ActionResult is also called before rendering the View?
The problem is that you're putting a call to a partial view which just renders the view without calling the controller and the model passed to that view is null.
There are couple ways how to fix this:
1) use Action instead of Partial
#Html.Action("BlogRSS", "Blog")
2) Define a base ViewModel which you will pass to the each view and put your feed into it.

ASP.NET MVC Database Driven Menu in shared view

I need some guidance on inserting a menu in the _layout.cshtml file. I have hit two problems:
1) Even when I create an additional model to include two data models, I am unable to pass both the models to the layout file
eg:
Model:
public class IndexModel
{
public tblMenu tblMenu { get; set; }
public tblSite tblSite { get; set; }
}
I need info from the Menu table and the site table on the same page.
2) When I create a partial view to pass the menu data I continually get exceptions telling me that I can't use the model in this way.
My Partialview:
#model mvcSPS.Models.IndexModel
<li>#Model.tblMenu.MenuName</li>
My Controller:
public ActionResult _menu()
{
return PartialView(db.IndexModels.ToList());
}
My _layout.cshtml
<ul id="navigation">
#foreach (var item in Model)
{
#Html.Partial("_menu")
}
</ul>
I have trawled the net and to be quite frank.. I am having a really difficult transition from ASP classic (yes I know) to ASP.net and MVC.
Your gentle guidance would be much appreciated.
Thanks
Andrew
change your #Html.Partial in your _layout.cshtml to call the controller function and render the result of the Action method.
#foreach (var item in Model)
{
Html.RenderAction("_menu", "Home");
}
note: you shouldn't need a prepending '#' since it's in the context of the foreach loop
EDIT: Based on my comment suggestion below
HomeController:
public ActionResult Menu() {
return PartialView("_menu", db.IndexModels.ToList());
}
_layout.cshtml
#{Html.RenderAction("Menu", "Home");} //be sure to fully-qualify the controller since it's layout, otherwise it'll look to the current controller based on route values collection
_menu.cstml
<nav>
<ul>
#foreach(var item in Model) {
Html.Partial("_menuItem", item)
}
</ul>
</nav>
_menuItem.cshtml
#foreach(var item in Model) {
<li>
text
#if(item.Children.Any())
{
<ul>
Html.Partial("_menuItem", item.Children)
</ul>
}
</li>
}

ASP.NET MVC 3 Adding comments in article view

I have article model with public ICollection<Comment> Comments { get; set; } and comment model. I have created view for article (Details view) and I want to show everything from model article (not problem) and comments to article to and after comments then show form for adding comment to article (not in other page, I want it in the view with article). For now I have this:
#model SkMoravanSvitavka.Models.Article
#{
ViewBag.Title = "Zobrazit";
}
<h2>Zobrazit</h2>
<fieldset>
<legend>Article</legend>
<div class="display-label">Title</div>
<div class="display-field">#Model.Title</div>
<div class="display-label">Text</div>
<div class="display-field">#Model.Text</div>
<div class="display-label">PublishedDate</div>
<div class="display-field">#String.Format("{0:g}", Model.PublishedDate)</div>
</fieldset>
#if (Model.Comments != null)
{
foreach (var comment in Model.Comments)
{
#Html.Partial("_Comment", comment)
}
}
<p>
#Html.ActionLink("Edit", "Edit", new { id = Model.ArticleID }) |
#Html.ActionLink("Back to List", "Index")
</p>
It shows article and there is partial view for all comments to article. And now I am not sure how to add form for adding comments. Thank you
Edit: Here is my comment controller and create methods (vytvorit = create in czech :) ):
public ActionResult Vytvorit(int articleID)
{
var newComment = new Comment();
newComment.articleID = articleID; // this will be sent from the ArticleDetails View, hold on :).
newComment.Date = DateTime.Now;
return View(newComment);
}
[HttpPost]
public ActionResult Vytvorit(Comment commentEntity)
{
db.Comments.Add(commentEntity);
db.SaveChanges();
return RedirectToAction("Zobrazit", "Clanek", new { id = commentEntity.articleID });
}
When I change #Html.RenderAction to #Html.Action it works. It is showing textbox for comment and I can add comment but there is problem that it not just add textbox but it add my site again (not just partial view but all view) and I am sure I add Create view for comment as partial.
Create a new Partial view, make it a strongly typed one of type Comment.
from the scaffolding templates, choose "Create" template.
handle the normal add new scenario of the comment.
add this Partial view to the Article details page.
Note that when you are about to save a new comment, you will need to get the hosting Article ID.
Hope it's now clear, if not, let me know.
update: assuming that you will add the "AddComment" partial view to your "Article details" view, you can do the following in order to add the comment.
1- Modify the GET (Create) action inside your CommentController as the following:
public ActionResult Create(int articleID)
{
var newComment = new CommentEntity();
newComment.articleID = articleID; // this will be sent from the ArticleDetails View, hold on :).
return View(newComment);
}
1- make the POST (Create) action like this:
[HttpPost]
public ActionResult Create(Comment commentEntity)
{
// Complete the rest..
}
2- The Partial view for the comment will look something like this:
#model NGO.Models.Comment
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div class="addcommentbox">
<h2> Add Comment </h2>
#Html.TextAreaFor(model => model.Description)
<div class="ErrorMessage">
#Html.ValidationMessageFor(model => model.Description)
</div>
<input id="addComment" type="button" onclick="" value="Add" />
</div>
}
3- inside the ArticleDetails page, in the desired place that you need the add comment section to show, use RenderAction method to render the AddComment Partial view like the following:
Html.RenderAction("Create", "Comment",new {articleID = Model.ID});
the previous line will call the GET(Create) action inside CommentColtroller and pass the current Article ID, so the AddComment Partial view will come already populated with the current Article ID (and this is what we want).
that's it, feel free to ask me if it's not clear yet, and do let me know if it worked for you
I strongly recommend you to create view model for your article page. like below one;
public class ArticleViewModel {
public Article _article {get;set;}
//this is for the comment submit section
public Comment _comment {get;set;}
//this for the comments that you will view
public IQueryable<Comment> _comment {get;set;}
}
after that, pass this to your view from your controller and make your view strongly-typed to this ArticleViewModel class.
Create a section which is wrapped inside form tag as below;
#using(Html.BeginForm()){
#*Put your code inside here.
Create inputs for sending comments. like below;*#
#Html.TextboxFor(Model._comment.Content)
}
and then create a method for that;
[HttpPost]
[ActionName("name_of_your_article_page_action")]
public ActionResult Create(Comment commentEntity) {
}
NOTE : Of course, you do not need to create a seperate viewmodel. you can hardcode the name your inputs but this makes it hard to use validation and other kind of things. but not impossible to use validation though !

asp.net mvc - multiple databound components on a page

Im new to asp.net mvc and just learning the basics right now.
Im wondering how pages with muliple databound items would work with mvc views.
for example say there is a page that lists a bunch of "news articles" from a "NewsArticles" table.
and in the side of the page there is a another list which contains a list of "CaseStudies" for example.
then how would that be achieved in mvc?
You'd create your own view model class:
public MyPageViewModel
{
public IEnumerable<NewsArticles> Articles{get;set;}
public IEnumerable<CaseStudies> CaseStudies{get;set;}
}
Return it as the model in your action:
public ActionResult MyPage()
{
var model = new MyPageViewModel();
model.Articles = ArticleManager.GetArticles();
model.CaseStudies = CaseStudyManager.GetCaseStudies();
return View(model);
}
Then you can use a strongly typed view of type ViewPage<MyPageViewModel>, and output them like this:
<ul>
<% foreach(NewsArticle article in Model.Articles){%>
<li><%=article.Title%></li>
<%}%>
</ul>
<ul>
<% foreach(CaseStudy caseStudy in Model.CaseStudies){%>
<li><%=caseStudy.Title%></li>
<%}%>
</ul>
One option would be to specify the ViewData in the action method:
ViewData["NewsArticles"] = GetNewsAticles();
ViewData["CaseStudies"] = GetCaseStudies();

Adding "active" tag to navigation list in an asp.net mvc master page

In the default asp.net mvc project, in the Site.Master file, there is a menu navigation list:
<div id="menucontainer">
<ul id="menu">
<li><%= Html.ActionLink("Home", "Index", "Home")%></li>
<li><%= Html.ActionLink("About Us", "About", "Home")%></li>
</ul>
</div>
This renders in the browser to:
<div id="menucontainer">
<ul id="menu">
<li>Home</li>
<li>About Us</li>
</ul>
</div>
I want to be able to dynamically set the active list item, based on the view that is being called. That is, when the user is looking at the home page, I would want the following HTML to be created:
<div id="menucontainer">
<ul id="menu">
<li class="active">Home</li>
<li>About Us</li>
</ul>
</div>
I would expect that the way to do this would be something like:
<div id="menucontainer">
<ul id="menu">
<li <% if(actionName == "Index"){%> class="active"<%}%>><%= Html.ActionLink("Home", "Index", "Home")%></li>
<li <% if(actionName == "About"){%> class="active"<%}%>><%= Html.ActionLink("About Us", "About", "Home")%></li>
</ul>
</div>
The key bit here is the <% if(actionName == "Index"){%> class="active"<%}%> line. I do not know how to determine what the current actionName is.
Any suggestions on how to do this? Or, if I'm on completely the wrong track, is there a better way to do this?
I made myself a helper method to handle this type of thing. In the code behind of my master page (could be pushed of to an extension method ... probably a better approach), I put the following code.
protected string ActiveActionLinkHelper(string linkText, string actionName, string controlName, string activeClassName)
{
if (ViewContext.RouteData.Values["action"].ToString() == actionName &&
ViewContext.RouteData.Values["controller"].ToString() == controlName)
return Html.ActionLink(linkText, actionName, controlName, new { Class = activeClassName });
return Html.ActionLink(linkText, actionName, controlName);
}
Then, I just call it in my page like so:
<%= ActiveActionLinkHelper("Home", "Index", "Home", "selected")%>
Inside a view, you can get the current action name with:
ViewContext.RouteData.Values["action"].ToString()
You can also try to detect which is the current selected tab from its controller name and view name, then add the class attribute.
public static string MenuActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName)
{
var htmlAttributes = new RouteValueDictionary();
if (helper.ViewContext.Controller.GetType().Name.Equals(controllerName + "Controller", StringComparison.OrdinalIgnoreCase))
{
htmlAttributes.Add("class", "current");
}
return helper.ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(), htmlAttributes);
}
In MVC 3 Razor View Engine, you can do it as:
#{string ctrName = ViewContext.RouteData.Values["controller"].ToString();}
<div id="menucontainer">
<ul id="menu">
<li #if(ctrName == "Home"){<text> class="active"</text>}># Html.ActionLink("Home", "Index", "Home")</li>
<li #if(ctrName == "About"){<text> class="active"</text>}># Html.ActionLink("About Us", "About", "Home")</li>
</ul>
</div>
My sample worked when I have two pages as: Home/About and its controller has same name Index, so I get controller Name for distinction insteed of action. If you want to get action, just replace with following:
#{string ctrName = ViewContext.RouteData.Values["action"].ToString();}
An old question but hopefully someone might find this very helpful.
Put some thing that you can use to identify your page in the ViewBag, I used ViewgBag.PageName
For example, in index.cshtml, put something like
#{
ViewBag.PageName = "Index";
}
Add a class to each link item with a conditional statement to return active if the page being visited has the required value, or return an empty string otherwise. View below for details:
<li class="#((ViewBag.PageName == "Index") ? "active" : "")">Home</li>
<li class="#((ViewBag.PageName == "About") ? "active" : "")">About</li>
<li class="#((ViewBag.PageName == "Contact") ? "active" : "")">Contact</li>
I didn't just test it, I use this method in my projects
To contribute my own answer (tested in MVC4), I took a few best bits of the other answers, fixed a few issues, and added a helper to work with urls that aren't necessarily resolved via Controller & Action (eg. if you have an embedded CMS dealing with some page links, etc.)
The code is also forkable on github: https://gist.github.com/2851684
///
/// adds the active class if the link's action & controller matches current request
///
public static MvcHtmlString MenuActionLink(this HtmlHelper htmlHelper,
string linkText, string actionName, string controllerName,
object routeValues = null, object htmlAttributes = null,
string activeClassName = "active")
{
IDictionary htmlAttributesDictionary =
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (((string)htmlHelper.ViewContext.RouteData.Values["controller"])
.Equals(controllerName, StringComparison.OrdinalIgnoreCase) &&
((string)htmlHelper.ViewContext.RouteData.Values["action"])
.Equals(actionName, StringComparison.OrdinalIgnoreCase))
{
// careful in case class already exists
htmlAttributesDictionary["class"] += " " + activeClassName;
}
return htmlHelper.ActionLink(linkText, actionName, controllerName,
new RouteValueDictionary(routeValues),
htmlAttributesDictionary);
}
///
/// adds the active class if the link's path matches current request
///
public static MvcHtmlString MenuActionLink(this HtmlHelper htmlHelper,
string linkText, string path, object htmlAttributes = null,
string activeClassName = "active")
{
IDictionary htmlAttributesDictionary =
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (HttpContext.Current.Request.Path
.Equals(path, StringComparison.OrdinalIgnoreCase))
{
// careful in case class already exists
htmlAttributesDictionary["class"] += " " + activeClassName;
}
var tagBuilder = new TagBuilder("a")
{
InnerHtml = !string.IsNullOrEmpty(linkText)
? HttpUtility.HtmlEncode(linkText)
: string.Empty
};
tagBuilder.MergeAttributes(htmlAttributesDictionary);
tagBuilder.MergeAttribute("href", path);
return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal));
}
Using MVC3 with a Razor View offers another option:
_Layout.cshtml:
<li class="#ViewBag.NavClassHome">#Html.ActionLink("Home", "Index", "Home")</li>
<li class="#ViewBag.NavClassAbout">#Html.ActionLink("Disclaimer", "About", "Home")</li>
HomeController:
public ActionResult Index() {
ViewBag.NavClassHome = "active";
return View();
}
public ActionResult About() {
ViewBag.NavClassAbout = "active";
return View();
}
If you want to preserve this for a postback as well, you have to assign the ViewBag value here as well:
[HttpPost]
public ActionResult Index() {
ViewBag.NavClassHome = "active";
return View();
}
[HttpPost]
public ActionResult About() {
ViewBag.NavClassAbout = "active";
return View();
}
Tested and working fine for me, but you will have a css class name in your server side code.
This should work using jQuery on the client side of things, uses Google to serve the latest jQuery library:
<script src="http://www.google.com/jsapi" type="text/javascript" language="javascript"></script>
<script type="text/javascript" language="javascript">google.load("jquery", "1");</script>
<script language="javascript" type="text/javascript">
$(document).ready(function(){
var str=location.href.toLowerCase();
$('#menucontainer ul#menu li a').each(function() {
if (str.indexOf(this.href.toLowerCase()) > -1) {
$(this).attr("class","current"); //hightlight parent tab
}
});
});
</script>
I wanted to have a bit more control over my layout, and this is what I did.
Create a LayoutModel that other models inherit:
public abstract class LayoutModel
{
public CurrentPage CurrentPage { get; set; }
}
Create a LayoutAttribute that inherits from ActionFilterAttribute like so:
public class LayoutAttribute : ActionFilterAttribute
{
private CurrentPage _currentPage { get; set; }
public LayoutAttribute(
CurrentPage CurrentPage
){
_currentPage = CurrentPage;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
if (result == null || result.Model == null || !(result.Model is LayoutModel)) return;
((LayoutModel)result.Model).CurrentPage = _currentPage;
}
}
Now on the Action or Controller level I can set the current page (and other stuff if I wanted) like this:
[Layout(CurrentPage.Account)]
public class MyController : Controller
{
}
In my layout view I now have access to the current page, and whatever else I add to the LayoutModel.
The fact that your View has to know about your controller's actions is breaking with the MVC pattern. Perhaps your controller could pass some "control" information to the view to ultimately allow it to accomplish the same thing, the only difference is who is in charge.
Like in your controller's action you could:
public ActionResult Index(){
ViewData["currentAction"] = "Index";
//... other code
return View();
}
Then over in your view you could:
<% if( ((string)ViewData["currentAction"]) == "Index" {%> <!- some links --><% } %>
<% if( ((string)ViewData["currentAction"]) == "SomethingElse" {%> <!- some links --><% } %>
However, the more I think about it the more I question why you are using the same View for multiple actions. Is the view that similar?
If the use case justifies it then go with my above suggestion. But otherwise perhaps you could break things out into multiple views (one for each controller action) and the problem solves itself.
Based on the previous answers, here is what my current solution is for the same issue:
In the master page I give each li an id that corresponds to the controller and the action, since this should be known from the ActionLink. I was previously doing this with the page title but this helps with organization.
Site.Master:
<ul id="menu">
<li id="menuHomeIndex" runat="server"><%= Html.ActionLink("Home", "Index", "Home") %></li>
<li id="menuHomeAbout" runat="server"><%= Html.ActionLink("About Us", "About", "Home") %></li>
</ul>
Site.Master.cs:
// This is called in Page_Load
private void SetActiveLink()
{
string action = "" + ViewContext.RouteData.Values["controller"] + ViewContext.RouteData.Values["action"];
var activeMenu = (HtmlGenericControl)Page.Master.FindControl("menu" + action);
if (activeMenu != null)
{
activeMenu.Attributes.Add("class", "selected");
}
}
It's more work than the inline code but I think it's cleaner and also lets you have actions with the same name in different controllers. So if you add more menu items with different controllers, not all actions named Index will be highlighted in the menu.
If anyone sees issues with this approach please let me know.
Using MVC3 with a Razor View, you can implement this like:
<ul id="menu">
#if (ViewContext.RouteData.Values["action"].ToString() == "Index")
{
<li class="active">#Html.ActionLink("Home", "Index", "Home")</li>
}
else
{
<li>#Html.ActionLink("Home", "Index", "Home")</li>
}
#if (ViewContext.RouteData.Values["action"].ToString() == "About")
{
<li class="active">#Html.ActionLink("About", "About", "Home")</li>
}
else
{
<li>#Html.ActionLink("About", "About", "Home")</li>
}
</ul>
And then applying your style of your ".active" class like:
ul#menu li.active
{
text-decoration:underline;
}
Here is the version compatible with the current version of MVC4.
I have rewritten Adam Carr's code as an extension method.
using System;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
namespace MyApp.Web {
public static class HtmlHelpers {
/// <summary>
/// Returns an anchor element (a element) that contains the virtual path of the
/// specified action. If the controller name matches the active controller, the
/// css class 'current' will be applied.
/// </summary>
public static MvcHtmlString MenuActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName) {
var htmlAttributes = new RouteValueDictionary();
string name = helper.ViewContext.Controller.GetType().Name;
if (name.Equals(controllerName + "Controller", StringComparison.OrdinalIgnoreCase))
htmlAttributes.Add("class", "current");
return helper.ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(), htmlAttributes);
}
}
}
Hope this will help.
<ul>
<li class="#(ViewContext.RouteData.Values["Controller"].ToString() == "Home" ? "active" : "")">
<a asp-area="" asp-controller="Home" asp-action="Index"><i class="icon fa fa-home"></i><span>Home</span>
</a>
</li>
</ul>
Try
Should work fine !!!
EDIT : REMOVED IN BETA1
Removed the ViewName property from the ViewContext class.

Resources