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>
}
Related
I have a 2-level menu item: I have a list of department and each department has a list of stores.
I have a Menu, PartialView which iterates through the Model (departments) and builds the menu:
#model IEnumerable<Department>
<ul>
#foreach (var department in Model)
{
<li>
#Model.DepartmentName
<ul>
#foreach (var store in department.Stores)
{
<li>#store.StoreName</li>
}
</ul>
</li>
}
</ul>
And this is how I call the Menu PartialView in my _layout.cshtml:
#Html.Partial("Shared/_Menu", MyApplicationCache.departments)
As you can see I am passing the same model (from the cache) to the PartialView on all the requests.
Does Razor ViewEngine have an internal caching system to recognize that this view has already been built (complied to HTML string) for this model? Or does it re-render (recompile) the PartialView on every single request?
The PartialView gets re-rendered at every single request, assuming you don't have any OutputCacheAttribute applied on the Controller or its action method involved.
If you need output caching, you need to set this up explicitly via OutputCacheAttribute, see the documentation.
You can easily check this by outputting a DateTime, eg. via a menu-item as shown here below.
At every request, it will show a new value, proving it got re-rendered.
<li>#DateTime.Now</li>
Full menu:
#model IEnumerable<Department>
<ul>
#foreach (var department in Model)
{
<li>
#Model.DepartmentName
<ul>
<li>#DateTime.Now</li>
#foreach (var store in department.Stores)
{
<li>#store.StoreName</li>
}
</ul>
</li>
}
</ul>
Re-Rendering and Re-Compiling are very very different in ASP.Net MVC. While most of the answers here are correct, the View is only compiled once (except in debug mode, where it's compiled each time so you can change the view, hit refresh and see the change, or if the timestamp changes on the file in production). It is compiled into a runtime class that derives from WebViewpage(no ViewModel) or WebViewPage<T>(has ViewModel of type T).
The class is instantiated for each view needed (so if you used the same partial multiple times, you have to instantiate the each time), the model is populated, and the execute() method is called to create/stream the HTML to the client. The view could never be cached per model as it is to complicated to do that and instead the MVC team chose to allow configuring caching per controller method, not per WebViewPage.
#ErikPhilips, thanks a lot for this - So the View is only compiled once (no matter if we use or not use OutputCache)? It's the execute method which renders the runtime class into HtmlString, and it is the rendering which would benefit from caching?
Sorta of, but it's much more advanced, easier and complicated then that.
Advanced - The output cache is based on the controller method. So if the output cache configuration determines that the call can use a cached version, the controller method is never called. That's where the huge performance gain is. Imagine no call to the DB / external API call, there isn't a need. You could configure the cache so if it sees id=1 cache it for 30 minutes. Now anyone who calls that method with authorization and id=1 gets the cached string/html.
Easier - You put your OuputCacheAttribute on a method, configure it and you're done. Pretty darn easy to configure.
Complicated - Caching can be more complicated because you can render other controller methods using Html.Action() (Html.Partial() if you don't need a layout for your partial) or the preferred Html.RenderAction(); (Html.RenderPartial() if you don't need a layout). There use to be a Donut Hole Caching Issue (recommended reading) but that's been fixed for a long time.
This question has a great answer, which proves PartialViews are not cached, and this link which is suggested in the comments explains how to use [OutputCache] for a partialView - this could be used with Html.Action() / Html.RenderAction() and renders PartialViews as a [ChildAction].
Caching PartialView as a child action makes sense, but I did not want to render my menu as a [ChildAction], because I did not want to make a separate call for displaying the menu, so this is what I ended up doing:
I used RazorEngine to render my PartialView into HtmlString on application Startup, and will keep the HtmlString in a static variable (cache).
public static class MenuCache
{
private static readonly MvcHtmlString _menuMvcHtmlString;
static MenuCache()
{
using (var context = ApplicationDbContext.Create())
using (var razorEngine = RazorEngineService.Create(new TemplateServiceConfiguration()))
{
var repository = new MyRepository(context);
var departments = repository.GetDepartments();
// use razorEngine to render menu partial view into html string
// keep the htmlString in cache: _menuMvcHtmlString
string menuPartialView = File.ReadAllText(HostingEnvironment.MapPath("~/Views/Shared/_Menu.cshtml"));
string menuHtmlString = razorEngine.RunCompile(menuPartialView, "menuKey", null, departments);
_menuMvcHtmlString = new MvcHtmlString(menuHtmlString);
}
}
public static MvcHtmlString GetMenuHtmlString()
{
return _menuMvcHtmlString;
}
}
I also created a Customized HtmlHelper method which would return the HtmlString for the menu:
public static class HtmlHelperExtensions
{
public static MvcHtmlString MyMenu(this HtmlHelper html)
{
return MenuCache.GetMenuHtmlString();
}
}
Now in my _Layout page, I can use the customized HtmlHelper to display the menu:
#Html.MyMenu()
I have an asp.net-mvc website where up until now there have been no entitlements as its been open to everyone. Many of the pages are detailed forms with textboxes, select dropdowns, etc
I now need to change this to make many of the existing pages "entitled" so only certain people have edit capability and everyone else sees a read only page. I am trying to figure out if I should
Create a seperate view for everyone one of my existing views with forms that is just read only html on a page and redirect based on entitlements on the server side, something like
public ActionResult OrderView()
{
var isEntitled = Model.IsEntitled()
if (isEntitled)
{
return("OrderEditableView", GetViewModel());
}
else
{
return("OrderReadOnlyView", GetViewModel());
}
}
or
Reuse the same view and simply disable or hide the "Save" button on the screen.
on my view have
<% if (Model.IsEntitled) { %>
<button id="saveButton">Save Changes</button>
<% } %>
The second option would be much quicker to implement but would be a little weird because it would look like you could edit all of the fields but just dont' see (or see a disabled) Save button on the form.
The first option seems cleaner but I would have to go and create a new view for everyone of my screens and that seems like a lot of work (over 100 views currently)
This seems like a common situation so I wanted to see if there was a best practice on dealing with this situation. Obviously I am looking for a solution given the current situation that I am in but I would also be interested if there were patterns or solution that would be considered best practice or recommended if I was starting from scratch.
I would go for creating a separate partial/full views for both.
Reasons:
easy to change and maintain
validation code only in editable part
clear separation of concerns
cleaner code
may be we can reuse some of the code by abstracting/separating similar code to re usable partial views
easy control over access
Also, in real world we balance among budget,time and effort. so considering that I may use mix of these depending upon the situation.
I'd go for the second options with a single view for both writable and readonly forms.
For the visual side, I'd implement a few things in addition to hiding the Save button:
Set the input fields to readonly. You can easily do this with client side Javascript.
Change the colors (and possibly other attributes) of the input fields to emphasize that they are readonly (e.g. remove the borders). This can be easily achieved by a style sheet and a single additional class name in the form tag.
The authorization on the server side is simple as well. Just annotate the action accordingly.
It could look like this (simplified, using Razor and jQuery):
View:
#using (Html.BeginForm("AddressController", "SaveChanges", FormMethod.Post,
new { #class = Model.IsEntitled ? "regular" : "readonly"}))
{
<p>
#Html.TextboxFor(model => model.Name)
</p>
<p>
#Html.TextboxFor(model => model.Address)
</p>
#if (Model.IsEntitled) {
<p>
<button id="saveButton">Save Changes</button>
</p>
}
}
<script type="text/javascript">
$( document ).ready(function() {
$('form.readonly input').attr("readonly", true);
});
</script>
Controller:
public class AddressController : Controller {
[Authorize(Roles = "xyz")]
[HttpPost]
public ActionResult SaveChanges(Address address) {
...
}
}
CSS:
.readonly input {
border: none;
}
an option is to do the switch in the view..
<% if (Model.IsEntitled)
{
Html.Partial("OrderEditablePartialView", Model)
}
else
{
Html.Partial("OrderReadOnlyPartialView", Model)
}
%>
another option could be to create custom Html helpers to render the elements as editable or readonly, which would give you some flexibily on how you handled each element.
crude example:
public static MvcHtmlString FormTextBox(this HtmlHelper helper, string name, string text, bool editable)
{
return
new MvcHtmlString(
String.Format(
editable
? "<input type='text' id='{0}' name='{0}' value='{1}'>"
: "<label for='{0}'>{1}</label>",
name, text));
}
The quickest solution would be some javascript that disables all input elements on pages where the user isn't entitled. How to implement this is up to you, it depends on how your views and models are structured.
This way you can reuse the edit-views without even having to edit them, but depending on the kind of user you have you can expect "site not working" calls when your forms appear disabled.
patterns or solution that would be considered best practice or recommended if I was starting from scratch.
The proper way to go would be DisplayTemplates and EditorTemplates with proper viewmodels, but from your "create a new view for everyone of my screens and that seems like a lot of work" comment I gather you're not interested in that.
So I'd go with the former. Please make sure you check the permissions in the [HttpPost] action methods, as malicious users can easily re-enable the form and post it anyway.
in the asp.net when i used masterpages i can use masterpage code behind to run share codes .
in the MVC how can i do it . all pages use layout.cshtml but there isn't any share controller .
in my layout i have some datas that must load from database but there isn't anywhere to write these code and fill viewbag to send data to layout.cshtml
<div class="page">
<header >
<div id="title">
<h1>Management</h1>
</div>
<div id="logindisplay">
#Html.Partial("_LogOnPartial")
</div>
<br />
<br />
<nav>
<ul id="menu">
// these data must load from db
</ul>
</nav>
</header>
<section id="main">
#RenderBody()
</section>
<footer>
</footer>
</div>
In the shared directory you put all your files (like master pages and partial views) that will be used across your web application, hopefully in a situation where they are used more than once (for partial views). There is not a shared controller, but you could create a CommonController that can handle stuff like your menu.
I don't know what other data you need to display in your view, but looking at your example it seems like you want to display menu items in a ul tag.
I would have a class that would represent a menu item like:
public class MenuItem
{
public int MenuId { get; set; }
public string MenuName { get; set; }
}
I always create a view model for each view. This is better because then you have only the fields that are required on the view. Never send your domain objects to the view directly.
A view model would look something like this, it can contain many other properties, but for this demo I will only include the meu items.
public class MyViewModel
{
// This will contain the menu items that you want displayd in your view
public IEnumerable<MenuItem> MenuItems { get; set; }
}
You action method (in a controller called something like CommonController):
public ActionResult MyActionMethod
{
MyViewModel viewModel = new MyViewModel
{
// Here you do a database call to populate your menu items
// This GetAllMenuItems method returns a list of MenuItem objects
MenuItems = menuItemService.GetAllMenuItems()
};
return View(viewModel);
}
Create a partial view that will get its data from CommonController (you need to create this with the action method above). Loop through each MenuItem in MenuItems and display the name in a li tag.
<ul>
#foreach (var item in Model.MenuItems)
{
<li>item.MenuName</li>
}
</ul>
I hope this helps. The rest I leave up to you do the rest and read up on more articles. There is many info on the internet that describes what you want to do. You just need to invest in time to read these articles.
There are a couple of options. You can either use a partial view (with it's own controller and view model) or you can use a base controller. With a base controller, each controller would use it as an ancestor and you can use the base controller to load common data elements.
I know this post is old but I was having the same problem suddenly I got that we can easily access VB in Layout form using Razor I am working with vb. You can modify this code for c#. I am using #Imports System.Data.SqlClient. This code is working perfectly for me and I want exactly what you describe
<li class="treeview">
<a href="#">
<i class="fa fa-indent"></i>
<span>Transaction</span> <i class="fa fa-angle-left pull-right"></i>
<span class="label label-primary pull-right"></span>
</a>
<ul class="treeview-menu scrollbar">
#Code
Using conn As New SqlConnection(connectionString)
Dim sSql = "SELECT * FROM SM_OBJECT_INFO WHERE menu_name = 'Transaction' Order By group_index, index_no"
Dim formname As String
Dim formtitle As String
Dim cmd As SqlCommand = New SqlCommand(sSql, conn)
conn.Open()
Dim rst As SqlDataReader = cmd.ExecuteReader
Do While rst.Read()
formname = rst!form_name
formtitle = rst!object_name
#<li><a href=#formname>#formtitle</a></li>
Loop
End Using
end code
</ul>
</li>
This article worked for me-- below i am writing in details-
In the shared directory you put all your files (like master pages and partial views) that will be used across your web application, hopefully in a situation where they are used more than once (for partial views). There is not a shared controller, but you could create a CommonController that can handle stuff like your menu.
I don't know what other data you need to display in your view, but looking at your example it seems like you want to display menu items in a ul tag.
I would have a class that would represent a menu item like:
public class MenuItem
{
public int MenuId { get; set; }
public string MenuName { get; set; }
}
I always create a view model for each view. This is better because then you have only the fields that are required on the view. Never send your domain objects to the view directly.
A view model would look something like this, it can contain many other properties, but for this demo I will only include the meu items.
public class MyViewModel
{
// This will contain the menu items that you want displayd in your view
public IEnumerable<MenuItem> MenuItems { get; set; }
}
You action method (in a controller called something like CommonController):
public ActionResult MyActionMethod
{
MyViewModel viewModel = new MyViewModel
{
// Here you do a database call to populate your menu items
// This GetAllMenuItems method returns a list of MenuItem objects
MenuItems = menuItemService.GetAllMenuItems()
};
return View(viewModel);
}
Create a partial view that will get its data from CommonController (you need to create this with the action method above). Loop through each MenuItem in MenuItems and display the name in a li tag.
<ul>
#foreach (var item in Model.MenuItems)
{
<li>item.MenuName</li>
}
</ul>
I hope this helps. The rest I leave up to you do the rest and read up on more articles. There is many info on the internet that describes what you want to do. You just need to invest in time to read these articles.
This is very useful article to achieve asp.net master page functionality in MVC. i.e. if we want a functionality which is common for every page[ every controller/view in MVC] need to follow this article steps.
I faced once problem which is not mentioned in this article going to share below-
When we will create partial view which will be called on _layout page for being share in every view.. we hare to write -
#{Html.RenderAction("BindMenu", "Common");}
on _Layout page.
Since I was newbie and i wast trying to call partial view like-
#Html.Partial("~/Views/Shared/_HeaderMenuPartial.cshtml", Model.MenuItemsList)
which was not working.
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.