Should calling PartialView() within an action use the layout?
Here's my Actions:
public ActionResult SomeAction()
{
if (Request.IsAjaxRequest())
return PartialView();
else
return View();
}
Here's my _ViewStart.cshtml:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
Scenario #1: Action calls a view with no layout specified.
#{
//Layout = "~/Views/Shared/_SomeLayout.cshtml";
}
<h2>View!</h2>
View's Result: The view is wrapped in a layout
PartialView's Result: The view is not wrapped in a layout
This is backed up by this response from Darin Dimitrov.
Scenario #2: Action calls a view with a layout specified.
#{
Layout = "~/Views/Shared/_SomeLayout.cshtml";
}
<h2>View!</h2>
View's Result: The view is wrapped in a layout
PartialView's Result: The view is still wrapped in a layout
This also seems to be backed up by this other response from Darin Dimitrov. (NOTE: Even though his answer is a catch-all for AJAX requests, this was the response to a question where the OP had two views, one full and one partial.)
So on the first, Darin is explaining that if you don't want a layout, use PartialView(), but in the second one he is saying if you don't want a layout, then here's a workaround.
Can someone explain to me if there is something I'm missing or why it is this way. Regardless of what Darin has said, if I only set the layout in _ViewStart.cshtml, then I can ignore it with PartialView(), but if I set another layout in the View itself, then I can't ignore it.
Does this make sense? Should I be able to ignore both layouts? If not, why?
Rendering process of full page or partial page is same in Razor, because they are using same base classes. For rendering process both full page and partial page create a RazorView object but with different constructor parameters.
From source code these are the two methods for rendering views.
System.Web.Mvc namespace RazorViewEngine.cs
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new RazorView(controllerContext, partialPath,
layoutPath: null, runViewStartPages: false, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator)
{
DisplayModeProvider = DisplayModeProvider
};
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var view = new RazorView(controllerContext, viewPath,
layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator)
{
DisplayModeProvider = DisplayModeProvider
};
return view;
}
See that when creating partial view, layoutPath:null,runViewStartPages:false in constructor parameters. In your scenario #1, this is why View wrapped in a layout but partialView not. According to these methods when returning PartialView, ViewStart does not executed but when returning View it does.
Default layout for partialView is null like specified in the constructor parameter. But after ViewActionResult returns, overriden layout might have been specified. That is what happens in your scenario #2. By declaringLayout = "~/Views/Shared/_SomeLayout.cshtml"; you are specifying WebPageBase.Layout property to some path and this overrides layoutPath:null. Even if you return PartialView() razor will generate complete view. In the same way if you declare Layout = null; in your view and even if you return View() from controller Razor will generate partialView. So whether your view will be partial or complete view not determined by if you return View() or PartialView(). That is determined by whether Layout property is null or some path.So for more readable code, when you want to generate partialView use return PartialView() and do not define any Layout inside your view.
Related
In ASP MVC 4, is it possible from a view to call another controller action?
I'm on http://localhost:57456/Archers and I would like to call a method from the controller Participe (So should be http://localhost:xxxxx/Participe/Action).
This is only applying to this action, so I don't want to redirect every action to this controller.
You can use Html.Action inside your view to call child actions
<div> #Html.Action("Action","Participate") </div>
I would suggest you call your ParticipeController from the first action, and include the controller result into the view Data or the model to return to the view:
public class ArchersController {
public ActionResult Index() {
// your current code here
// your custom call
var result = new ParticipeController().Action("your params here");
ViewData["ParticipeResult"] = result;
// return View();
}
}
Maybe though, some responsibility principles could be applied here in order to isolate the Participe call you want to make here into its own class or method.
Say the requirement is to redirect to another page after successfully saving a model in an ASP.NET MVC controller:
[HttpPost]
public ActionResult Index(ViewModel viewModel)
{
if (ModelState.IsValid)
{
// Do save;
return RedirectToAction("whatever"); // <--- here's the problem
}
// display validation errors and so
return View(viewModel);
}
This would work fine unless the controller was rendered as a Child Action:
#{
Layout = "my layout";
Html.RenderAction("something else, a view for example");
Html.RenderAction("Index action of the above controller"); // <----
}
In this case RedirectResult class would check and see that the context is a child action and would throw the exception: "Child actions are not allowed to perform redirect actions".
I understand that writing to Response stream is already in progress and a redirect cannot take place here but nevertheless one has to be able to redirect to other pages after a server-side action in a controller even if that action is a child action. We use Layouts and RenderActions to reuse the shared parts of the page design.
How would you implement such a redirect in such a controller?
Edit:
The main goal is to reuse View/Controllers that do a specific job and split them to logical concepts like displaying some data or providing an edit form. My approach here is to use RenderAction to render their result into a container. Container view (main page's Index action) acts as a traditional asp.net Page, its Layout view as a Master page and edit and view controller/views are equivalent to User Controls (modules). The problem is there is no way to Redirect the Response after something has been written to it:
I'll submit a new answer in an attempt to keep things clean.
A normal mvc flow goes like this :
Http command reaches 1 controller which acts as a choirmaster(aka controller) and calls several logic containers(e.g services / commandHandlers) :
public ActionResult Index(){
var data = _yourService.FetchData();
return View(data);
}
This controller renders 1 view which can have multiple partials
#{
Layout = "my layout";
}
<p>Some html</p>
Html.RenderPartial("A shared partial");
Html.RenderPartial("shared\yourUserControl", Model.PropertyOrSomething);
If the partial contains too much logic to generate you could add a RenderAction or create an htmlHelper extension.
But neither of these should be able to control the flow of your request, a save or something that can redirect should in my opinion never be called from inside a view.
I assume you want to reuse the code in your controller so badly because it is getting quite big.
My advice would be to try and clear that controller up as much as possible by delegating as much of the logic upwards as you can.
Just by looking at your controller method for 5 seconds you should get an idea of what this action will do, if that's not the case : refactor it ! :)
If i understand correctly you have a parent controller which accepts a saveAction and while rendering the view you call another(?) saveAction as a child.
This flow feels unnatural to me. The parent action and only the parent action should handle the save command. You can render as many child actions as you want as long as they are only used to render some html(as this is what the view does). Don't let them handle redirection or saving, that way of working is everything but transparant for colleges or future you.
The controller controls the flow, not the view.
Edit:
A normal setup would be having 2 actions, eg: Index and Put.
public ActionResult Index(){
//fill model with dropdown data etc
return View();
}
public ActionResult Put(viewModel data){
if (ModelState.IsValid)
{
// Do save;
return RedirectToAction("whatever"); // <--- here's the problem
}
// display validation errors and so
return View("Index",viewModel);
}
Edit2:
If you return View("Index",viewModel) you will generate your index view with it's layout and the validation messages will be located in the modelstate.
Your view however should only have 1 childaction(or more if there are multiple, as long as it's not the save action).
Your Index view could look like this :
#{
Layout = "my layout";
}
Html.RenderAction("something else, a view for example");
#Html.BeginForm("Put","YourController"){
//all your input controls which will also show the validation errors
}
Edit 3:
If you want to reuse html code you should use #Html.Partial or Html helper extension methods. Note that if you pass no model the parents model is passed but you can pass a submodel to match the type safety of the partial.
It would look something like this :
#{
Layout = "my layout";
}
Html.RenderAction("something else, a view for example");
Html.RenderPartial("shared\yourUserControl", Model.PropertyOrSomething);
Try using AJAX in your view to do this, and instead of returning a RedirectToAction in your controller, return a JSON object:
View:
$.ajax({
url: '<%: Html.ResolveUrl("~/ControllerFolder/ControllerName/") %>',
type: "POST",
data: data,
success: function (result) {
$("#Div").html(result);
if (result.redirectUrl != null)
{
window.location = result.redirectUrl;
}
}
});
Controller:
[HttpPost]
public ActionResult Index(ViewModel viewModel)
{
if (ModelState.IsValid)
{
// Do save;
return Json(new { redirectUrl = Url.Action("NewAction", "NewController", RouteValues)});
}
// display validation errors and so
return View(viewModel);
}
Hope this helps...
It's really easy to return a different View from the Controller:
return View("../Home/Info");
However, I need a model in the Info view. I have a lot of stuff going on in the Info() action result method. I can just copy it and have something like this:
var infoModel = new InfoModel {
// ... a lot of copied code here
}
return View("../Home/Info", infoModel);
But that is not reasonable.
Of course I can just redirect:
return RedirecToAction("Info");
But this way the URL will change. I don't want to change the URL. That's very important.
You can call right to another action from within an action, like this:
public ActionResult MyAction(){
if(somethingOrAnother){
return MyOtherAction();
}
return View();
}
//"WhichEverViewYouNeed" is required here since you are returning this view from another action
//if you don't specify it, it would return the original action's view
public ActionResult MyOtherAction(){
return View("WhichEverViewYouNeed", new InfoModel{...});
}
It looks like you want to invoke an action from a different controller. I'd suggest that you might want to simply render a view that renders that action using Html.Action() instead of trying to tie the two together in the controller. If that's unreasonable then you might want to create a base controller that both controllers can derive from and put the shared code to generate the model in base controller. Reuse the view as needed.
public ActionResult Foo()
{
return View();
}
Foo View
#Html.Action( "info", "home" )
Why not just invoke the method of the action?
In Razor, when loading a partial view, it is possible to just specify the partial view name, and the Razor view engine will search the RazorViewEngine.PartialViewLocationFormats:
#Html.RenderPartial("_PartialView", Model);
will actually search the locations specified in PartialViewLocationFormats in the view engine, such as for example
~/Views/Home/_PartialView.cshtml
~/Views/Shared/_PartialView.cshtml
However, when specifying the Layout, I seem to be forced to specify a specific path to the layout:
#Layout = "~/Views/Shared/MyLayout.cshtml";
What I would like to do would be to specify the layout just by name, and have the the actual layout be found by searching a list of common locations:
#Layout = "MyLayout";
...but I can't find any facilities to do so. Since I could not find any documentation regarding this, I tried playing with setting RazorViewEngine.MasterLocationFormats, but this property is not used when locating layouts.
Does anybody know how to do this?
Recently I was struggling on a similar issue where, in a themed application that uses a custom ViewEngine to search theme's location first for views, I was trying to override some master files. One way to force the location of the layout to go through the ViewEngine's FindView is to specify the name of the master view in a controller action when returning:
return View("myView", "myViewMaster");
However, if "myViewMaster" also has a layout (nested layouts), this method doesn't work for the nested layout. My best solution so far is to call the view engine directly in the view:
#{
Layout = (ViewEngines.Engines.FindView(this.ViewContext.Controller.ControllerContext, "myViewMaster", "").View as RazorView).ViewPath;
}
This works but could certainly be encapsulated in a extension method to avoid repetition and abstract the code.
Hope it helps!
I tried a couple of different things and couldn't find a way to do what you wanted. However, you could just leave the Layout blank and use the _ViewStart.cshtml file to assign the layout for each page using a ViewBag property is something.
So if you had an enum (for instance) and you set the dynamic viewbag property of CustomLayout to a value of that enum, you could use the _ViewStart file to do something like this.
#{
MvcApplication12.Controllers.CustomLayouts customLayout = this.ViewContext.Controller.ViewBag.CustomLayout;
switch (customLayout)
{
case MvcApplication12.Controllers.CustomLayouts.Blue: Layout = "~/Views/Shared/Blue.cshtml"; break;
case MvcApplication12.Controllers.CustomLayouts.Green: Layout = "~/Views/Shared/Green.cshtml"; break;
case MvcApplication12.Controllers.CustomLayouts.Default:
default: Layout = "~/Views/Shared/Default.cshtml"; break;
}
}
Not sure if this helps or not.
I took the accepted answer from #sowee15 and added an extension method to help wrap it up a bit:
using System;
using System.Web.Mvc;
namespace MyApp.Classes
{
public static class ViewEngineCollectionExtensions
{
public static string FindViewPath(this ViewEngineCollection viewEngines, ControllerContext controllerContext, string viewName, string masterName = null)
{
var viewResult = ViewEngines.Engines.FindView(controllerContext, viewName, masterName ?? string.Empty);
if(viewResult == null)
throw new Exception(string.Format("The specified view {0} could not be found.", viewName));
var view = viewResult.View as RazorView;
if(viewResult == null)
throw new Exception(string.Format("The specified view {0} must be a Razor view.", viewName));
return view.ViewPath;
}
}
}
Then you can call extension from your view or viewstart.cshtml like so:
#{
Layout = ViewEngines.Engines.FindViewPath(ViewContext.Controller.ControllerContext, "_PopupLayout");
}
Is there a way that I can see, while debugging, the result (html, xml, json) of a Controller's action.
For instance, I have this in my controller:
public PartialViewResult Reload()
{
return PartialView("Index");
}
While debugging, I'd like to see what HTML will be sent back to the client.
Thanks
Browser will show the result anyway
Why would you want to see it in debug mode, while you can see it in your browser anyway. That's where it goes afterwards anyway. If JSON is in question use Firefox + Firebug and you'll see all requests and responses with all related data and info.
If you tell us why exactly would you like to do this, we may suggest a better way of getting you there...
A possible solution
The most likely solution to jump in while data is being rendered is to write your own view engine. And to make it easier on yourself, inherit from RazorViewEngine. This will likely spawn some more class inheriting (i.e. RazorView) but it would get you to a point where view actually get's generated and would do that in an efficient way: view would get rendered only once.
But on the other hand I'd question the rationale again. If you'd be looking at generated HTML right before it gets to the client, what point does that have? Because the next moment this same data gets sent to the client, where you can already observe it without any code manipulation and putting custom code in Asp.net MVC's extensibility points.
Intermediate view manipulation in Asp.net MVC?
No such thing in Asp.net MVC...
You referred in a comment below to observing intermediate view changes during debug when doing things like c.Controls.Add(), but this just wouldn't work, because:
controller action doesn't manipulate views in any way shape or form; it merely prepares data for them and chooses which view will handle that data in however way they implement it; the choosing part can as well be abstracted away from controller actions in which case they'd only prepare data and handle it to some other view-choosing factory;
views are being generated in whole by view engines using ASPX/ASCX/CSHTML files merely as templates:
Web forms view engine does parse those templates into object hierarchy, but if most of your view's code is static HTML (which it should be), majority of those controls will just be literals;
Razor view engine doesn't use any control hierarchy object model as Web forms engine does; Razor views are just strings, that engine parses character-by-character, line-by-line looking for special directives where it needs to insert view data in; no controls whatsoever...
All in all: no intermediate view manipulation. You can however debug your view while it's being parsed by the view engine though. Just put breakpoints inside a directive within the view.
It is not easy, because partial view result is sent directly to httpcontext.current.response.output, it doesn't return string. You can use this extension method to catch it as string by filtering httpcontext output:
/// <summary>Renders a view to string.</summary>
public static string RenderViewToString(this Controller controller,
string viewName, object viewData)
{
//Getting current response
var response = HttpContext.Current.Response;
//Flushing
response.Flush();
//Finding rendered view
var view = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName).View;
//Creating view context
var viewContext = new ViewContext(controller.ControllerContext, view,
controller.ViewData, controller.TempData);
//Since RenderView goes straight to HttpContext.Current, we have to filter and cut out our view
var oldFilter = response.Filter;
Stream filter = new MemoryStream(); ;
try
{
response.Filter = filter;
viewContext.View.Render(viewContext, null);
response.Flush();
filter.Position = 0;
var reader = new StreamReader(filter, response.ContentEncoding);
return reader.ReadToEnd();
}
finally
{
filter.Dispose();
response.Filter = oldFilter;
}
}
and use it like this during debug:
public PartialViewResult Reload()
{
var result = RenderViewToString("Index",ViewData);
return PartialView("Index");
}
Extension method for Spark:
public static string RenderSparkToString(this Controller controller,
string viewName, object viewData)
{
var view = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName).View;
//Creating view context
var viewContext = new ViewContext(controller.ControllerContext, view,
controller.ViewData, controller.TempData);
var sb = new StringBuilder();
var writer = new StringWriter(sb);
viewContext.View.Render(viewContext, writer);
writer.Flush();
return sb.ToString();
}
What prevents you from just hitting the Route that renders the partial view and view source?