I'm working on an MVC3 Razor web application which gets it's page decoration from a java content management system. As this decoration is shared by every page I've put the retrieval of the CMS content in the _Layout.cshtml file but I'm not entirely happy with the code I've implemented...
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
#{
-- The first two lines are temporary and will be removed soon.
var identity = new GenericIdentity("", "", true);
var principal = new GenericPrincipal(identity, new string[] { });
var cmsInterface = MvcApplication.WindsorContainer.Resolve<ICMSInterface>();
cmsInterface.LoadContent(principal, 2);
}
#Html.Raw(cmsInterface.GetHeadSection())
</head>
<body>
#Html.Raw(cmsInterface.GetBodySection(0))
#RenderBody()
#Html.Raw(cmsInterface.GetBodySection(1))
</body>
</html>
As there is no controller for the _layout file I can't see where else I could put the code to do the retrieval. Here are a few things that I've considered:
Retrieve the CMS content in separate pieces so I don't need the LoadContent call. Unfortunately, because of the component I have to use to retrieve the CMS content this isn't possible, it is all or nothing.
Use a partial view so I can utilise a controller. As I'd need to put the entire page into the partial that option just seems a bit ridiculous.
Call a single static method on some helper class which retrieves the data and adds the three sections to the ViewBag. That will allow me to move the code out of the view and feels like the best solution but I'm still not particularly happy with it.
Does anyone have any other suggestions/comments?
You can use a global action filter to add the required data to the ViewBag in all controllers:
public class LoadCmsAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (!filterContext.IsChildAction &&
!filterContext.HttpContext.Request.IsAjaxRequest() &&
filterContext.Result is ViewResult)
{
var identity = new GenericIdentity("", "", true);
var principal = new GenericPrincipal(identity, new string[] { });
var cmsInterface = MvcApp.WindsorContainer.Resolve<ICMSInterface>();
cmsInterface.LoadContent(principal, 2);
var viewBag = filterContext.Controller.ViewBag;
viewBag.HeadSection = cmsInterface.GetHeadSection();
viewBag.FirstBodySection = cmsInterface.BodySection(0);
viewBag.SecondBodySection = cmsInterface.BodySection(1);
}
}
}
Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
...
filters.Add(new LoadCmsAttribute());
}
One solution:
Create a base controller that each of your controllers inherits from.
Override OnActionExecuted or a similar overview
Add the data to the ViewBog or ViewData in your overridden code
The OnActionExecuted code will be run every time an action is called so you may want to perform some checking to ensure that the action will return a view, etc. There may be a better override OnActionExecuting, OnResultExecuting, etc. but that's the one that comes to the top of my mind.
Another Solution:
Create a filter attribute that you can use to decorate you controllers.
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs
This might be a better solution if you don't want a base controller.
*"As there is no controller for the _layout file".*
The point is that your assumption is not entirely true! As a matter of fact it is quite common to populate the _Layout page with the help of controllers. Let think for instance to the Login/Logout rectangle that is a lot of web sites...it is typically put in the _Layout and handled with a controller.
There is no controller that pass a ViewModel to the Layout...simply because the Layout...is just...a Layout and not something that convey information...howeber it can be a "container" for other contents that in turn may have a ViewModel.
In practice you can invoke Child controllers from the _Layout by using Html.Action or Html.RenderAction...This is the way Login is handled in most of the asp.net Mvc web site...and I suggest you to do the same for your content...fill your content by invoking specialized child controllers, one for each different "area" of the Layout page.
Seems like your answer might be here: child action that obviates need for base controller.
Related
Whenever we make a call to any action controller whether through a full post back or an ajax call I want to check the view model and depending on some logic inject some javascript to be executed in the browser. The application is mostly already coded and what I am trying to do is to have some generic code which can do the trick. I am wondering what would be the best way to do it.
I am thinking about using action filters and then checking the model and then inject the js if required. But not sure how would that work on events like action executed etc. Any code sample will be helpful.
The other option is to do it on the client side. But again not sure how to properly do it in a generic way.
Look into overriding the base controller's OnActionExecuted event, which should provide you access to the view model after it has been processed by the action. I'm curious though, how exactly are you going to inject a snippet of javascript into an AJAX response, which is typically a simple JSON object?
If all you're really asking is how to inject javascript from the controller, you could put the following in your view:
<script type="text/javascript" defer="defer">
#Html.Raw(ViewBag.StartupScript)
</script>
You could add the above to a specific view, or a layout page. Then, you could do something like this:
public class MyController : Controller
{
public override void OnActionExecuted(...)
{
if (...)
{
ViewBag.StartupScript = "alert('hello world!');";
}
}
}
To inject on postback as well as ajax calls here is how you can do it:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
StringBuilder sb = new StringBuilder();
sb.Append("<script type=\"text/javascript\">\n\t");
sb.Append("alert('Hello Injection');");
sb.Append("</script>\n");
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.Write(sb.ToString());
}
else
{
ViewBag.StartupScript = sb.ToString();
}
}
Probably not the cleanest solution, but works.
filterContext.HttpContext.Response.Write(sb.ToString());
this will override the partial view come from Ajax call, any walk around?
I'm new to ASP.net MVC and I want to use a view but with a different Master Page depending on the user Role.
For now, i'm leaning to use one controller who return View1 if the user is in Role1 and View2 is in Role2. View1 and View2 contains the same partial view inside to render the content that is share by both but have a different master page.
I want to know if it's a good practice or if you recommend another design. My solution seems a little bit complicated to do something simple. Maybe I am missing something
Thanks !
You could have a function which returns the master name based on the user role and then write a custom action filter which will execute after the action and set the corresponding master page based on the currently connected user role:
public class MasterChooserAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var result = filterContext.Result as ViewResult;
if (result != null)
{
var user = filterContext.HttpContext.User;
result.MasterName = GetMaster(user);
}
}
private string GetMaster(IPrincipal user)
{
// TODO: based on the current user roles return the proper master page
throw new NotImplementedException();
}
}
and then simply decorate your base controller with this action filter or if this is an ASP.NET MVC 3 application simply declare it as global filter.
Simply select the layout in your view.
You can dynamically change #{Layout = XXX} in mvc 3.
see: http://weblogs.asp.net/scottgu/archive/2010/10/22/asp-net-mvc-3-layouts.aspx
Your controller could check the roles, and assign the layout to use, which then is assigned to #Layout in your view, but you could just as well keep this code in your view to determine the layout to use, since after all it is 'view logic'
There are various ways to select the master page each having their merits.
The simplest would probably be to return the Master page name using the controller View method
public ViewResult Index() {
var masterName = getMasterPageNameForUser(); //get you master page/layout name here
return View("Index", masterName, model);
}
However this will lead to some repetitive code so an alternative could be to create a custom IViewEngine and set the master name there. Scott Hanselman's post and this coder journal post will give you an idea of how to create a custom view engine. From there it's a mater of setting the master name.
What happens if you come up with a third role? Fourth role? Instead of putting that kind of logic in your controller, what if the master page displays different things depending on their role? You could hide whole chunks of <div> or whatnot in the master. Then, you've only got one place to change it whenever the role dependency changes. Is the master page going to be that different based upon the role?
In your controller do
this.ViewBag.Layout = something
in your view
Layout = this.ViewBag.Layout
Are there any good examples of mvc routing wherein every 404 page not found request is routed to a standard view in MVC which basically pulls the content from the database.
Just add this route to the bottom of your RouteTable:
routes.MapRoute("DynamicPages", "{*page}", new { Controller = "DynamicPages", Action = "Show", Page = String.Empty });
And create a controller for displaying dynamic pages from db:
public class DynamicPagesController : Controller
{
public ActionResult Show(string page)
{
var pageContent = DB.GetContentForPage(page);
return Content(pageContent);
}
}
Here's one way to do this: In your global.asax file in Application_Start, you need to set the default controller factory. Override it with an instance of your own factory.
void Application_Start()
{
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());
}
MyControllerFactory should inherit from DefaultControllerFactory and when selecting the controller to use, look in your database for the appropriate page you want to display. If the page exists, select the appropriate controller and override the action in the requestContext.RouteData collection to point at the appropriate action for displaying dynamic pages.
If the requested page doesn't exist, pass back a call to the base method and let it do what it would normally do.
There are other ways you could do it, but this one should work and allows you to intercept the request before you hit the 404 page.
modify the web.config file, you may Reference to this page and look at the setting custom error pages in web.config section.
I've just been bitten by a problem where I have a view (FindUser.aspx) trying to render a partial view (FindUser.ascx). The default search paths for views look for a file named after the view in a variety of folders. Rather surprisingly, for views, it looks for a file with the extensions of .aspx or .ascx. And the partial views use the same list.
Because I've got the two files named the same, the view resolution repeatedly finds the page first, and falls into an endless loop.
I know I can fix this either by calling the view and the partial view different names, or by changing my search locations to be .aspx only for views and .ascx only for partial views.
My question is why does MVC default to looking at both extensions? It seems to make more sense that a view == a page == .aspx and a partial view == a control == .ascx. So why muddy the waters?
Because partial or not, a view is still a view. Having FindUser.aspx and FindUser.ascx is the same as having two regular views with the same name.
I think that the way to avoid the problem you're having is to use different view names. You probably shouldn't have two views whose file name differs only in extension. However, if you really want a strict Page = View, Control = Partial mapping, just create your own ViewEngine by inheriting from WebFormViewEngine and change the view location formats:
public class MyWebFormViewEngine : WebFormViewEngine {
public MyWebFormViewEngine() {
base.ViewLocationFormats
= new string[] {"~/Views/{1}/{0}.aspx", "~/Views/Shared/{0}.aspx" };
base.PartialViewLocationFormats
= new string[] { "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.ascx" };
}
}
Then configure it as your View Engine in Application_Start():
// Call this method during Application_Start to setup your view engine
internal static void SetupViewEngines() {
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new MyWebFormViewEngine());
}
For what it's worth I append "Control" to the name of all of my .ascx ViewUserControls. So I would have FindUser.aspx and FindUserControl.ascx. Doesn't solve the problem but it helps you to avoid it by avoiding naming collisions.
You can give MVC the direct path when rendering Views. Say I have a Foo.aspx in my Home folder and a Foo.ascx partial view in Shared. In your action method you can do either:
return View("~/Views/Shared/Foo.ascx"); // or
return View("~/Views/Home/Foo.aspx");
And it will get the proper one you're looking for.
Reason
View == UserControl in ASP.NET MVC.
Fix
Use different names.
Tip
It`s common convention to name usercontrols with underscore prefix.
If you're using Areas, you'll have to add additional LocationFormats in the constructor:
public class ExtensionBasedWebFormViewEngine : WebFormViewEngine
{
public ExtensionBasedWebFormViewEngine()
{
ViewLocationFormats = new[] {"~/Views/{1}/{0}.aspx", "~/Views/Shared/{0}.aspx"};
AreaViewLocationFormats = new[] {"~/Areas/{2}/Views/{1}/{0}.aspx", "~/Areas/{2}/Views/Shared/{0}.aspx"};
PartialViewLocationFormats = new[] {"~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.ascx"};
AreaPartialViewLocationFormats = new[] { "~/Areas/{2}/Views/{1}/{0}.ascx", "~/Areas/{2}/Views/Shared/{0}.ascx" };
}
}
In MonoRail you can just CancelLayout() to not render the layout. In ASP.NET MVC, the only way to affect the layout seems to be to pass the layout name into the View() method like View("myview", "mylayout"); only it seems that passing null or an empty string doesn't do what I'd want.
I ended up creating an empty layout that just rendered the content, but that seems silly.
"Not Render the layout" means exactly that. In the web forms view engine they call layouts "master pages". I want to render just my action's view and not surround it with the master page.
In MVC 3, you can remove the master layout code with:
#{
Layout = "";
}
At the beginning of view add this:
#{
Layout = null;
}
If you want style sheet to remain, you'll need to add reference to it in that view.
To disable this for all pages, edit the _ViewStart.cshtml (in the root, under the 'Views' folder), and ensure that it contains the following:
#{
Layout = null;
}
And to enable the template for any specific view, the following can be added to the .cshtml file for that view, to enable the template:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
In the Controller action we can set the required layout.
return View("Index", "_LAYOUT_NAME", model);
https://stackoverflow.com/a/5161384/2039603
I see in the right answer it says that "It seems this was impossible in the version of ASP.NET MVC"
Which version are you using? Because I found the solution (I had the same issue) to your problem
So, to Disable Layout in the page, you should use:
#{
Layout = null;
}
And, as suggested here, this could solve your problem:
public ActionResult Index()
{
SampleModel model = new SampleModel();
//Any Logic
return View("Index", "_WebmasterLayout", model);
}
Instead of using a normal view, create a partial view. These can then be used on their own, which acts very much like CancelLayout() - or you can incorporate them into a view that references the Master Page, in which case it will be the full layout. They are also useful if you want to send back a partial HTML chunk in response to an AJAX request.
Not having any luck trying to set the masterPage parameter to "" or null and returning a View (like I didn't)?
Then try this and use PartialView instead:
public ActionResult Article(string id)
{
return PartialView("~/Areas/Store/Views/CustomerService/" + id);
}
I needed to do this to load the contents of a view asynchronously from JS.
It seems this was impossible in the version of ASP.NET MVC I was asking about.
You can create a custom ActionResult that does pretty much anything. The ActionResult controls what is sent back to the client as the response. It would be trivial to create a class that extends ActionResult that does nothing.
One alternative is to actually specify a layout but make that layout empty
"_EmptyLayout.cshtml" that contains nothing or just a comment that says it contains nothing so later someone sees it as intended.