I'm building an ASP.Net MVC 2 application with a component architecture. There are two different types of components: Elementary Components, which have an associated controller action rendering a partial view, and Layout Components, which render all their child components (Elementary Components or again Layouts) in a certain layout.
Here is my generic RenderComponent() action method, which takes a component ID and renders the appropriate view:
[ChildActionOnly]
public ActionResult RenderComponent(int id)
{
ComponentRepository repository = new ComponentRepository();
Component component = repository.GetComponent(id);
if (component.ControllerName != null && component.ViewName != null)
{
// render elementary component
return PartialView("Elementary", component);
}
else
{
// render layout
return PartialView(component.Layout.ViewName, component);
}
}
Elementary.ascx renders an elementary component:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyApp.Models.Component>" %>
<% Html.RenderAction(Model.ViewName, Model.ControllerName); %>
Currently the only existing layout is the VerticalLayout.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyApp.Models.Component>" %>
<%
foreach (var child in Model.ChildComponents()) {
%>
<div class="layout-container">
<% Html.RenderAction("RenderComponent", "Core", child.ID); %>
</div>
<%
}
%>
The Problem:
When I tried to render an example layout component with three associated elementary child components, the page wouldn't render. Some debugging revealed the following problem:
RenderComponent(5) renders the layout view.
For rendering the first child component in the layout, Html.RenderAction("RenderComponent", "Core", 1) is called in the view. Stepping further, I discovered that in effect RenderComponent(5) is called instead of RenderComponent(1)!!
This obviously results in an infinite loop of the layout view rendering itself.
Why is this happening? How can I fix it? Is my hierarchical component architecture incompatible with ASP.Net MVC? How would you build such a system in ASP.Net MVC?
OK, my bad... Of course it has to be <% Html.RenderAction("RenderComponent", "Core", new { id = child.ID}); %> in VerticalLayout.ascx.
Related
Sory about my question, I am brand new to MVC 4 Razor, it's different from Asp.NET Web form.
Look like joomla, and other web languague, how can i create a "module", eg: "news, ads, counter" and stick it to asp.NET page.
I have a layout.cshtml in share folder, i think it's "Master Page" (like Master Page in Asp.NET webform)
How can i create some positions in that layout ?
You can create partial views or controller/actions that return a partial.
Partial Views
First create a partial view (which is a razor view without boilerplate markup like doctype, html and body elements.
To use a partial view in Razor:
#Html.Partial("name-of-partial-view", model-for-the-partial-view)
Actions returning a partial
To have a controller create a partial for you, create an action like this:
public DemoController : Controller
{
[ChildActionOnly] // Optional attribute, making this action invisible to the routing system
public ActionResult Demonstration(string someparam)
{
// Do something with someparam to get information to display
return PartialView();
}
}
You'll need to create a partial view to be returned from this action. (As before, a partial doesn't have the boilrplate markup like doctype, html and body.)
And to call it from Razor:
#Html.Action("Demonstration", "Demo", new { someparam = "something" });
If you want this partial on every page, put it somewhere in your layout page.
I'm working on setting up a shared content (navigation) for an asp.net MVC layout page.
Here is my partial view "_LayoutPartial.cshtml" with code to pull navigation data from a model.
#model MyApp.Models.ViewModel.LayoutViewModel
<p>
#foreach (var item in Model.navHeader)
{
//Test dump of navigation data
#Html.Encode(item.Name);
#Html.Encode(item.URL);
}
</p>
Here is how the code for my controller "LayoutController.cs" looks like.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MyApp.Models.ViewModel;
namespace MyApp.Controllers
{
public class LayoutController : Controller
{
//
// GET: /Layout/
LayoutViewModel layout = new LayoutViewModel();
public ActionResult Index()
{
return View(layout);
}
}
}
Here is the code for the "_Layout.cshtml" page. I'm attempting to call the partial view here using Html.RenderAction(Action,Controller) method.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<p>
#{Html.RenderAction("Index","Layout");}
</p>
#RenderBody()
</body>
</html>
When the layout page executes the #{Html.RenderAction("Index","Layout");} line, it throws out an error message "Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'."
What am I missing friends? How can I call a partial view in a layout page?
Thank you all in advance!
Instead of:
public ActionResult Index()
{
return View(layout);
}
do:
public ActionResult Index()
{
return PartialView(layout);
}
If you don't do that when you return a normal view from your child action, this normal view attempts to include the Layout, which in turn attempts to render the child action, which in turn returns a view, which in turn includes the Layout, which in turn attempts to render the child action, ... and we end up with names like the one ported by this very same site.
Also in your partial you don't need to do double encoding. The # Razor function already does HTML encode:
#model MyApp.Models.ViewModel.LayoutViewModel
<p>
#foreach (var item in Model.navHeader)
{
#item.Name
#item.URL
}
</p>
First verify that your child view is inside the Shared directory
#Html.Partial("_LayoutPartial")
OR
#{Html.RenderAction("actionname", "controller name");}
And don't use #Html.Encode(), Razor is already doing for u. Just use
#item.Name
#item.URL
I have solved this error getting on Layout page
System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper
Important !
First create partial view inside shared folder
In Controller ,
public PartialViewResult Userdetails()
{
....
return PartialView("PartialViewName", obj);
}
In Layout Page,
#{Html.RenderAction("action","controller");}
I know this is an old question but I thought I would throw this in here. You can use either Html.Action or Html.RenderAction. They both technically do the same thing but depending on how much content you're returning back can have an impact on which one you should really use for best efficiency.
Both of the methods allow you to call into an action method from a view and output the results of the action in place within the view. The difference between the two is that Html.RenderAction will render the result directly to the Response (which is more efficient if the action returns a large amount of HTML) whereas Html.Action returns a string with the result.
Source
I am rendering a parital view which has a submit button with HttpPost action in controller. This action method needs parent model as its parameter. Is there any way to send parent model as first parameter from inside a partial view?
Main view -> aspx file which has model as ParentModel
Partial view -> ascx file which has model as ParentModel.ChildModel
Controller action -> MyActionName(ParentModel model, int direction, int user)
If I keep the method as HttpPost then by default Parent model gets passed but then I cannot send 2nd and 3rd parameter as their values are decided at run time and these are not childmodel properties. e.g. direction parameter which indicates if user has clicked next/prev button. In this case, next and prev buttons call same action method (MultipleAction submit)
Additional info: My parent model has a collection of child model. I am looping through this collection and calling RenderPartial for each item. So, I cannot pass this parent model directly to my partial view (which is default behavior).
Any suggestions please? Thanks..
You could wrap all those partials into a HTML <form> so that all values get submitted too the server when this form is posted:
<% using (Html.BeginForm()) { %>
<%= Html.TextBoxFor(x => x.SomePropertyOfParent) %>
<%= Html.TextBoxFor(x => x.SomeOtherPropertyOfParent) %>
<%= Html.EditorFor(x => x.Children) %>
<input type="submit" value="OK" />
<% } %>
I use an editor template instead of partials for the Children collection. The custom editor template will automatically be rendered for each element of this children collection and provide any input fields allowing to modify it.
Then when the form is finally submitted all the properties required by the model binder to reconstruct the ParentModel will be sent to the server. As far as the direction and user parameters are concerned I would make them part of the parent view model so that my POST controller action looks like this:
[HttpPost]
public ActionResult MyActionName(ParentViewModel model)
{
...
}
For my learning, I'm building a document management solution with ASP.NET MVC3. Below are pages I manage:
a search/result page (list of items)
a favorite page (list of items)
an edit page
a create page
I also have a Site.Master page where I show a treeview menu on the left side of the screen. So wherever the user is located in the website, the treeview menu is showing his location by underlining his location in the menu.
For building the treeview menu, I use the code below (cleaned for easy reading):
<ul id="treemenu1" class="treeview">
<li>Documents
<ul>
<%= Html.TreeviewMenu(TreeMenu.Create("Search", "Search", "Affaires", null))%>
<%= Html.TreeviewMenu(TreeMenu.Create("Favorite", "Favorite", "Affaires", null))%>
<%= Html.TreeviewMenu(TreeMenu.Create("New", "Create", "Affaires", null))%>
</ul>
</li>
</ul>
The problem is that I need to underline the active item in my menu. So if user is displaying the search page, my search menu entry must be underlined. How can I proceed? I was thinking about the integration of this information in the strongly typed viewmodel passed to each viewpage but it failed because each page is using a different viewmodel. I prefer not using a session variable because it is not a clean solution.
Any ideas?
A solution with a session variable: I save the "current menu item" in a session variable (from my controller). So, whenever the Site.Master page is reloaded, it recreate every treeview menu item. For each one, it checks if the item is equal to the session variable. If yes, the class "selected" is added to the item (css highlighted with blue).
I don't really like using session variables. Maybe there are more elegant solutions?
How about using a helper:
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());
}
and then:
<ul>
<%= Html.MenuItem("Search", "Search", "Affaires") %>
<%= Html.MenuItem("Favorite", "Favorite", "Affaires") %>
<%= Html.MenuItem("New", "Create", "Affaires") %>
</ul>
which could yield the following if you navigate to /Affaires/Favorite:
<ul>
<li>Search</li>
<li class="active">Favorite</li>
<li>New</li>
</ul>
Well, there is one easy and effective solution: let each page signal whether it's the active page and set a css class with jQuery.
Assuming your rendered html looks like:
<ul id="treemenu1" class="treeview">
<li>Documents
<ul>
<li class="search"></li>
<li class="favorite"></li>
</ul>
</li>
</ul>
At the bottom of each page, do something like (this would sit on the search view):
<script type="text/javascript">
$(document).ready(function () {
// Set active nav
$('#treemenu1 li.search').addClass('selected');
});
</script>
UPDATE based on new info
A little cleaner than a session vraiable could be to use the ViewBag property from the controller.
public ActionResult Search(/*whatever*/)
{
// do things
// set the selevted view
ViewBag.SelectedMenuItem = "search";
return View();
}
Then in your master page you can check against <%: ViewBag.SelectedMenuItem %>
Note that SelectedMenuItem is a random name. The ViewBag property is of type dynamic so you can use any property name you like.
I am migrating a web site to a new one using ASP .NET MVC2.
In the original site, master page has code-behind to check a query string parameter value. Depending on this value, code-behind dynamically modify some CSS property to hide / display master page elements.
As MVC2 has no code-behind because we are supposed to perform everything in the controllers, how should I proceed in this case ?
I see this : asp.net mvc modifying master file from a view
It partially answers my needs but the query string processing is common to all pages. How can I move this processing in a common code section ?
Regards.
A helper method looks like a good place:
public static class HtmlHelperExtensions
{
public static string GetCss(this HtmlHelper htmlHelper)
{
// read some request parameter
// here you also have access to route data so the
// parameter could be part of your custom routes as well
var foo = htmlHelper.ViewContext.HttpContext.Request["foo"];
// based on the value of this parameter
// return the appropriate CSS class
return (foo == "bar") ? "barClass" : "fooClass";
}
}
And somewhere in your master page:
<body class="<%= Html.GetCss() %>">
Or if you are always going to apply it to the body tag only it might be more appropriate to do this in order to reduce the tag soup:
public static class HtmlHelperExtensions
{
public static MvcHtmlString StartBody(this HtmlHelper htmlHelper)
{
var body = new TagBuilder("body");
var foo = htmlHelper.ViewContext.HttpContext.Request["foo"];
var bodyClass = (foo == "bar") ? "barClass" : "fooClass";
body.AddCssClass(bodyClass);
return MvcHtmlString.Create(body.ToString(TagRenderMode.StartTag));
}
}
and in your master page at the place of the body tag:
<%= Html.StartBody() %>
I can think of two solutions to this:
Derive your controllers from one controller base and set the ViewData parameter there depending on posted Form values
Don't use ViewData at all, but simply look for the form value in the view (using HttpContext.Current)
The second method violates the MVC pattern. IMO it is still acceptable in some scenarios, for example I am using this approach to highlight the currently selected item in a navigation menu.