ASP.NET MVC3 Nested Resources within an Area - asp.net-mvc-3

I come from a Rails background and I'm having problems wrapping my head around Microsoft's MVC framework.
Today it's Routing. Rails gives you namespaces (e.g. Admin) which is the equivalent of Areas in .NET MVC3. Rails also allows you to define nested resources within your routes that will give you for example /posts/1/comments/1/edit and in your action you basically get params[:post_id] and params[:id].
I need something similar in ASP.NET MVC3 but not sure how to go about this. Googling for this results in at least 30 different ways to accomplish this and non of them mention areas.
It feels like I should add/modify something within here:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
But not sure where. Any suggestions?

I think you're in the right file (your AreaRegistration.cs file). I prefer being a little more explicit with my routes rather than using the default 'catch all' type of route that they provide. So here's an example of how I'd handle this:
Add something like this before the existing route (or get rid of the existing one all together) in the RegisterArea method
context.MapRoute(
"Edit_Comment",
"posts/{postId}/comments/{commentId}/edit",
new { controller = "Comment", action = "Edit" }
);
Then in your CommentController.cs you would have the following action:
public ActionResult Edit(int postId, int commentId)
{
// Do your edit logic then return an ActionResult
}

Related

ASP.NET MVC 3 won't recognise .cshtml view files

I have ported an mvc 3 app from vs 2010 to vs2012.
The ported app is using .NET 4.
All the old bits work, but with a new view, created in vs 2012, the view engine is not looking for .cshtml files for the view.
For example, when the user requests the index action on the Welcome controller in the Solicitors area, the url is:
mysite.com/solicitors/welcome/gg
(where gg is the user name). In that case, the error that comes back is:
The view 'Index' or its master was not found or no view engine
supports the searched locations. The following locations were
searched: ~/Areas/Solicitors/Views/Welcome/Index.aspx
~/Areas/Solicitors/Views/Welcome/Index.ascx
~/Areas/Solicitors/Views/Shared/Index.aspx
~/Areas/Solicitors/Views/Shared/Index.ascx ~/Views/Welcome/Index.aspx
~/Views/Welcome/Index.ascx ~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx ~/Areas/Solicitors/Views/Welcome/gg.master
~/Areas/Solicitors/Views/Shared/gg.master ~/Views/Welcome/gg.master
~/Views/Shared/gg.master ~/Areas/Solicitors/Views/Welcome/gg.cshtml
~/Areas/Solicitors/Views/Welcome/gg.vbhtml
~/Areas/Solicitors/Views/Shared/gg.cshtml
~/Areas/Solicitors/Views/Shared/gg.vbhtml ~/Views/Welcome/gg.cshtml
~/Views/Welcome/gg.vbhtml ~/Views/Shared/gg.cshtml
~/Views/Shared/gg.vbhtml
I have already added the following key to appsettings in web.config, but it makes no difference.
<add key="webpages:Version" value="1.0" />
EDIT:
Route in SolictorAreaRegistration.cs:
context.MapRoute(
"Solicitors_Welcome",
"Solicitors/Welcome/{nameUser}",
new { controller = "Welcome", action = "Index", nameUser = UrlParameter.Optional }
);
EDIT 2:
Using RouteDebug, I can see that the correct controller and action are found.
Route Data
Key Value
nameUser: gg
controller: Welcome
action: Index
Data Tokens
Key Value
Namespaces: System.String[]
area: Solicitors
UseNamespaceFallback: False
EDIT 3:
The route is found correctly, as I can see from debugging: the Index action is hit.
The problem happens when the line call the view is called:
namespace MyApp.Areas.Solicitors.Controllers
{
[Authorize]
public partial class WelcomeController : Controller
{
//
// GET: /Solicitors/Welcome/
public virtual ActionResult Index(string nameUser)
{
return View("Index", nameUser);
}
}
}
OK, got to the bottom of it:
The Problem:
The problem is that the model of my view is of type string. In my action, I was passing in a string as the model parameter:
public virtual ActionResult Index(string nameUser)
{
return View("Index", nameUser);
}
This will clash with one of the overloads of Controller.View(...):
View(string, string)
The second parameter expects the name of a layout file. When you do this, MVC goes off looking for a layout file with a name of the value of your string, which could be, for example:
"Hello, World. I'm an idiot, but if you give me a decent error message, I might be able to fix the bug."
Obviously, a layout file with that name doesn't exist. Nor does a layout file called "gg" either (my (test) solicitor's username).
The Solution:
The solution is simple:
Specify that the second parameter is the model, not the layout.
public virtual ActionResult Index(string nameUser)
{
return View("Index", model: nameUser);
}
Useful Article:
To view an extended discussion of this very issue, see the following article:
MVC Gotcha: Beware when using your view's model is a string
Many thanks to heartysoft.com for the enlightenment.
It is looking as you can see from the error message:
~/Areas/Solicitors/Views/Welcome/gg.cshtml
If you need to look for the Index view then you need to specify it:
http://mysite.com/solicitors/welcome/index/gg

Dynamic Routing in ASP.NET MVC 3

I am building a blog engine using MVC 3 and razor. In this scenario, I have given options like a user can have multiple blogs (similar to blogger.com)
Now say a user 'yasser' has the following 3 blogs
TechStory
GameGeek
MeMyStory
so I want all other users to access these blogs by the following urls
www.domainName.com/blogs/TechStory
www.domainName.com/blogs/GameGeek
www.domainName.com/blogs/MeMyStory
And more blogs can be added hence more such url will be acessed in future.
I know that something needs to be done with Routing, but being new to MVC dont seems to get it. Please can some one guide me on this.
Add this route on top of your Default one:
routes.MapRoute(
"Blog",
"Blogs/{blogName}",
new { controller = "Blogs", action = "Index" }
);
Your controller will look like this:
public class BlogsController : Controller
{
public ActionResult Index(string blogName)
{
BlogModel model = // find blog by blog name
return View(model);
}
}
Also, one suggestion: Keep your controller names in singular mode: BlogController instead of BlogsController. Change URL and Routing accordingly if you decide to do so.

ASP.NET MVC 3 - Setting up routes

I'm migrating an ASP.NET web forms application to ASP.NET MVC 3. I kind of understand routing, but I sort of don't. In my application, I have created three .cshtml files in the directory located at /internal/products/find/. For the sake of demonstration, those .cshtml files are named "view1.cshtml", "view2.cshtml", and "view3.cshtml".
I have a controller named "InternalController". My goal is to use InternalController for all of the locations inside the /internal path. I'm not sure if what I'm trying to do is allowed. I assume it is. Either way, at this time, I have the following in InternalController:
public ActionResult View1()
{
return View();
}
public ActionResult View2()
{
return View();
}
public ActionResult View3()
{
return View();
}
In my global.asax.cs file, I'm trying to register the routes to these views as follows:
routes.MapRoute(
"View1",
"{controller}/products/find/view1",
new { controller = "Internal", action = "View1" }
);
routes.MapRoute(
"View2",
"{controller}/products/find/view2",
new { controller = "Internal", action = "View2" }
);
routes.MapRoute(
"View3",
"{controller}/products/find/view3",
new { controller = "Internal", action = "View3" }
);
Whenever I try to visit /internal/products/find/view1 in my browser, I see the ASP.NET error screen and it says:
The view 'View1' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/internal/View1.aspx
~/Views/internal/View1.ascx
~/Views/Shared/View1.aspx
~/Views/Shared/View1.ascx
~/Views/dashboard/View1.cshtml
~/Views/dashboard/View1.vbhtml
~/Views/Shared/View1.cshtml
~/Views/Shared/View1.vbhtml
What am I doing wrong? The path /internal/products/find/view1 is the most important part for me. Ideally, I would like to expose that in InternalController everytime. But I'm having a rough go at it. What am I doing wrong?
Thanks!
When you write
routes.MapRoute(
"View1",
"{controller}/products/find/{action}",
new { controller = "Internal", action = "View1" }
);
it means that whenever user writes into his browser:
http://mysite.com/blahblah/products/find/blahblahview
it will activate action view1 inside controller blahblahview. But it doesn't mean that view1.cshtml file is at that path. Actually, asp.net mvc looks for views at directories defined by convention...and convetion is:
~/Views/ControllerName/ViewName
so, your view should be in a folder:
~/Views/Internal/View1.cshtml
Unlike ASP.NET WebForms you are used to, ASP.NET MVC is pretty much driven by naming conventions as you could probably see (you always name your controllers like BlahBlah*Controller*, you always place your views inside Views folder etc... Read some tutorials here and catch up with basics.

Conditional ASP.NET MVC 3 Routing (With Areas)

Hurro.
I'm trying to achieve some conditional routing based on whether the current user is an admin or not. The system only has two modes, admin or non-admin and nothing more than this. I'm using areas for my admin area because the controller names would be the same, but they'll deliver different functionality pretty much in every case.
In this system, however, the admins shouldn't really be aware of their admin location, they just know that they use the system to do something else other than what regular users do. I don't want there to be any distinction between the two in terms of URL because of this. What I want to do is be able to do something like mysite.com/AuditHistory and dependant on whether you're an admin or user will depend on what controller is used. So if it's a user making this request, then it'd use the AuditHistoryController in the regular controllers folder, but if it's an admin then it'd use the AuditHistoryController in Areas/Admin/Controllers.
I've seen the use of IRouteConstraint and can do something along the following lines:
public class AdminRouteConstraint : IRouteConstraint
{
public AdminRouteConstraint() { }
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.User.IsInRole("Admin");
}
}
With the following:
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", controller = "Home", id = UrlParameter.Optional },
new { controller = new AdminRouteConstraint() }
);
Can I simply get rid of "Admin/" at the front and do the same thing for the other routes but say UserRouteConstraint? I've not seen this done anywhere though and not sure if it's correct.
Any ideas on how to do this?
Could you simply redirect the user from the ActionResult if they are in a role? That is if you don't mind the URL changing?
Something like this...
[Authorize]
public ActionResult AuditHistory()
{
if(Context.User.IsInRole("Admin")
{
return Redirect("Admin/AuditHistory");
}
else
{
return View();
}
}
To me, this is a bit of a hack. But it may be a solution.
Obviously, you would need to do basic checks like making sure the current request is authenticated etc.
If you really don't want the URL to change, you could possibly have two separate views and do away with the admin Area
[Authorize]
public ActionResult AuditHistory()
{
if(Context.User.IsInRole("Admin")
{
return View("AdminAuditHistory", new AdminAuditHistoryViewModel());
}
else
{
return View("AuditHistory", new AuditHistoryViewModel());
}
}
In fact I think this is probably the cleanest solution, but is possibly still a bit of a hack.
I hope this helps.

Nested areas in MVC 2 / MVC 3 / MVC 4

Since MVC 2 we can create areas easily. Now my question is related to nested areas (areas inside of areas).
Select my "father" area folder, Right-mouse-click > Add > NO option for a new Area.
Is it possible to do it in some other way ? or will this option be available in the near future?
I realise this is an old question but I'll answer it in case anyone else is trying to figure it out. A solution to this is to create areas that use a different routing value at a lower level than area, so for example your RouteConfig would look something like this:
public class RouteConfig
{
/// <summary>
/// A function that registers the default navigation route.
/// </summary>
/// <param name="routes">The RouteCollection to act on.</param>
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
var route = routes.MapRoute(
name: "Default",
url: "{area}/{subArea}/{controller}/{action}/{id}",
defaults: new { area = "DefaultArea", controller = "Home", action = "Splash", id = UrlParameter.Optional, section = "Customer" },
namespaces: new string[] { "Application.Controllers" });
}
}
And one of your sub-area registrations might look like this:
public class ApplicationSubAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "ApplicationSubArea";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"SubArea_default",
"Area/SubArea/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new string[] { "Application.Areas.AreaName.SubAreaName.Controllers" }
);
}
}
After reading that, does "area" still look like a word? Because it doesn't to me.
P.S. You can do this recursively as many times as you like (theoretically) such that for example you could do
url: "{area}/{subArea}/{subSubArea}/{subSubSubArea}/{evenMoreSubArea}/{controller}/{action}/{id}",
in your RouteConfig.cs and
"Area/SubArea/SubSubArea/SubSubSubArea/EvenMoreSubArea/{controller}/{action}/{id}",
in your area registration.
For now there isn't any information telling if there will be nested areas.
In the future maybe this will change.
Using the idea of Multi-project areas as a start, I guess you could recursively create more nested areas.
Maybe something like this could help. It's more like a study which is in mvc-contrib.
I saw it for version 1 don't know if it's compatible for MVC2
It's the concept of sub-controllers: http://mhinze.com/subcontrollers-in-aspnet-mvc/
At this time MVC supports only Main Application and then Areas in next level and NOT nested Areas, but you can look at This Nuget Package that adds the following functionality to your project:
Organize your controllers and views using namespaces (no more areas) that can go as deep as you want.
Default constraints for primivite types that can be overridden on a per-parameter or per-site basis.
Intelligent grouping of similar routes for efficient matching.
Support for a root controller.
Support for overloaded actions.
Support for hierarchical (a.k.a. RESTful) routes.
Support for user-defined custom routes.
Detection of ambiguous routes.
Formatting of routes (e.g. to lowercase, hyphen-separated, underscore-separated, etc).
Render your routes as calls to the MapRoute extension method, for debugging.
Support for embedded views (as assembly resources).
You do not want to have nested aereas.
There is something wrong in your Software design.
the most common case is, that you use areas as Html Renderer,
therefore are the Display Templates.

Resources