Locate view bug in MVC3 RChttp://forums.asp.net/t/1623138.aspx/1?Locate+view+bug+in+MVC3+RCTue, 28 Dec 2010 18:44:15 -050016231384167699http://forums.asp.net/p/1623138/4167699.aspx/1?Locate+view+bug+in+MVC3+RCLocate view bug in MVC3 RC <p>Hi,</p> <p>I have a controller in an area (/Areas/Admin/Controller/JackpotController.cs) with the following code</p> <pre class="prettyprint">public ActionResult Index() { return View(); }</pre> <p></p> <p>I have a Razor view here: /Areas/Admin/Views/Jackpot/Index.cshtml.</p> <p>I have a Web Forms view (which is not in an area) here: /Views/Jackpot/Index.aspx.</p> <p>The controller returns the Web forms view and not the Razor view!</p> <p>I removed both views to force this exception, which explains why the wrong view is returned:</p> <h2 style="font-family:Verdana; font-weight:normal; font-size:14pt; color:maroon"> <i>The view 'Index' or its master was not found. The following locations were searched:<br> ~/Areas/Admin/Views/Jackpot/Index.aspx<br> ~/Areas/Admin/Views/Jackpot/Index.ascx<br> ~/Areas/Admin/Views/Shared/Index.aspx<br> ~/Areas/Admin/Views/Shared/Index.ascx<br> ~/Views/Jackpot/Index.aspx<br> ~/Views/Jackpot/Index.ascx<br> ~/Views/Shared/Index.aspx<br> ~/Views/Shared/Index.ascx<br> ~/Areas/Admin/Views/Jackpot/Index.cshtml<br> ~/Areas/Admin/Views/Jackpot/Index.vbhtml<br> ~/Areas/Admin/Views/Shared/Index.cshtml<br> ~/Areas/Admin/Views/Shared/Index.vbhtml<br> ~/Views/Jackpot/Index.cshtml<br> ~/Views/Jackpot/Index.vbhtml<br> ~/Views/Shared/Index.cshtml<br> ~/Views/Shared/Index.vbhtml</i></h2> <p>As you can see, MVC incorrectly searches the root aspx views before the area cshtml views. Correct behavior should be to always search areas first, regardless of view engine.</p> <p>/ Oskar</p> 2010-11-13T21:13:31-05:004167777http://forums.asp.net/p/1623138/4167777.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p></p> <blockquote><span class="icon-blockquote"></span> <h4>dillenmeister</h4> As you can see, MVC incorrectly searches the root aspx views before the area cshtml views. Correct behavior should be to always search areas first, regardless of view engine.</blockquote> <p></p> <p>If you see the MVC source you will find that MVC pick the top most view engine in the view engines stack then start matching the view. If no matching view is found then pick the next view engine and so on. I think this is much feasible that you select a view engine one by one and match it with your view rather then selecting all the regestered view engines only for area concern first and then selecting all the regestered view engines for all non-area concern.</p> <p>For your particular case you can either prioritize razor view engine or use the IView overload of View method.</p> <p>return View(new RazorView(&quot;~/Areas/SomeArea/Views/List/Index.cshtml&quot;));</p> <p>http://forums.asp.net/p/1593209/4041505.aspx</p> 2010-11-14T02:33:12-05:004168243http://forums.asp.net/p/1623138/4168243.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>Thank you for explaining why it chooses the web forms engine before the razor one. I understand that it might have been easier to implement it like that. But you still haven't convinced me that this behavior is better.</p> <p>I think it should first look in Areas, then the root and then Shared, like this:</p> <ol> <li>Area<br> <ol> <li>Web forms</li><li>Razor</li><li>...</li></ol> </li><li>Non-area<br> <ol> <li>Web forms</li><li>Razor</li><li>...</li></ol> </li><li>Shared<br> <ol> <li>Web forms</li><li>Razor</li><li>...</li></ol> </li></ol> <p>But I might be missing something, so please describe a scenario where the current implementation (view engine by view engine) would be better then the order above.</p> 2010-11-14T19:02:10-05:004168249http://forums.asp.net/p/1623138/4168249.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>Just remove the webForms view engine.</p> <p><pre class="prettyprint">protected void Application_Start() { ViewEngines.Engines.RemoveAt(0);</pre><br> <br> </p> 2010-11-14T19:12:27-05:004168252http://forums.asp.net/p/1623138/4168252.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p></p> <blockquote><span class="icon-blockquote"></span> <h4><u style="font:bold 100% serif; border:0pt none; position:static; background:none repeat scroll 0% 0% transparent; margin:0pt; padding:0pt; text-align:left; text-indent:0pt; text-transform:none; color:red">dillenmeister</u></h4> <p></p> <p>I think it should first look in Areas, then the root and then Shared, like this:</p> <p></p> </blockquote> <p></p> <p>The order is view engines. You will get the expected behavior if you change the order of view engines using the code snippet I provided. </p> <p></p> <pre class="prettyprint">protected void Application_Start() { ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new RazorViewEngine()); ViewEngines.Engines.Add(new WebFormViewEngine()); AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); }</pre> <p><br> <br> </p> 2010-11-14T19:15:07-05:004168267http://forums.asp.net/p/1623138/4168267.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC &lt;div style=&quot;color: #000000; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; margin: 8px;&quot; mce_style=&quot;color: #000000; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; margin: 8px;&quot;&gt; <p>Thanks! That is the best way for me to work around the problem.</p> <p>May I ask if you think the current behavior (view engine by view engine) is better than what I suggested (area, non-area, shared)?</p> &lt;/div&gt; 2010-11-14T19:30:14-05:004168271http://forums.asp.net/p/1623138/4168271.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>The current algorithm is better for most developers and better for the MVC framework maintainability. Most applications don't use more than one view engine. Some use 3 or more view engines. In reality this isn't a problem as you probably shouldn't have the same named view for different view engines.<br> </p> 2010-11-14T19:39:14-05:004168284http://forums.asp.net/p/1623138/4168284.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC &lt;div style=&quot;color: #000000; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; margin: 8px;&quot; mce_style=&quot;color: #000000; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; margin: 8px;&quot;&gt; <p>The views will often be named the same regardless of view engine right (Index, Create, Details etc.)?&nbsp;</p> <p>The problem was not that I had Index.aspx and Index.cshtml in the same view folder. I understand that it will pick Index.aspx first if I would do that.&nbsp;The problem was that I had Index.cshtml in an Area view folder and Index.aspx in the root view folder and MVC picks Index.aspx from the root even though the controller with return View(&quot;Index&quot;) was in the area.</p> <p>If you do <b>return View(&quot;Index&quot;)</b> in <b>/Areas/Admin/Controllers/HomeController.cs</b> wouldn't it be more logical and intuitive if it used&nbsp;<b>/Areas/Admin/Views/Home/Index.cshtml</b> rather than&nbsp;<b>/Views/Home/Index.aspx</b>?</p> <p>Sorry if I'm nagging, I'm just surprised that I'm the only one that thinks this is weird:)</p> &lt;/div&gt; 2010-11-14T20:10:41-05:004168299http://forums.asp.net/p/1623138/4168299.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p></p> <blockquote><span class="icon-blockquote"></span> <h4>dillenmeister</h4> <p></p> &lt;div style=&quot;color:#000000;font-family:Verdana, Arial, Helvetica, sans-serif;font-size:12px;background-image:initial;background-attachment:initial;background-origin:initial;background-clip:initial;background-color:#ffffff;margin:8px;&quot; mce_style=&quot;color:#000000;font-family:Verdana, Arial, Helvetica, sans-serif;font-size:12px;background-image:initial;background-attachment:initial;background-origin:initial;background-clip:initial;background-color:#ffffff;margin:8px;&quot;&gt; <p>Sorry if I'm nagging, I'm just surprised that I'm the only one that thinks this is weird:)</p> &lt;/div&gt; <p></p> </blockquote> <p></p> <p>No problem. It's unfortunate that the current design does not work in your particular scenario. However, you should understand that the concept of Areas is not something that is inherent to MVC as a whole. Rather (though i'm simplifying a bit), it's a convention that is implemented in the WebFormViewEngine (and RazorViewEngine in MVC 3).</p> <p>The general contract for view engines in MVC is that there is a collection of them and when a view lookup occurs they are asked in turn to provide a view. The first one wins and any view engine after is not 'questioned'. It's up to the individual view engines to have any fallbacks, such as the &nbsp;Areas pattern fallback. There might exist view engines out there that don't implement areas at all.&nbsp;</p> <p>Unfortunately we cannot change this contract at this point because that would likely result in breaking changes in existing applications. However, you can always implement you own IViewEngine that follows your preferred lookup patterns.</p> <p></p> 2010-11-14T20:41:32-05:004168325http://forums.asp.net/p/1623138/4168325.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p></p> <blockquote><span class="icon-blockquote"></span> <h4>marcind</h4> It's up to the individual view engines to have any fallbacks, such as the &nbsp;Areas pattern fallback. There might exist view engines out there that don't implement areas at all.</blockquote> <p></p> <p>Thank you for taking the time clearing this up for me! I understand why it has to behave the way it does now.</p> 2010-11-14T21:48:20-05:004168721http://forums.asp.net/p/1623138/4168721.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>Oskar, I've written a blog post that addresses your scenario. You can make things work intuitively by a single change in Global.asax (view engine registration). Read about <a href="http://blogs.msdn.com/b/marcinon/archive/2010/11/14/mvc-areas-and-multiple-view-engines.aspx" title="Areas and multiple view engines"> Areas and multiple view engines</a>.</p> 2010-11-15T06:52:57-05:004170547http://forums.asp.net/p/1623138/4170547.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>Very good and informative blog post!</p> <p>Quote from the blog post:</p> <p></p> <blockquote><span class="icon-blockquote"></span> <h4>marcind</h4> This technique should be helpful for those who want to transition their application from Aspx to Razor (or maybe between some other pair of view engines). However, once you are done with the transition you should probably stick to just having one view engine registered to avoid unnecessary searches in the other view engines.</blockquote> <p></p> <p>Do you think the only reason to mix view engines is when transitioning between one to another? Is the best practice to have one view engine only? I can understand this for Razor and web forms, since they are very similar in how they work. But suppose you want to mix different view engines because one view engine solves a certain problem better than the other for some of the views? With the ability in MVC3 to easily choose view engine for each view, I can imagine people mixing view engines quite a bit which would make this a more common issue. Any thoughts on this?</p> 2010-11-16T06:56:28-05:004171240http://forums.asp.net/p/1623138/4171240.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>There is a cost associated with having multiple view engines looking for files on disk, unquestionably. It's a decision you make as a developer what the potential benefits vs. costs are.<br> </p> 2010-11-16T14:24:52-05:004171399http://forums.asp.net/p/1623138/4171399.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>To add to what Brad said, it's a best practice (in any sort of development)&nbsp;to remove unused things from your&nbsp;application (be it code, resources, view engines) to&nbsp;keep it as simple and straigthforward as possible. But if you need something then you need it. Having multiple view engines will have a small initial perf hit, but should not have a signifficant impact throughout the whole life of your app domain. My comment was more about maintaining a clean application.</p> 2010-11-16T15:56:47-05:004173083http://forums.asp.net/p/1623138/4173083.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>Marcin</p> <p>That's interesting ... but it doesn't seem to work, unless I'm missing something.</p> <p>When trying to find views&nbsp;that are not part of an area, you get&nbsp;an exception when using&nbsp;the area-specific view engines.&nbsp; Looking in the source at VirtualPathProviderEngine.GetPath(), if there is no area defined, the set of view locations is only made up of those in ViewLocationFormats ... and that's empty.&nbsp; So, GetPath() throws an exception.</p> <p>Actually, the problem I had/have is slightly different.&nbsp; I do have a combination of Razor and WebForms as part of a migration ... with a preference for Razor.&nbsp; When looking for a view in an area (which is WebForms in part), it actually found a view of a matching name in the default non-area locations which was Razor (rather than the WebForms one I expected) ... but then found the master in the area in Razor (because I'm partially migrated).&nbsp; Result: a nasty mess and a mismatch of view and master!</p> <p>The problem (if problem it is) seems to be that, even when an area is defined, GetPath() falls back to looking in the non-area locations as well.&nbsp; Seems to me that a similar mechanism to the one in controllers to prevent that fallback (perhaps selectively) would be nice.&nbsp; I don't see an easy way to override that, however.</p> <p>Adrian</p> 2010-11-17T16:50:03-05:004173230http://forums.asp.net/p/1623138/4173230.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p></p> <blockquote><span class="icon-blockquote"></span> <h4>adrian pell</h4> <p>That's interesting ... but it doesn't seem to work, unless I'm missing something.</p> <p></p> </blockquote> &nbsp; <p></p> <p>Sorry about that, I've fixed the code in my blog post.</p> <p></p> <blockquote><span class="icon-blockquote"></span> <h4>adrian pell</h4> <p>Actually, the problem I had/have is slightly different.&nbsp; I do have a combination of Razor and WebForms as part of a migration ... with a preference for Razor.&nbsp; When looking for a view in an area (which is WebForms in part), it actually found a view of a matching name in the default non-area locations which was Razor (rather than the WebForms one I expected) ... but then found the master in the area in Razor (because I'm partially migrated).&nbsp; Result: a nasty mess and a mismatch of view and master!</p> <p>The problem (if problem it is) seems to be that, even when an area is defined, GetPath() falls back to looking in the non-area locations as well.&nbsp; Seems to me that a similar mechanism to the one in controllers to prevent that fallback (perhaps selectively) would be nice.&nbsp; I don't see an easy way to override that, however.</p> <p></p> </blockquote> <p></p> <p>I'm having a hard time understanding how to repro your issue. Could you post a list of the relevenat files that exist in your project, what kind of request you are making, and which ones get chosen by the system (and indicate how that differs from what you expect)?</p> 2010-11-17T18:37:11-05:004173279http://forums.asp.net/p/1623138/4173279.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p></p> <blockquote><span class="icon-blockquote"></span> <h4>marcind</h4> <p>Sorry about that, I've fixed the code in my blog post.</p> <p></p> </blockquote> <p></p> <p>Cool - although I guess the presence of that non-existent file name would result in more failed lookups in the file system.&nbsp; Probably for every non-area view lookup.</p> <p></p> <blockquote><span class="icon-blockquote"></span> <h4>marcind</h4> <p>I'm having a hard time understanding how to repro your issue. Could you post a list of the relevenat files that exist in your project, what kind of request you are making, and which ones get chosen by the system (and indicate how that differs from what you expect)?</p> <p></p> </blockquote> <p></p> <p>Sorry - let me try again!&nbsp; Here's the rough filesystem layout ...</p> <p>Views<br> ... Admin <em>[this is the area name]</em><br> ... ... Home<br> ... ... ... Index.aspx&nbsp;&nbsp; <em>&lt;--- this is the view I expect to render</em><br> ... ... Shared<br> ... ... ... _Site.cshtml<br> ... ... ... Site.Master<br> ... Home <em>[this is the controller that is not in a view]</em><br> ... ... Index.cshtml<br> ... Shared<br> ... ... _Site.cshtml</p> <p>The view engines are configured (1) Razor (2) WebForms.</p> <p>When I try to fetch the view for action Admin/Home/Index, here's what I think happens:</p> <ul> <li>Razor view engine looks for Home/Index.cshtml.&nbsp; It tries in the Admin area (fails) then falls back to try in the non-area views, and here it finds Index.cshtml.&nbsp; Of course, this is actually the <strong>wrong</strong> view. </li><li>Now, Razor looks for the&nbsp;layout and finds _Site.cshtml in the Admin area (this is here because I'm gradually migrating) </li><li>Unfortunately, the Admin layout&nbsp;does not have some sections defined that the regular one does, so the view engine fails to render.&nbsp; It would have rendered the wrong thing anyway.</li></ul> <p>It seems to me that the fallback in GetPath()&nbsp;to look in the non-area view locations is the problem here.&nbsp; I suppose that makes sense when looking for masters and, perhaps, partials - but it seems wrong for the actual base view.&nbsp; I don't see a way to change the behavior however without a trick similar to yours.</p> <p>FWIW, the solution you updated does work in this case, although as I said I'd be concerned about the extra file system accesses for the non-existent file (especialy since this is for views in my normal site, as opposed to the area).&nbsp; I just ran Process Monitor against that, and it does seem to do a bunch of lookups, although most of them are apparently for the compiled version of the non-existent page ... whcih doesn't exist either.</p> <p>&nbsp;</p> <p>Adrian</p> <p>&nbsp;</p> <p>&nbsp;</p> 2010-11-17T19:13:45-05:004173288http://forums.asp.net/p/1623138/4173288.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>BTW - although this particular problem is expressed in terms of two view engines, the same thing could happen in the case of a single view engine where the appropriate view is missing inside the area (not written yet, etc.)&nbsp; Rather than giving a rendering error (cannot find view), it&nbsp;could render the wrong thing (from the base, not the area) with potential for mis-matching&nbsp;layouts, models and who knows what else.</p> <p>I know that avoiding duplicate names is best, but that's not always possible.</p> <p>Adrian&nbsp;</p> 2010-11-17T19:21:01-05:004228587http://forums.asp.net/p/1623138/4228587.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p>I don't know if this worked for other people, but it didn't work for me. I just downloaded MVC3 RC2 yesterday. In this release, neither Razor, nor webforms engine scan Areas for views at all.</p> <p>I have Area called &quot;Admin&quot; with AdminController inside of that area. I right clicked on the &quot;Index&quot; method of the controller and added an Index view. I made the following change in Global.asax.cs</p> <p>protected void Application_Start()<br> {<br> &nbsp;&nbsp;&nbsp; ViewEngines.Engines.Clear();&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp; ViewEngines.Engines.Add(new RazorViewEngine());&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp; ViewEngines.Engines.Add(new WebFormViewEngine());&nbsp;&nbsp; </p> <p>&nbsp;&nbsp;&nbsp; AreaRegistration.RegisterAllAreas();&nbsp;&nbsp; </p> <p>&nbsp;&nbsp;&nbsp; RegisterGlobalFilters(GlobalFilters.Filters);&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp; RegisterRoutes(RouteTable.Routes);&nbsp; <br> }</p> <p>When I load Admin area in the browser, it seems that both engines are only searching the root view folder and completely ignoring the areas. Basicaly, it just assumes that Admin&nbsp;was just another controller added in the root and doesn't search anywhere else.&nbsp;See below.</p> <p>The view 'Index' or its master was not found or no view engine supports the searched locations. The following locations were searched:<br> ~/Views/Admin/Index.cshtml<br> ~/Views/Admin/Index.vbhtml<br> ~/Views/Shared/Index.cshtml<br> ~/Views/Shared/Index.vbhtml<br> ~/Views/Admin/Index.aspx<br> ~/Views/Admin/Index.ascx<br> ~/Views/Shared/Index.aspx<br> ~/Views/Shared/Index.ascx</p> <p>What am I missing here?</p> <p>&nbsp;</p> 2010-12-28T15:58:36-05:004228635http://forums.asp.net/p/1623138/4228635.aspx/1?Re+Locate+view+bug+in+MVC3+RCRe: Locate view bug in MVC3 RC <p></p> <blockquote><span class="icon-blockquote"></span> <h4>mlofny</h4> The view 'Index' or its master was not found or no view engine supports the searched locations. The following locations were searched:<br> ~/Views/Admin/Index.cshtml<br> ~/Views/Admin/Index.vbhtml<br> ~/Views/Shared/Index.cshtml<br> ~/Views/Shared/Index.vbhtml<br> ~/Views/Admin/Index.aspx<br> ~/Views/Admin/Index.ascx<br> ~/Views/Shared/Index.aspx<br> ~/Views/Shared/Index.ascx</blockquote> <p></p> <p>This is showing that your <b>Area route </b>is not matched. Try this<br> </p> <p>context.MapRoute(<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;Admin_default&quot;,<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;Admin/{controller}/{action}/{id}&quot;,<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new {<b> controller=&quot;Admin&quot;</b>,action = &quot;Index&quot;, id = UrlParameter.Optional }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; );</p> 2010-12-28T16:50:07-05:00