ASP.Net MVC View Architecture - asp.net-mvc

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.

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.

.net MVC Areas and subfolders

I am using Areas in my project but I would like to organise my views within these areas into sub folders.
For example, I have an area called Members which contains a Controller called Settings and another Controller called MemberCategory. MemberCategory is in effect a subfolder of Settings.
I would like when I access the Member Category view for my url to resolve to Members/Settings/MemberCategory
At present it resolves to Members/MemberCategory
Is it possible to nest the views into subfolders and change the controller to point to
return View("Members/Settings/MemberCategory");
Or do this need to done with routing?
Any examples would be appreciated.
I have resolved this problem with Routes and not nesting the views into subfolders.
In my Area Registration file I have added the following above the default route:
context.MapRoute(
"MemberCategory",
"Members/Settings/MemberCategory",
new { controller = "MemberCategory", action = "Index" }
);
context.MapRoute(
"MemberCategoryAction",
"Members/Settings/MemberCategory/{action}/{id}",
new { controller = "MemberCategory", action = "Index", id = UrlParameter.Optional }
);
Not sure if this is the most elegant way of doing this but it works in my case.
You can also do something like:
return this.View("../MailTemplates/ResetPassword");
To get to the view you want to use. The code editor will not be able to resolve this, but it works.
I think this is cleaner than:
return this.View("~/Areas/Cms/MailTemplates/ResetPassword");

How does RenderPartial figure out where to find a view?

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();
}

How would you use ASP.NET MVC to create pages in a CMS?

In a content management system you can usually create pages on the fly eg
www.website.com.au/home.aspx
www.website.com.au/projects.aspx
www.website.com.au/contact-us.aspx
In a previous CMS that we wrote we physically created these files on disk when the user selected to create a new page in his site. We copied the new file from a base template page, renamed the file and renamed the class in the code behind eg
template_page.aspx and template_page.aspx.cs turned into
projects.aspx and projects.aspx.cs
This was all done via our CMS application. No files needed to be manually created by a user.
How would this approach work using MVC?
Eg www.website.com.au/home/
www.website.com.au/projects/
www.website.com.au/contact-us/
Presumably we would need to dynamically create controllers and views on the fly?
This seems even messier than the old approach but I suppose its feasible.
Can anyone think of a smarter way to do it?
You should be able to use one controller and a couple views (display, create, edit) with some routing work. I did a super simple implementation for a personal project that went like this. I put this route near the top of my routing list and used the constraint to determine if it should be considered as a static page from my rules. My implementation didn't have any sort of hierarchy, i.e. pages/About-us/contact - only /contact.
route:
routes.MapRoute("StaticContent", "{title}",
new { controller = "Page", action = "Details"},
new { title = new InvalidTitleContstraint()});
controller:
public class PageController : Controller
{
// Details checks if it can find a matching title in the DB
// redirects to Create if no match
public ActionResult Details(string title)
// GET
public ActionResult Create()
// POST
public ActionResult Create(Page page)
}

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

Resources