Saturday, January 15, 2011

ASP.NET MVC controller action methods that return views don't care whether that view is technically implemented as a full view or a partial view. You can just as easily return a view created as a partial as easily as a standard, full view and the controller cares not. Nor, it seems, does the view engine. It will happily render out the content of the view as it is defined in its markup file. This provides a handy mechanism by which a partial view can be used to perform partial page updates via AJAX. An initial load of a full view can include a call to render a partial view, then a subsequent call from AJAX can explicitly return just the partial view. I've used this technique several times with the original ASPX view engine and not had any issues. However, once I started playing with the Razor view engine, I encountered a strange side effect.

Both the ASPX and Razor view engines can define the master page/layout to use at a global level. For ASPX it's in the web.config:

ASPX view engine partial views (*.ascx) include (apart from the obvious file extension difference) a specific directive instructing the view engine that it is a partial view (@ Control) and not a regular view (@ Page). This causes the view engine to knowingly treat it as a partial view, rendering only its contents. However, since Razor views are all the same there is no declaration to indicate that the view code in one .cshtml file is for a partial view versus a full view. Any Razor view will have the same startup rules applied.

When a Razor view is rendered as a partial from another view, such as:

the view engine seems to recognize that the view is being loaded as a partial and (presumably) changes the inner view's Layout property to null and things work as desired.

However, when you make an MVC action call that specifically loads JUST a partial view as the ViewResult with the intent of doing a partial page refresh, the results will differ based on the the view engine. For an ASPX engine partial view (i.e. *.ascx) the view will load and render as is. It contains no references to a master page and the view engine knows explicitly that it is a "@ Control" and thus does not wrap it in any default master page, if one is defined. On the other hand, a Razor view is loaded as any other view and will include the master layout set by default. What is intended as a partial page update will actual return with all of the layout pages HTML around it. In order to keep a Razor view as a partial view, any existing value for the view's Layout property must be negated. This code can be placed in a code block of any view that is intended to be a partial view:

@{ this.Layout = null;}

This will cancel out any default master layout setting and render the view with only that view's contents. When the same view is included in another view as a partial, the above code will have not effect since the calling view has already nulled out the layout file anyway.

If you wanted to, you could use a view for either a full view (with the default layout's content) or a partial view by conditionally nulling out the Layout property based on some view data set by an appropriate controller action. Combined with a parameter passed in by the AJAX caller, this could provide a useful mechanism for reusing view elements.

7 comments:

I ran into this problem just today (I am learning razor by myself) this post was very helpful.I had a problem where I was doing a RenderAction within the layout view, I was not aware of the _ViewStart file so since the controler rendered was returning a ViewResult I got a stack overflow due load the layout recursively.What i did is I just changed the return type of my Controller to PartialView and that was it. I learnt a lot just by resarching this, thanks.

This was exactly what I was searching for - an explanation of partial views with the Razor view engine. However, the better solution to my problem was actually to use Pancho's suggestion of using PartialView() in the controller action rather than changing the layout in the view.

@Pancho - Thank you for pointing out the PartialView() method. Somehow, I never saw that. It makes sense that they'd include that in the controller for just such scenarios. It's certainly a better and clearer solution to using the standard View() method and having to nullify the layout property in the partial view markup. Obviously I haven't explored enough.