views:

7997

answers:

3

I've been playing around with ASP.NET MVC and had a question. Or maybe its a concern that I am doing this wrong. Just working on a lame site to stretch my wings a bit. I am sorry this question is not at all concise.

Ok, here's the scenario. When the user visits home/index, the page should show a list of products and a list of articles. The file layout is such (DAL is my data access layer):

Controllers
    Home
     Index  

Views
    Home
     Index  inherits from ViewPage
    Product   
     List  inherits from ViewUserControl<IEnumerable<DAL.Product>>
     Single  inherits from ViewUserControl<DAL.Product>
    Article
     List  inherits from ViewUserControl<IEnumerable<DAL.Article>>
     Single  inherits from ViewUserControl<DAL.Article>

Controllers.HomeController.Index produces a View whose ViewData contains two entries, a IEnumerable and a IEnumerable.

View.Home.Index will use those view entries to call: Html.RenderPartial("~/Views/Product/List.ascx", ViewData["ProductList"]) and Html.RenderPartial("~/Views/Article/List.ascx", ViewData["ArticleList"])

View.Product.List will call foreach(Product product in View.Model) Html.RenderPartial("Single", product);

View.Article.List does something similar to View.Product.List

This approach fails however. The approach makes sense to me, but maybe someone with more experience with these MVC platforms will recognize a better way.

The above produces an error inside View.Product.List. The call to Html.RenderPartial("Single",...) complains that "Single" view was not found. The error indicates:

The partial view 'Single' could not be found. The following locations were searched:
~/Views/Home/Single.aspx
~/Views/Home/Single.ascx
~/Views/Shared/Single.aspx
~/Views/Shared/Single.ascx

Because I was calling RenderAction() from a view in Product, I expected the runtime to look for the "Single" view within Views\Product. It seems however the lookup is relative the controller which invoked the original view (/Controller/Home invoked /Views/Product) rather than the current view.

So I am able to fix this by changing Views\Product, such that:

View.Product.List will call foreach(Product product in View.Model) Html.RenderPartial("~/Views/Product/Single.ascx", product);

instead of

View.Product.List will call foreach(Product product in View.Model) Html.RenderPartial("Single", product);

This fix works but.. I do not understand why I needed to specify the full path of the view. It would make sense to me for the relative name to be interpreted relative to the current view's path rather than the original controller's view path. I cannot think of any useful case where interpreting the name relative to the controller's view instead of the current view is useful (except in the typical case where they are the same).

Around this time I should have a question mark? To emphasis this actually is a question.

+3  A: 

[edit:

I was thinking, you have 2 cases:

  • the Home controller is the only one that ever references Product / Articles List user control
  • the user controls are shared by several controllers

In the first case, the view user controls really belong to the home controller and it makes sense to put them in the home controller folder. In the second case, it makes sense to place them in the shared folder since they will be shared by controllers.

In either case, maybe you can place them in a sub folder. Like Views/Home/Products and then try RendarPartial("Product/Single") and see what happens? I don't know if it would try to resolve it to: Home/Product/Single and then Shared/Product/Single or not. If sub folders work, it seems to allow the logical seperation of Product and Article, while showing that they are still members of either the Home controller or Shared by all controllers.

]

Check out this blog entry by Steve Sanderson:

http://blog.codeville.net/2008/10/14/partial-requests-in-aspnet-mvc/

What you are doing isn't wrong, but it does seem to sort of go against the convention of View/Controller folder names. That said, it makes sense to want to define controller-agnostic view user controls and nesting them seems valid. So I dunno!

Anyways, the link just describes a method of instead of using RenderPartial to render a use control, it defines a method of RenderPartialRequest that renders the return value (in your case a user control) of a controller action. So you could add a Product and Articles controller with an Action List that returns your user control, and then call those two actions from the Home/Index view. This seems more intuitive to me, but just an opinion.

He also mentions subcontrollers from MVC Contrib, and I'm pretty sure there is desire for something like this to be a part of ASP.NET MVC release.

eyston
RenderPartial("Product/Single") will work for a sub folder in Product View folder. I've just used this technique for some partial views I wish to keep in a sub folder from the rest of the ASPX/ASCX associated with the controller.
RichardOD
+1  A: 

Because I was calling RenderAction() from a view in Product

...

I do not understand why I needed to specify the full path of the view. It would make sense to me for the relative name to be interpreted relative to the current view's path rather than the original controller's view path

The part I think you're misunderstanding is the "execution location" for lack of a better or official term. Paths are not relative to your view, not even your "controller's view" as you put it. They are relative to your request URL, which defines a controller context. I may not be saying it very well, but if you spent a little time in Reflector looking at how URLs and routes are resolved, I think this would all fall into place in your head.

sliderhouserules
What you said makes sense.
Frank Schwieterman
The behavior makes sense, but it doesn't make sense to me why that would be the design though. I don't think the code within the view should need to be aware of the path it is called from.
Frank Schwieterman
+2  A: 

From looking at the MVCStoreFront sample this is how they have everything structured for calling RenderPartial

Views
    Shared
        ProductSingle
        ProductList
        ArticleSingle
        ArticleList

Then render them via:

<% Html.RenderPartial("ProductSingle", ViewData["ProductList"]); %>
<% Html.RenderPartial("ProductList", product); %>
<% Html.RenderPartial("ArticleSingle", article); %>
<% Html.RenderPartial("ArticleList", ViewData["ArticleList"]); %>
Todd Smith