Order in which Views are searched in MVC - asp.net-mvc

In what way views are searched in route.config file. I want the order in which views are searched.
e.g.
~/Views//Home/Index
~/Views/Shared/Home/Index

By default, MVC view engine searches available view cshtml files in these locations in order from top to bottom:
~/Views/ControllerName/ActionName.cshtml
~/Views/Shared/ActionName.cshtml
~/Views/Shared/LayoutName.cshtml (for layout files)
Either changing or re-ordering view engine search method requires creating a new class like this one:
public class CustomViewSearch : RazorViewEngine
{
public CustomViewSearch()
{
MasterLocationFormats = new[]
{
"~/Views/Shared/{0}.cshtml"
};
ViewLocationFormats = new[]
{
// you can change view search order here
// {0} = action name, {1} = controller name
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{1}/{0}.cshtml"
};
PartialViewLocationFormats = ViewLocationFormats;
FileExtensions = new[]
{
"cshtml"
};
}
}
Then, place your custom view search method on Global.asax inside Application_Start method:
protected void Application_Start()
{
// remove all existing view search methods if you want
ViewEngines.Engines.Clear();
// add your custom view search method here
ViewEngines.Engines.Add(new CustomViewSearch());
}
Any suggestions welcome.

Related

ViewStart.cshtml is not getting rendered in Custom View Engine

I have defined my custome view engine as below just to add more locations to search for.
public class CustomViewEngine : RazorViewEngine
{
private static string[] AdditionalViewLocations = new[]
{
"~/{1}/{0}.cshtml"
};
public CustomViewEngine() : base()
{
MasterLocationFormats = MasterLocationFormats.Union(AdditionalViewLocations).ToArray();
PartialViewLocationFormats = PartialViewLocationFormats.Union(AdditionalViewLocations).ToArray();
ViewLocationFormats = ViewLocationFormats.Union(AdditionalViewLocations).ToArray();
}
}
Now issue i am facing is, the layout is not getting applied for any of the views which was set in _ViewStart.cshtml page. Appreciates your help.

Using MVC 4 areas at custom path

I'm trying to use areas at a custom path, and I'm having issues. I've been googeling a bunch, but havent found a solution.
My project is a EPiServer CMS project (which shouldn't have any effect I think, just wanna mention it, in case it does)
My structure is
Root
CompanyName
Areas
Commerce
Controllers
Models
Views
Cms
Controllers
HomePageController
Models
Views
HomePage
Index.cshtml
So I have a layer more to the tree then 'normal' which is the 'CompanyName'
I have this in global.asax.cs
protected void Application_Start()
{
ViewEngines.Engines.Add(new AreaTemplateViewEngineDynamic());
AreaRegistration.RegisterAllAreas();
...
}
I have a Custom RazorEngine (Could have just added more paths to the default, but have this solution as of now)
public class AreaTemplateViewEngineDynamic : RazorViewEngine
{
public AreaTemplateViewEngineDynamic()
{
this.PartialViewLocationFormats = this.ViewLocationFormats = this.MasterLocationFormats =
new string[]
{
"~/CompanyName/Views/{1}/{0}.cshtml", "~/CompanyName/Views/Shared/{0}.cshtml"
};
this.AreaMasterLocationFormats = this.AreaPartialViewLocationFormats = this.AreaViewLocationFormats =
new string[]
{
"~/CompanyName/Areas/{2}/Views/{1}/{0}.cshtml", "~/CompanyName/Areas/{2}/Views/Shared/{0}.cshtml"
};
}
}
Adding this area registration
public class CmsAreaRegistration: AreaRegistration
{
public override string AreaName
{
get { return "Commerce"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Cms_default",
"Cms/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "Root.CompanyName.Areas.Cms.Controllers" }
);
}
}
When I try to load the page, it seems it doesnt look at the Area paths, only the non-area paths.
The view 'index' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/HomePage/index.aspx
~/Views/HomePage/index.ascx
~/Views/Shared/index.aspx
~/Views/Shared/index.ascx
~/Views/HomePage/index.cshtml
~/Views/HomePage/index.vbhtml
~/Views/Shared/index.cshtml
~/Views/Shared/index.vbhtml
~/CompanyName/Views/HomePage/index.cshtml
~/CompanyName/Views/Shared/index.cshtml
The path I want it to find is
~/CompanyName/Areas/Cms/Views/HomePage/index.cshtml
Also if I had to use
#{Html.RenderAction("MiniCart", "Cart", new { area = "Commerce"} );}
I would expect it to finde
~/CompanyName/Areas/Commerce/Views/Cart/MiniCart.cshtml
You are only setting the location for the AreaMasterLocation when you should also set the following locations:
AreaPartialViewLocationFormats
AreaViewLocationFormats
Find the following class in the object browser: VirtualPathProviderViewEngine for more properties and methods.
I wrote my own RazorViewEngine, where I added some custom codes to finding paths.
Could just use the URL, because the URL was controlled by the CMS, so the URL didnt represent the MVC path.

cannot find partial views under areas shared folder

I have a layout page under
~/Areas/Admin/Shared/_Layout.cshtml
Now inside that I have a section where I was supposed to render a partial view . So what I did inside _layout.cshtml was to provide #Html.RenderAction("Sidebar")
The controller is actually basecontroller which is inherited to all other controllers . as
[OutputCache(Duration=60)]
public partial class BaseController : Controller
{
[ChildActionOnly]
public virtual ActionResult Sidebar()
{
return View();
}
}
Now this Controller is supposed to be interited by all X,Y , Z controllers so the childaction Sidebar would be available to all of them so that #Html.Renderaction("Sidebar") doesnt have the trouble to find the child action to be rendered .
Now the problem is the partialview path is under /Areas/Admin/Views/Shared/Partials/Sidebar/cshtml
I have also configured the razor view engine to find under that particular /Areas/Admin/Views/Shared/Partials/Sidebar.cshtml. And registered it under global.asax.
But its unable to find the partial view and giving the error as
~/Areas/Admin/Views/Admin/Sidebar.aspx
~/Areas/Admin/Views/Admin/Sidebar.ascx
~/Areas/Admin/Views/Shared/Sidebar.aspx
~/Areas/Admin/Views/Shared/Sidebar.ascx
~/Views/Admin/Sidebar.aspx
~/Views/Admin/Sidebar.ascx
~/Views/Shared/Sidebar.aspx
~/Views/Shared/Sidebar.ascx
~/Areas/Admin/Views/Admin/Sidebar.cshtml
~/Areas/Admin/Views/Admin/Sidebar.vbhtml
~/Areas/Admin/Views/Shared/Sidebar.cshtml
~/Areas/Admin/Views/Shared/Sidebar.vbhtml
~/Admin/Sidebar.cshtml
~/Views/Admin/Sidebar.vbhtml
~/Views/Shared/Sidebar.cshtml
~/Views/Shared/Sidebar.vbhtml
My custom razor view engine is
public class LocalizedViewEngine : RazorViewEngine
{
///{0} = View Name
///{1} = Controller Name
private static readonly string[] NewPartialViewFormats = new[] {
"~/Areas/Admin/Views/{1}/Partials/{0}.cshtml",
"~/Areas/Admin/Views/Shared/Partials/{0}.cshtml",
"~/Views/Shared/Partials/{0}.cshtml",
"~/Views/{1}/Partials/{0}.cshtml"
};
private static readonly string[] NewViewLocationFormats = new[] {
"~/Areas/Admin/Views/{1}/{0}.cshtml"
};
public LocalizedViewEngine()
{
base.ViewLocationFormats =
base.ViewLocationFormats.Union(NewViewLocationFormats).ToArray<string>();
base.PartialViewLocationFormats =
base.PartialViewLocationFormats.Union(NewPartialViewFormats).ToArray<string>();
}
}
And My global.asax contains
ViewEngines.Engines.Add(new LocalizedViewEngine());
But its unable to find the partial view under tha ~/Areas/Admin/Views/Shared/Partials/Sidebar.cshtml . Where am I going wrong ?

How can I make FindPartialView search inside my Area?

So I register all Areas in Global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
//...
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
But in my /Areas/Log/Controllers, when I try to find a PartialView:
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, "_LogInfo");
It fails, viewResult.SearchedLocations is:
"~/Views/Log/_LogInfo.aspx"
"~/Views/Log/_LogInfo.ascx"
"~/Views/Shared/_LogInfo.aspx"
"~/Views/Shared/_LogInfo.ascx"
"~/Views/Log/_LogInfo.cshtml"
"~/Views/Log/_LogInfo.vbhtml"
"~/Views/Shared/_LogInfo.cshtml"
"~/Views/Shared/_LogInfo.vbhtml"
And thus viewResult.View is null.
How can I make the FindPartialView search in my Area?
Update:
This is my custom view engine, which I have registered in Global.asax:
public class MyCustomViewEngine : RazorViewEngine
{
public MyCustomViewEngine() : base()
{
AreaPartialViewLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml"
};
PartialViewLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
// and the others...
}
}
But the FindPartialView doesn't use the AreaPArtialViewLocationFormats:
"~/Views/Log/_LogInfo.cshtml"
"~/Views/Shared/_LogInfo.cshtml"
I had exactly the same problem, I have a central Ajax controller I use, in which I return different partial views from different folders/locations.
What you are going to have to do is create a new ViewEngine deriving from a RazorViewEngine (I'm assuming your using Razor) and explicitly include new locations in the constructor to search for the partials in.
Alternatively you can override the FindPartialView method. By default the Shared folder and the folder from the current controller context are used for the search.
Here is an example which shows you how to override specific properties within a custom RazorViewEngine.
Update
You should include the path of the partial in your PartialViewLocationFormats array like this:
public class MyViewEngine : RazorViewEngine
{
public MyViewEngine() : base()
{
PartialViewLocationFormats = new string[]
{
"~/Area/{0}.cshtml"
// .. Other areas ..
};
}
}
Likewise if you want to find a partial in a Controller inside the Area folder then you will have to add the standard partial view locations to the AreaPartialViewLocationFormats array. I have tested this and it is working for me.
Just remember to add the new RazorViewEngine to your Global.asax.cs, e.g.:
protected void Application_Start()
{
// .. Other initialization ..
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new MyViewEngine());
}
Here is how you may use it in an exemplary controller called "Home":
// File resides within '/Controllers/Home'
public ActionResult Index()
{
var pt = ViewEngines.Engines.FindPartialView(ControllerContext, "Partial1");
return View(pt);
}
I have stored the partial I'm looking for in the /Area/Partial1.cshtml path.

MVC 2 AreaRegistration Routes Order

I noticed that in MVC 2 Preview 2, AreaRegistration is loading the routes for each area in an arbitrary order. Is there a good way to get one before the other?
For example, I have two areas - "Site" and "Admin". Both have a "Blog" controller.
I would like the following:
/admin/ --> go to Admin's Blog controller
/ --> go to Site's Blog controller.
The problem is that it is loading the site's route first, so it is matching {controller}/{action}/{id} instead of admin/{controller}/{action}/{id} when I go to the url "/admin/". I then get a 404, because there is no Admin controller in the "Site" area.
Both areas default to the "Blog" controller. I realize I could simply put site/{controller}/... as the url, but I would rather have it at the root if possible. I also tried keeping the default route in the global RegisterRoutes function, however, it is then not sent to the "Sites" area.
Thanks in advance!
Aside from what Haacked said, it is very much possible to order area registrations (and thus their routes). All you have to do is register each area manually, in whatever order you want. It's not as sleek as calling RegisterAllAreas() but it's definitely doable.
protected void Application_Start() {
var area1reg = new Area1AreaRegistration();
var area1context = new AreaRegistrationContext(area1reg.AreaName, RouteTable.Routes);
area1reg.RegisterArea(area1context);
var area2reg = new Area2AreaRegistration();
var area2context = new AreaRegistrationContext(area2reg.AreaName, RouteTable.Routes);
area2reg.RegisterArea(area2context);
var area3reg = new Area3AreaRegistration();
var area3context = new AreaRegistrationContext(area3reg.AreaName, RouteTable.Routes);
area3reg.RegisterArea(area3context);
}
Another option is to take the code for RegisterAllAreas(), copy it into your own app, and build your own mechanism for determining the order. It is quite a bit of code to copy if you want all the fancy caching logic that the built-in method does, but your app might not even need that.
Currently it's not possible to order areas. However, I think it makes sense to try and make each area as independent from other areas as possible so the order doesn't matter.
For example, instead of having the default {controller}/{action}/{id} route, maybe replace that with specific routes for each controller. Or add a constraint to that default route.
We are mulling over options to allow ordering, but we don't want to overcomplicate the feature.
I make this solution:
AreaUtils.cs
using System;
using System.Web.Mvc;
using System.Web.Routing;
namespace SledgeHammer.Mvc.Site
{
public static class Utils
{
public static void RegisterArea<T>(RouteCollection routes,
object state) where T : AreaRegistration
{
AreaRegistration registration =
(AreaRegistration)Activator.CreateInstance(typeof(T));
AreaRegistrationContext context =
new AreaRegistrationContext(registration.AreaName, routes, state);
string tNamespace = registration.GetType().Namespace;
if (tNamespace != null)
{
context.Namespaces.Add(tNamespace + ".*");
}
registration.RegisterArea(context);
}
}
}
In global.asax:
Utils.RegisterArea<SystemAreaRegistration>(RouteTable.Routes, null);
Utils.RegisterArea<ClientSitesAreaRegistration>(RouteTable.Routes, null);
//AreaRegistration.RegisterAllAreas(); do not dublicate register areas
No requred changes to generated area registration code.
I also use custom constrant in routes to filter routes by type of domain in request (system domain or user site).
This is my area registrations as example:
namespace SledgeHammer.MVC.Site.Areas.System
{
public class SystemAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "System"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"System_Feedback",
"Feedback",
new { controller = "Feedback", action = "Index" }
);
context.MapRoute(
"System_Information",
"Information/{action}/{id}",
new { controller = "Information", action = "Index", id = UrlParameter.Optional }
);
}
}
}
namespace SledgeHammer.MVC.Site.Areas.ClientSites
{
public class ClientSitesAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "ClientSites"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"ClientSites_default",
"{controller}/{action}/{id}",
new { controller = "Site", action = "Index", id = UrlParameter.Optional },
new { Host = new SiteInGroups("clients") }
);
}
}
}
For reference,
In MVC3 (don't know about MVC2) when you just want to map root to a specific area/controller you could simply use a global route.
Just remember to specify the namespace/area.
routes.MapRoute(
"CatchRoot", "",
new { controller = "SITEBLOG-CONTROLLER-NAME", action = "Index"}
).DataTokens.Add("area", "SITE-AREA-NAME");

Resources