how to override a views Layout declaration - asp.net-mvc

In asp.net MVC 3 is there a way to override the Layout declaration set in a view from a controller or action filter?
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
I have tried overriding the MasterName property in the OnResultExecuted or the OnResultExecuting like the following code snippet, to no avail.
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var view = filterContext.Result as ViewResult;
view.MasterName = null;
}

Another place where you can control the layout is in the _ViewStart.cshtml.
Here, you can do the logic you need and programatically specify which layout to use. This allows you to place the logic in only one place and keep it out of the view.
#{
if(myBusinessRule)
{
Layout = "~/Views/Shared/_Layout.cshtml";
}
else
{
Layout = "~/Views/Shared/_SecondaryLayout.cshtml";
}
}
Blog post where it was introduced by Scott Gu

You can create an action filter to override Layout file, but if you want to remove it, you will have to create an empty layout file instead of assigning the Master property to null. Like this:
public class OverrideLayoutFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var view = filterContext.Result as ViewResult;
view.MasterName = "_LayoutEmpty";
base.OnResultExecuting(filterContext);
}
}
Controller:
public class HomeController : Controller
{
[OverrideLayoutFilter]
public ActionResult Index()
{
return View();
}
}
Now your new layout file needs to be placed in SharedFolder and you only put the RenderBody function inside
_LayoutEmpty.cshtml
#RenderBody()
Note: If you have sections defined in a view that you want to override layout you will also have to define those sections with an empty content.

Use ViewBag
when you need to change the layout call an action and put the new layout (even null) in viewbag.
#{
Layout = ViewBag.layout;
}
and inside the action
if(something)
ViewBag.layout = "~/Views/Shared/whatever.cshtml";
else
ViewBag.layout = null;

sorry to simply add a ref to one of my previous posts on this subject, but have a look here, it may give a wider view (pun intended) on the topic:
Where and how is the _ViewStart.cshtml layout file linked?

Related

ASP .NET MVC 5 Filter get View.ViewBag data

I need log all GET request url + page.Title. Page title set in _Layout.cshtml as
<title>#ViewBag.Title - #S.AppName</title>
ViewBag.Title set in View.cshtml
ViewBag.Title = S.MemberDayReports;
I try use custom ActionFilterAttribute. In OnResultExecuted(..) ViewBag.Title is null.
I try override in controller OnResultExecuted but ViewBag.Title is null.
How intercept view output and log ViewBag.Title?
override WebViewPage
public abstract class WebViewPageAdv<T> : WebViewPage<T>
{
public string Title
{
get
{
var title = String.Format("{0} - {1}", ViewBag.Title, S.AppName);
//Because Title get in _Layout.cshtml its full page load
//and can log user GET request
return title;
}
}
}
Views\web.config
<pages pageBaseType="WebViewPageAdv">
_Layout.cshtml
<title>#this.Title</title>
Viewbag main purpose is controller to view communication. Value are set to null after redirection.TempData keep the information for the time of an HTTP Request. This mean only from one page to another.Tye to use temp data .
or also you can update a static variable for title in each post/get. Fro this purpose you can use filter to track current and last data. ViewBag wont fullfill your requirement.
The ViewBag description in MSDN describes where ViewBag is used for:
The ViewBag property enables you to dynamically share values from the
controller to the view. It is a dynamic object which means it has no
pre-defined properties. You define the properties you want the ViewBag
to have by simply adding them to the property. In the view, you
retrieve those values by using same name for the property.
If you change the property of the ViewBag in the View is not returned back to the pipeline and it is not visible in either filters or controller.
As a workaround you can set Title property in the view - in this case it would be visible both in filters and in controller:
public ActionResult Index()
{
ViewBag.Title = "Your title";
return View();
}
I haven't checked if values set in the controller are readable this way, but you can set the viewbag like
public class FilterNameHere : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewBag.propertyNameHere = "value here";
}
}
Then register the filter as a GlobalFilter via GlobalAsax such as
GlobalFilters.Filters.Add(new FilterNameHere());

ASP.NET MVC 4 - Portal site

I am new to MVC4 and please bear with me. I am building a portal where users will be shown different widgets. Lets just say widgets are some rectangular boxes with a title and a content. What would be a better way to implement this? i am planning to use partial views. And then call Html.renderaction on the aggregate view. Is this a good choice or is there a better way to do it?
2.)Also when the widget encounters any exception i would like to show a custom error message just on the widget area. I dont want the entire page to be redirected to an error page. Just the rectangle area alone.
#Html.RenderAction should do the work, for the exceptions a try/catch can help you:
[ChildActionOnly]
public ActionResult Widget(int id) {
try
{
var widget = Repository.GetWidget(id);
return PartialView(widget);
}
catch
{
return PartialView("WidgetErrorPage");
}
}
UPDATE:
In that case you can use an ActionFilter to handle the exceptions, like explained here Return View from ActionFilter or here Returning a view with it's model from an ActionFilterAttribute:
public class WidGetHandleException : ActionFilterAttribute
{
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
filterContext.Result = new PartialViewResult
{
ViewName = "WidgetErrorPage",
ViewData = filterContext.Controller.ViewData,
TempData = filterContext.Controller.TempData,
};
}
}
And then decorate all your widget actions like this:
[ChildActionOnly]
[WidGetHandleException]
public ActionResult Widget()
{
}

How to pass an array / model object to master layout page

I've created a xml file (layout.xml) to render its contents to layout page,
Action LoadPage is used to deserialize this xml.
var ser = new XmlSerializer(typeof(MyModel));
string path = Server.MapPath(Url.Content("~/LayoutConfig/layout.xml"));
var arrList = (MyModel)ser.Deserialize(File.OpenRead(path));
How to pass arrList to mvc master layout page.
I've tried setting ViewBag.Data = arrList; it works only for that particular controller action
I know ViewBag is actually just a wrapper around the ViewData object, and its purpose is to use dynamics to access the data instead of using magic strings
And i tried a dirty hack i.e #Html.Action("LoadPage","Main") to the master layot page and its not worked for me..
My question is it possible to get arrList in master layout page on each time it is called.
Any guidance and help is appreciated..!
Well, if you have in every View this key on ViewBag you could read on the Layout page., you could create your own base controller that inherits from Controller class and override the OnActionExecuting method, to fill this property on the ViewBag. In your controllers you should inherit from this custom base controller and you would get in all Views this Key on the ViewBag such as the asp.net mvc does with Title property on the ViewBag. For sample:
public class InfoController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var ser = new XmlSerializer(typeof(MyModel));
string path = Server.MapPath(Url.Content("~/LayoutConfig/layout.xml"));
var arrList = (MyModel)ser.Deserialize(File.OpenRead(path));
ViewBag.ArrayInfo = arrList;
base.OnActionExecuting(filterContext);
}
}
And in the other controllers:
public class HomeController : InfoController
{
public ActionResult Index()
{
return View();
}
}

assigning a layout for an entire controller

is there any way to assign an entire controller to use a certain layout? I know you can assign the layout in the ViewStart, is there anyway for the viewStart to know what controller is being used?
My objective is to have two admin layouts, one with an extra navbar when you are working with anything in the admin controller.
You could write a custom action filter:
public class LayoutAttribute : ActionFilterAttribute
{
private readonly string _layout;
public LayoutAttribute(string layout)
{
_layout = layout;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResult;
if (result != null)
{
result.MasterName = _layout;
}
}
}
and then decorate your controller with it and all actions (returning view results obviously) withing this controller will use the layout you have specified:
[Layout("_SimpleLayout")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
}

Changing the view in an ASP.NET MVC Filter

I want to redirect the user to a different view if they are using a mobile browser. I've decided I'd like to do this using MVC filters by applying it to actions which I want to have a mobile view.
I believe this redirect needs to happen in OnActionExecuted, however the filterContext does not contain information on the view - it does, however in OnResultExecuted, but by this time I believe it is too late to change the view.
How can I intercept the view name and change the ViewResult?
This is what I have in the result executed and what I'd like to have work in Action Executed.
public class MobilePageFilter : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.Result is ViewResult)
{
if (isMobileSite(filterContext.HttpContext.Session[SetMobile.SESSION_USE_MOBILE]))
{
ViewResult viewResult = (ViewResult)filterContext.Result;
string viewName = viewResult.ViewName;
filterContext.Result = new ViewResult
{
ViewName = "Mobile/" + viewName,
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
}
}
base.OnResultExecuted(filterContext);
}
}
I would recommend you the following blog post which explains a better alternative to achieve what you are asking for rather than using action filters.
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!

Resources