views:

57

answers:

3

In ASP.NET MVC, How do I make a partial view available to all controllers? I want to create navigation that is common across the entire site, but when I place the Html.Action into my master page, it only works on views associated with 1 controller.

Right now, I have a controller action defined like this:

    // GET: GetCategoriesPartial
    [ChildActionOnly]
    public ActionResult GetCategoriesPartial()
    {
        var category = CategoriesDataContext.GetCategories();
        return PartialView(category);
    }

And I've created my partial view like this:

<%@ Import Namespace="wopr.Models" %>
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<ul>
<%
    foreach (var cat in Model as IEnumerable<Category>) {
        %>
        <li><a href="/categories/Details/<%=cat.catID%>"><%=cat.catName%></a></li>
        <%
    }

%>
</ul>

My Master Page looks like this:

<%@ Import Namespace="wopr.Models" %>
<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link type="text/css" rel="Stylesheet" href="/Content/Site.css" />
</head>
<body>
    <div class="wrap-all">
    <div style="text-align:right;">
        <a href="/">Home</a> | 
        <a href="/games/">Games</a> | 
        <a href="/games/Index2/1">Games <em>(paginated)</em></a> | 
        <a href="/categories/">Categories</a> | 
        <a href="/upload/">Upload</a>
    </div>
        <asp:ContentPlaceHolder ID="MainContent" runat="server">

        </asp:ContentPlaceHolder>


        <!--This errors on any non-CategoryController page.-->
            <%= Html.Action("GetCategoriesPartial")%>
        <!---->

    </div>
</body>
</html>

This code works as long as I'm viewing something handled by the CategoriesController. If I go to any view handled by a different controller, I get the exception:

System.Web.HttpException: A public action method 'GetCategoriesPartial' was not found on controller 'wopr.Controllers.GamesController'.

How do I make this partial view available to all the site's controllers?

Thanks for any help. Quakkels

+2  A: 

Put it in the views\shared folder

However looking at your error message, something else seem to be happening. You cannot use <%=Html.Action%> to render a view. You should use <%=Html.RenderPartial("ViewName")%>

Ropstah
Forgive my noob ways :-) How would that work? I thought that no logic should be executed in views. or is it ok to put the definition for a partial view's action in there?
Quakkels
Its not really logic its markup you are including. A user control is just essentially a compact view.
James
Renderpartial doesn't require a controller/action. As Pharabus states, it looks inside the current controller folder, and then in the shared folder for the view. You can pass a second parameter as a model in the partial view (optionally passed as viewdata through the controller and masterpage)
Ropstah
I used <%=Html.Action because that is how Phill Haack http://haacked.com/archive/2009/11/18/aspnetmvc2-render-action.aspxrecommended.
Quakkels
@James: I don't understand what you're trying to say..?
Ropstah
@ropstah I was responding to @Quakkels comment `I thought that no logic should be executed in views...`.
James
@Quakkels: I'm sorry for being so definite in my words, ofcourse you can add extra layers to 'serve' partial views. However the way you implement it, requires every controller to have the `GetCategoriesPartial` function. So one 'solution' might be creating a base controller which has a default implementation for `GetCategoriesPartial`. However I'd just go for the `RenderPartial` solution ;)
Ropstah
@ropstah -- note that the action does get some data for the view, you'd need to make sure that every view model that relied on the partial had the category data. I think using RenderAction or Action is probably better in this case. I believe the only issue is that the controller needs to be specified -- since the master is used for all controllers (including those different than the one containing the categories action).
tvanfosson
@tvanfosson: You are absolutely right. Might it be a good solution to make the category data globally available somehow?
Ropstah
@ropstah -- you could use a common view model, I sometimes do that. It will require a bit of plumbing -- I do it using a base controller and overriding OnActionExecuted to add the common data to the view model cast as the common model. Honestly, though this is in an MVC1 app and I might do it differently in MVC2. The RenderAction/Action method looks a lot cleaner.
tvanfosson
+1  A: 

Just to add to ropstah's response, the convention in asp.net-mvc is for a controller to first look in a folder with the same name (less the controller bit) as itself and then to look under the shared folder for a view if it does not find one.

Pharabus
I was literally typing this exact response, you beat me to it! +1
James
+1  A: 

With MVC2 you can now render an action directly in the view. You will need to specify the controller that the action is on, if it isn't rendered from the same controller. Your partial view can be located in the views folder for the controller (instead of shared) if you include it this way. Note that it won't get the ViewData from the current action -- only that set up by the action you are calling.

<% Html.RenderAction( "GetCategoriesPartial", "Category" ) %>

or

<%= Html.Action( "GetCategoriesPartial", "Category" ) %>
tvanfosson
hmmmm this looks cool.
Quakkels
This is EXACTLY what I was looking for.... thanks a bunch!!!
Quakkels
I really should get on with my life.. (read: move to MVC2 :P). That would've got me the accepted answer!
Ropstah
@ropstah - I know what you mean -- I'm going back right now and cleaning up a lot of cruft in an MVC1 app that looked good when I did it, but now that MVC2 has more powerful constructs looks like it was coded by a 5-year old. To be fair (to the platform) much of it also reflects my lack of knowledge while learning it initially. "Who was the idiot that wrote this stuff, oops, it was me..."
tvanfosson
@tvanfosson - I ran into it a few years ago, but I 'physically' ran into it again a month ago: lack of lambda constructs in VB! :)
Ropstah