How does RenderPartial figure out where to find a view? - asp.net-mvc

Ok. Googling fail probably and I remember reading about this a while back but can't find it.
I have a View and a Partial View in different directories. In a view I say #Html.RenderPartial("[partial view name]"); how does RenderPartial figure out where to look? It must be a convention but what is it?
My view is in: WebRoot\Views\Admin\ folder and partial is at WebRoot\Views\Admin\Partials
Not sure if this the right set up.
I'm using MVC 3 (Razor engine)

you can, but you have to register the routes, to tell the view engine where to look for. example in Global.asax.cs you'll have:
ViewEngines.Engines.Add(new RDDBViewEngine());
and the class is:
public class RDDBViewEngine : RazorViewEngine
{
private static string[] NewPartialViewFormats = new[] {
"~/Views/Shared/Partials/{0}.cshtml" ,
"~/Views/{0}.cshtml"
};
public RDDBViewEngine()
{
base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(NewPartialViewFormats).ToArray();
}
}
{0} is for all the subfolders with partials.

Locating views is the responsibility of the ViewEngine. The WebFormViewEngine was the one originally shipped with MVC 1, and you can see the paths it searches on codeplex. Note that it searches the same paths for views and partial views.
The CshtmlViewEngine (Razor) introduced with MVC 3 (or rather WebMatrix) searches similar locations but looks for different extensions.

Each view engine registered in your application has a list of file patterns that will be searched when you reference a view using a simple name (you can also reference it using a full path e.g. ~\Views\Admin\View.aspx)
In MVC 3 the properties of the view engine specify the patterns to search for (this applies to Razor and WebForms view engines).

Instead of subclassing the RazorView engine (as was suggested by zdrsh) you can just alter existing RazorViewEngine's PartialViewLocationFormats property. This code goes in Application_Start:
System.Web.Mvc.RazorViewEngine rve = (RazorViewEngine)ViewEngines.Engines
.Where(e=>e.GetType()==typeof(RazorViewEngine))
.FirstOrDefault();
string[] additionalPartialViewLocations = new[] {
"~/Views/[YourCustomPathHere]"
};
if(rve!=null)
{
rve.PartialViewLocationFormats = rve.PartialViewLocationFormats
.Union( additionalPartialViewLocations )
.ToArray();
}

Related

How to implement my own folder structure(architecture) in ASP.NET MVC Project?

As we know, when we create an MVC application, it creates its own typical structure which is known as convention over configuration and its a good practice .
It configure views, controller and model separately .
My concern is, can i architect(design) it like :
If I do that, My viewengine will search views inside view not inside subfolders and there are so many things like routing will get changed.. and so on..
Actually I dont want to construct my view,controller or model in a typical way, I want to put my view separately according to my domain, not according to controller like MVC does.
However in case of controller we can use any folder structure . I am specific about model,views and routing should not be affected as well.
And it is all about "Convention over My own Configuration".
Can someone please explain, how to get it done or any other alternatives.
Thanx
Anupam.
It sounds like what you are looking for is 'Areas'. This allows you to separate your controllers & views into separate 'area' folders.
More information can be found here, as including the necessary information to get this set up in this answer is probably not practical:
http://msdn.microsoft.com/en-GB/library/ee671793(v=vs.100).aspx
The location and folder structure of your controllers and models doesn't really matter, should work either way. Controllers are located by their type and classname.
The viewengine by default does search subfolders, trying to match with the naming of your controller. It searches multiple locations.
Now, if you want to change how the view engine searches for files you can configure it in global.asax. Have a look here regarding RazorViewEngine for example.
Personally I have gone away from the view engine auto locating my views and instead use relative paths for all of them because I think it makes it more readable overall.
Below is an example of a configured view engine and a relative path.
global.asax
ViewEngines.Engines.Clear();
var razorEngine = new RazorViewEngine() { FileExtensions = new string[] { "cshtml" } };
ViewEngines.Engines.Add(razorEngine);
controller action
return View("~/Views/Home/Index.cshtml", model);
Hope I understood your question correctly.
So far I have reached to conclusion.
The thing we have to consider for this are :
1.Need to override ControllerFactory : we have to search controller in specific location or assembly reference.(by default controller factory just chak the controller name )
2.Need to override ViewEngine : We have to change view search location according to our need.
3.Little modification in Route : we have to specify module name in routs for proper redirection.
route will be something like :
routes.MapRoute(
name: "Default",
url: "{module}/{controller}/{action}/{id}",
defaults: new { module = "HR", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
will implement it soon. Your suggestions are most welcome.

Find MVC template view by name

I am writing an HtmlHelper extension and I need to search for the existence of a template by name. The template in question may be a display or editor template depending on the context. My initial thought was to use ViewEngines.Engines.FindPartialView method. However, it appears that this method is not searching the ~/Views/Shared/DisplayTemplates and ~/Views/Shared/EditorTemplates directories.
I suppose this is for good reason. After all, how would the ViewEngine know whether to return the display or editor template without some additional information of context?
So, that leads to the question: how can I search for a specific EditorTemplate/DisplayTemplate I've considered adding a custom view engine to the ViewEngines collection to include these locations. I'm concerned, however, that this might be problematic.
My main concern is that the DisplayTemplate/EditorTemplate view might be served up for something unintended. Does anyone else see this as a problem?
Is it a better idea just to new up a specific DisplayTemplateViewEngine/EditorTemplateViewEngine instance when necessary and keep the ViewEngines collection clear of this specific functionality?
Is there something else I'm missing?
I absolutely love that the MVC framework is open source! I was able to determine, from the TemplateHelpers class (internal to the MVC Runtime) that the DataBoundControlMode is considered when rendering a template. The answer was simple! All I have to do is prefix the template name with the appropriate template director. So, to find a display template:
var metadata = ModelMetadata.FromLambdaExpression(expression, HtmlHelper.ViewData);
ViewEngines.Engines.FindPartialView(
_controllerContext,
string.Format("DisplayTemplates/{0}", metadata.TemplateHint))
No additional view engines or routing required! In case you're interested in the application, my helper is auto-generating UI components for a given model. I wanted to enable the existence of a custom template to bypass the automated rendering.
A WebFormViewEngine has a few properties that define (patterns for) locations to search for views.
You either follow the convention of the view engine you use, or create a custom view engine (that for examlpe extends Razor) with custom view paths.
The latter is explained here:
public class CustomViewEngine : RazorViewEngine
{
public CustomViewEngine()
{
var viewLocations = new[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/DisplayTemplates/{0}.cshtml",
"~/Views/Shared/DisplayTemplates/{1}/{0}.cshtml",
// etc
};
this.PartialViewLocationFormats = viewLocations;
this.ViewLocationFormats = viewLocations;
}
}
So I guess in your helper you should look up the current view engine and look up its view location paths and search them in order. Doesn't an Html helper have a method or property for getting the view you're currently running in?
Why you just map the relative path
string path = Server.MapPath("~/View/");
And then check if the file exits base on the .cshtml exit's in that specific directory
string fileName = "MyView.cshtml";
if (File.Exists(path + fileName))
//do somethings
else
//do another things

Problem returning specific view in asp.net mvc3

I have a view file structure like:
Views
Company
Department
Employee
ManageEmployee.cshtml
and the controller is
public class EmployeeController : Controller
{
public ActionResult Index(int dptId)
{
var loadedEmp = getEmpOf(dptId);
return View("Company/Employee/ManageEmployee", loadedEmp);
}
}
But the controller give me an error - telling that it can't find the view.These are the paths it search.
~/Views/Employee/Company/Employee/ManageEmployees.aspx
~/Views/Employee/Company/Employee/ManageEmployees.ascx
~/Views/Shared/Company/Employee/ManageEmployees.aspx
~/Views/Shared/Company/Employee/ManageEmployee.ascx
~/Views/Employee/Company/Employee/ManageEmployee.cshtml
~/Views/Employee/Company/Employee/ManageEmployee.vbhtml
~/Views/Shared/Company/Employee/ManageEmployee.cshtml
~/Views/Shared/Company/Employee/ManageEmployee.vbhtml
Basically if I'm able to eliminate the Employee section, the engine will find it.
~/Views/Employee/Company/Employee/ManageEmployee.cshtml to this
~/Views/Company/Employee/ManageEmployee.cshtml
Any insights on how to achieve this.
Thanks.
Have you tried:
return View("/Company/Employee/ManageEmployee", loadedEmp);
It looks like the engine is trying to return the view relative to your current location in the site rather than from the root of the site.
View has to be returned from the controller in the following way (for Specific View):
return View("ManageEmployee", loadedEmp);
In MVC, the controller will automatically route to the View name you provided.
loadedEmp should be the object you are passing to the view.
You need to follow MVCs convention of ControllerNameController for your controller and your view structure of ControllerName/...
If you want full control over your structure you'll need to switch to a different framework like FubuMVC.
If you want your own convention of arranging the views folder structures, it would be better you plug in your own view engine.

Why does MVC look for .aspx and .ascx for both full and partial views?

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" };
}
}

ASP.Net MVC View Architecture

Can the folders within the View have subfolders? if so, how does the Controller reach them? For example... I would like to have the following urls work correctly:
Admin/Index
Admin/Profile/Index
Admin/Profile/Edit/1
Admin/Group/Index
Admin/Group/Edit/1
So, would I create a folder for Profile and Group as a subfolder within Admin?
I tried this and to map a route in the Global file but that does not seem to be working.
Seems like you're trying to create admin panel and by putting other controller related actions into admin controller. I believe it's not the most efficient way. You could simply do like this:
Use RedirectToAction as return if needed for admin links
Admin/Index
Admin/Profile
Admin/Group
And put rest to appropriate controllers
Profile/Index
Profile/Edit/1
Group/Index
Group/Edit/1
Also add [Authorize(Roles = "Administrator")] and [AcceptVerbs(HttpVerbs.Post)] for create/edit/delete actions
Just make the return action be like this:
return View("Profile/Index");
That will make the trick for the controller find the "subview". For mapping the URLs to the right controller you can use the routing system, but ignore the subview, just map to the right controller and let the controller handle the correct view.
You could create your own ViewEngine and override where to look for the files something like this
public class MyViewEngine : WebFormViewEngine
{
public MyViewEngine()
{
ViewLocationFormats = new[] {
"~/{0}.aspx",
"~/{0}.ascx",
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx",
};
MasterLocationFormats = new[] {
"~/{0}.master",
"~/Shared/{0}.master",
"~/Views/{1}/{0}.master",
"~/Views/Shared/{0}.master",
};
PartialViewLocationFormats = ViewLocationFormats;
}
}
Then in your application start add
ViewEngines.Engines.Add(new MyViewEngine());
There's another option that is using the concept of Areas in order to achieve a global grouping of views and controllers as described in this interesting Billy McCafferty's post:
Creating MVC "Areas" as Subfolders under Views
The code is available so you can check out how everything is done, but basically replaces the ViewEngine with another, similar to the original but with different management of the view locations and registers routes according to the areas defined.

Resources