Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.Net MVC and RenderPartial w/ relative paths

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<DAL.Product> and a IEnumerable<DAL.Article>.

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.

like image 991
Frank Schwieterman Avatar asked Nov 10 '08 07:11

Frank Schwieterman


3 Answers

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.

like image 198
sliderhouserules Avatar answered Sep 17 '22 22:09

sliderhouserules


[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.

like image 38
anonymous Avatar answered Sep 20 '22 22:09

anonymous


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"]); %>
like image 44
Todd Smith Avatar answered Sep 17 '22 22:09

Todd Smith