I'm using MVC3 and Spark.
I need to add a class to a LI if a certain ViewBag element is set to X.
<li id="menu-home" class="active?{ViewBag.Active=='home'}" >${Html.ActionLink("Home", "Index", "Site")}</li>
Like the above. This doesnt work, however wondering if there is a way to approach this?
Here are the steps I took and it worked for me:
Create a new ASP.NET MVC 3 project using the default template and the Razor view engine
Install the Spark.Web.Mvc3 NuGet package.
Change the Index action of HomeController to look like this:
public ActionResult Index()
{
ViewBag.Active = "home";
return View();
}
Rename ~/Views/Home/Index.cshtml to ~/Views/Home/Index.spark and make it look like this:
<li id="menu-home" class="active?{ViewBag.Active == 'home'}">
${Html.ActionLink("Home", "Index", "Site")}
</li>
Run the project
The generated HTML is:
<li id="menu-home" class="active">
Home
</li>
Remark: Everytime I see someone using ViewBag instead of strongly typed views with view models I feel in the obligation to point this as a bad practice.
Related
I have navbar elements in my _Layout.cshtml which depend on the controller being called. On a search page there will be no navigation but in order to keep the style of the site consistent the navbar itself will remain. I'm not sure what is the most accepted and idiomatic way of performing this work.
_Layout.cshtml
(etc)
<nav>
<div class="container">
<ul>
#if (TempData.ContainsKey(KeyChain.ItemKeyTempDataKey))**
{
var itemKey = TempData[KeyChain.ItemKeyTempDataKey] as ItemKey;
<li>#Html.ActionLink("Overview", "Index", "Overview", itemKey, new { })</li>
<li>#Html.ActionLink("Purchasing", "Index", "InvoiceMovementHistory", itemKey, new { })</li>
<li>#Html.ActionLink("Profit Trends", "Index", "SalesMovement", itemKey, new { })</li>
<li>#Html.ActionLink("Coupons", "Index", "Coupon", itemKey, new { })</li>
<li>#Html.ActionLink("Deals", "Index", "WebDeal", itemKey, new { })</li>
<li>#Html.ActionLink("Update Log", "Index", "UpdateLog", itemKey, new { })</li>
}
</ul>
</div>
</nav>
(etc)
ItemKey.cs
public class ItemKey
{
public string Upc { get; set; }
public string VendorItemCode { get; set; }
public int Vendor { get; set; }
}
UpdateLogViewModel.cs
public class UpdateLogViewModel
{
public IEnumerable<UpdateLogEntryViewModel> UpdateLogEntries { get; set; }
}
UpdateLogController.cs
public ActionResult Index(ItemKey itemKey)
{
TempData[KeyChain.ItemKeyTempDataKey] = itemKey;
//etc uses itemkey to get data in order to generate updateLogViewModel
return updateLogViewModel();
}
Things I thought of
Using TempData (as above) to display the navbar elements if the itemkey is populated. TempData, however, is kind of on its way out and feels hacky.
Add a rendersection to the navbar, put the navbar elements in a renderaction and populating them in the section on every view that uses it (which is essentially every view EXCEPT the search view). This just violates DRY on overdrive, but seems to me to be the idiomatic thing.
Derive a secondary sublayout that is an "itemlayout", which would be typed to itemkey and drops the tempdata check. At least provides compile-time checking as long as developers use the itemlayout for item subscreens. But, call me crazy, that's worse because now all of my derived view's viewmodels have to depend on the type from the itemlayout viewmodel. However, this has the advantage of making the dependency clear: if you're going to use this layout, you must derive from this viewmodel that contains an itemkey property. This seems like the most idiomatic way, but I hate the idea of a typed layout.
Move the navbar on to every view page. I will almost certainly not do this, but it should be mentioned that the possibility exists.
So, is there another way I could perform this action idiomatically in MVC, or is one of the options I've listed above the preferred method?
TempData is a bad way to send data around in an ASP.NET MVC application. It's a holdover from the Viewstate days. It's a bad idea.
Instead of TempData, you can make your Navbar a RenderAction, and pass it information from each page it appears on (from the view). You can also use an HtmlHelper (outlined below) to render the links. There's no sense in having all this cooped up in the Layout.cshtml, since it'll have code that doesn't apply to it.
Effectively what you're trying to do is show the active page in a different style.
There are quite a few ways of doing that.
Highlighting current page ASP.NET MVC
Highlighting current page in navigation ASP.NET MVC
And K. Scott Allen has a blog post about the various methods he uses.
All of this tricks have one thing in common: They all suggest using an HTMLHelper that simply looks at the current page.
The most natural and canonical way to do this in MVC is by overriding partials. Create a specific controller called SearchController.
Then, create a partial called _Navigation.cshtml in your "Views\Shared" folder, like this:
<nav>
<div class="container">
<ul>
<li>...</li>
<li>...</li>
<li>...</li>
</ul>
</div>
</nav>
And then, in "Views\Search" create another partial called _Navigation.cshtml like this:
<nav>
<div class="container">
<p>Nothing to see here.</p>
</div>
</nav>
Then, in your layout, when you do this:
#Html.Partial("_Navigation")
The precedence of the view resolver will pick up the latter on the search page and the former everywhere else.
Edit: Based on what I can gather from your comments and updates, you have a controller action that receives some values in the query string and you want to persist those in your action links. The answer is easy.
Assuming the URL /UpdateLog?Upc=xyz&VendorItemCode=abc&Vendor=1 hits your UpdateLog.Index action, then in your view, your links just need to be, e.g.:
#Html.ActionLink("Purchasing", "Index", "InvoiceMovementHistory")
If InvoiceMovementHistory.Index also accepts those parameters, the MVC framework will automatically map the current route parameters to the target route when it is generating the link. No need for you to manage the values at all.
If they're not query string parameters but URL segments, the same applies.
This stateless passing of context from request to request via GET parameters is the ultimate "idiomatic" way of doing this sort of thing in MVC and on the web in general.
Use this in conjunction with the view overriding I described above and you have a very easy way of doing this and switching the nav off for specific pages. That said, given the emerging clarity I would forego the partials and just check a ViewBag.DisableNav property in your layout:
#if (!ViewBag.DisableNav)
{
<nav>
<div class="container">
<ul>
<li>#Html.ActionLink("Purchasing", "Index",
"InvoiceMovementHistory")</li>
<li>...</li>
<li>...</li>
</ul>
</div>
</nav>
}
Being new to ASP MVC, I met the following problem.
I have a list of "repeating" controls on my page, which are presented by the following Razor code:
#model BankBLL.Interfaces.ISecureFolder
...(some irrelevant code here)
<header><h3 >Commitee list</h3></header>
#foreach (var commitee in Model.Commitees)
{
<a href="#Url.Action("CommiteePage", "SecureFolder", commitee)">
<div class="commiteeButtonImageContainer">#commitee.Name</div>
<img src="~/Images/CommiteeButtonImage.png"/>
</a>
}
Model.Commitees here is a List of ICommitee objects, that means that I am trying to "bind" each Url.Action to a corresponding ICommitee commitee object.
However, when it comes to my controller action:
public ActionResult CommiteePage(ICommitee commitee)
{
return View("CommiteePage", commitee);
}
looks like I am making it a wrong way, because application returns "Cannot create an instance of an interface." error, that means that application is unable to retreive required commitee object when the action link is clicked.
Is there a way to bind each row "item datacontext" (ICommitee object in this case) to correspoding Url.Action?
Unfortunately could not post it earlier due to reputation regulations.
Finally resolved this issue due to good explanation at:
HTML.ActionLink method
When you try to pass an argument from Url.Action or Html.ActionLink - you have to specify explicitly the final "null" argument responsible for html arguments.
In my case the following code works correctly:
slightly changed controller action (now receives just name instead of commitee object itself)
public ActionResult CommiteePage(string commiteeName)
{
return View("CommiteePage", SecureFolder.Commitees.First(o=>o.Name == commiteeName));
}
and changed syntax for html calling this action:
#foreach (var commitee in Model.Commitees)
{
<a href="#Url.Action("CommiteePage", "SecureFolder", new { commiteeName=commitee.Name }, null)">
<div class="commiteeButtonImageContainer">#commitee.Name</div>
<img src="~/Images/CommiteeButtonImage.png"/>
</a>
}
Now view correctly passes the name of selected commitee to controller so that I can redirect to corresponding commitee view.
Thank you all for helping to resolve this issue!
The main problem is that the default model binder cannot create an instance of an interface. Try to be more specific, i.e. public ActionResult CommiteePage(ImplementedCommiteeType commitee). You can also create a CommiteeViewModel: ICommitee class in which you can transport your structures (in Controllers and Views only).
Or you can create your own model binder which knows what to implement. This is slightly more complicated.
I have the following ActionLink that sits in the home page on the register controller (Index.cshtml)
#Html.ActionLink("terms of service", Url.Action(MVC.Home.Terms()),
null, new { target="_blank" })
Generating the following URL. Why is "register" being added to it? It's as if the link within the Register page which has it's own controller is preappending the register controller to any link in that view?
http://localhost/register/terms-of-service
routes.MapRoute(
"Terms",
"terms-of-service",
new { controller = "Home", action = "Terms" }
);
public partial class HomeController : SiteController
{
public virtual ActionResult Terms()
{
return View(new SiteViewModel());
}
Can you try generating the same link using standard MVC instead of T4MVC, to check whether the behavior is T4MVC specific? MVC behavior with routing can often be puzzling, so it's always good to isolate this first before investigating it as T4MVC thing.
This worked for me... Downloading from a .cshtml file without a redirect, etc... Took a while to get the something that worked
<a href="#Url.Action("ExportAsCSV", "Download", new { target = "_blank" })" >Download2</a>
Hi; i am a Asp.net software developer. i try to learn asp.net mvc. But i face to face strange thing. My contoller method name must be the same as view name or reverse. this is strange! Look please my _Layout:
<nav>
<ul id="menu">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>#Html.ActionLink("Article", "GetAll", "Article")</li>
</ul>
</nav>
article view page need GetAll method also need GetAll.cshtml. My desire: my view page name must independent of controller class'method name. My Controller:
My solution :
i think that Asp.net mvc is strange. i dislike controller' action name name must the same as view page name? how to make it? i think that View name must independent form any name
You are correct that by default your view name must be the same as your action name. However, this is easy to change. You can just called this overload of the View method in the controller and pass in whatever view name you want:
return View("SomeViewName",articles);
It doesn't have to be the same as the name of your method. By Default MVC3 will look for a View with the same name but you can create a View with ANY name and tell MVC to return that View:
return View("MyView",articles);
I have 2 comments:
GetAll() in MVC would typically be called Index (as in articles index)
You could name your Method something and return a view with a different name,
public ActionMethod GetAll()
{
return View("Index");
}
The more I use ASP.NET MVC, the more I love it. However, in the case of showing model data on master pages there seems several ways of doing it. I am unsure as to the best solution.
My example would be a commerce site where I want to output a list of product categories on every page and also show the status of the visitors cart.
In asp.net web forms I would typically do this using user controls each doing their own databinding to retrieve the required data.
In MVC all data should be passed by the controller.
So regarding the categories the simplest solution would seem to be to pass this in View data in the controller action:
ViewData["Categories"] = _service.GetCategories();
However, doing this for every action isn't very DRY so following this article I created a base controller that adds the required data to my ViewData:
public class AppController : Controller
{
IAppService _service;
public AppController() { }
public AppController(IAppService appService)
{
_service = appService;
SetSiteData();
}
private void SetSiteData()
{
ViewData["Categories"] = _service.GetCategories();
}
}
I then created an extension for ViewMasterPage:
public static void RenderCategoryList(this ViewMasterPage pg) {
pg.Html.RenderPartial("CategoryList", pg.ViewData["Categories"]);
}
And in my MasterPage:
<div>
<%this.RenderCategoryList(); %>
</div>
This seems quite a clean approach. However, is this the best way as I have also seen suggestions of creating a ViewModel for your MasterPage. I could see that perhaps as your ViewModel data grows, this may be a better solution.
Regarding the cart status, I suppose I would do something similar but am unsure whether RenderAction would be more appropriate (When to use RenderAction vs RenderPartial with ASP.NET MVC).
Thanks,
Ben
That works, although it's not the way I would do it for 2 reasons:
I don't like sticking data into ViewState since you essentially cast it as object
By requiring a base controller you're limiting the functionality to controllers that inherit from this basecontroller (which might not be an issue though)
I think this would be a perfect use of RenderAction (part of the MvcFutures project). This helper can be used to render an Action on another controller. So you might have a ProductController with a ListCategories action, you could just do something like:
<% Html.RenderAction<ProductController>(x => x.ListCategories()); %>
ListCategories would call
_service.GetCategories();
and might stick the info into its own Model. Then it would pass that model to the View would would be a Partial Page.
Thank you - RenderAction was perfect the job.
I got more information from here.
So for the benefit of others, here is an example of how I am outputting cart status:
Action:
[ChildActionOnly]
public ActionResult CartStatus()
{
return PartialView(_service.GetCartSummary());
}
Partial View (bound to Models.Cart)
<div class="cartSummary">
<%if (Model.HasItems) { %>
Cart Items: <%=Model.Items.Count() %> | Total: <%=Model.TotalItems %>
<%} else {%>
Your cart is empty. Please buy stuff!
<%} %>
Helper method for Master Page:
public static void RenderCartStatus(this ViewMasterPage pg) {
pg.Html.RenderAction("CartStatus", "Catalog", null);
}
And finally master page:
<%this.RenderCartStatus(); %>
Thank you for the advice.