Make advanced MVC Route - asp.net-mvc

I’ve a website that contains two main dynamic modules:
1- Module (1): To able the website admin from managing the whole website pages’ static content, he is able from adding a parent pages and child up to 2 levels
2- Module (2): To able the website admin from managing the news’ articles that should be published to the website
For the routing, we’re planning to make it as following:
1- Module (1)
www.websitename.com/parentpage
www.websitename.com/parentpage/childpage
www.websitename.com/parentpage/childpage/childchildpage
2- Module (2)
www.websitename.com/news/newstitle
The problem here is module(2) routing not handled or executed never. I think because its conflicts here with the second scenario in module(1).
Any suggestions or recommendations?
Best Regards,
shady

Thank you for your help. I have solved this problem using RouteBase
public class MyUrlRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
//Route Code
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Virtual Path Code
}
}
In RouteConfig
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new MyUrlRoute()); // Add before your default Routes
defaults: new { language = "en", controller = "Home", action = "Home" },
namespaces: new string[] { "Solution.Website.Controllers" }
);
}

Related

ASP.NET MVC - Attribute Routing not finding area view

I've been searching for answers for this everywhere, but I can't seem to find any. I basically have an MVC application setup and I am using the built in AttributeRouting for my routes.
The folder structure looks like this;
Models
Views
Controllers
Areas
Member
MemberAreaRegistration.cs
Controllers
HomeController.cs
Views
Home
Account.cshtml
And then I wire up my routes in the global.asax like this;
public class Application : System.Web.HttpApplication {
protected void Application_Start(){
AreaRegistration.RegisterAllAreas();
// other web optimization stuff
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
So then, MemberAreaRegistration.cs is simple.
namespace App.Web.Areas.Member {
public class MemberAreaRegistration: AreaRegistration {
public override string AreaName { get { return "Member"; } }
}
public override void RegisterArea( AreaRegistrationContext context){ }
}
And I try to wire it using the attributes...
/areas/member/controllers/homecontroller.cs
// ...
[Route("member/account")]
public ActionResult Account() { return View(); }
// ...
The problem is that this finds the route, but it cannot find the view. I get the following error;
The view 'Account' or its master was not found or no view engine
supports the searched locations. The following locations were
searched:
~/Views/Home/Account.aspx
~/Views/Home/Account.ascx
~/Views/Shared/Account.aspx
~/Views/Shared/Account.ascx
~/Views/Home/Account.cshtml
~/Views/Home/Account.vbhtml
~/Views/Shared/Account.cshtml
~/Views/Shared/Account.vbhtml
By all accounts, this should work fine - and if not, I expect the ~/area to at least be in the path it is trying to search. Do I have to wire something additional up to make this function?
I am using ASP.NET MVC 5.0
If I hardcode the absolute path of the view, it works. Obviously this is not a good situation though. I'd prefer it to find the view out of convention. But if I type return View("~/areas/member/views/home/account.cshtml"); I do get the view back - so I know it can access to file and that it is correct.
Here is my RouteConfig.cs per request
RouteConfig.cs
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
// mvc attribute routing allows us to supersede normal routing mechanisms and
// declare our routes a bit more verbosely
routes.MapMvcAttributeRoutes();
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "App.Web.Controllers" }
);
}
}
That's because, once you are defining your route as an action's attribute, ASP.NET MVC doesn't know which area it is in, hence it doesn't know where to look for Views.
In the Controller the Account action is in, try to explicitly specify a RouteArea attribute.
I'm writing this off the top of my head, but it should look like:
[RouteArea("Member")]
[RoutePrefix("member")]
public class HomeController: Controller {
[Route("account")]
public ActionResult Account() { return View(); }
}
or, alternatively:
[RouteArea("Member")]
public class HomeController: Controller {
[Route("member/account")]
public ActionResult Account() { return View(); }
}

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.

How do I run my Orchard Module?

I have an almost standalone Orchard module. It is an app comprising a collection of controllers and views, and in performs it's own EF based data access.
Once I have installed and activated this module in Orchard, how do I request it's controller actions and be served it's views in response? I understand it must have a Routes class and suspect it is something here that I must do to enable access to my module. I know a module is actually an Area, but I still battle to understand the Routes class.
Let's say my module has one controller, action and view, Home, Index, and Index, being Home/Index. Once Orchard is up and running, how do I access this specific Index actin?
There is a good guide in the Orchard Documentation to Creating an Orchard Module that uses its own controllers.
Here is a code sample taken from that guide that creates the routes for a module called HelloWorld:
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.Mvc.Routes;
namespace HelloWorld {
public class Routes : IRouteProvider {
public void GetRoutes(ICollection<RouteDescriptor> routes) {
foreach (var routeDescriptor in GetRoutes())
routes.Add(routeDescriptor);
}
public IEnumerable<RouteDescriptor> GetRoutes() {
return new[] {
new RouteDescriptor {
Priority = 5,
Route = new Route(
"HelloWorld",
new RouteValueDictionary {
{"area", "HelloWorld"},
{"controller", "Home"},
{"action", "Index"}
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "HelloWorld"}
},
new MvcRouteHandler())
}
};
}
}
}
If you create a class that implements IRouteProvider you can let Orchard know the routes for your controllers.

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

ASP.NET MVC 2 Preview 2 Route Request Not Working

Here's the error:
The incoming request does not match any route.
Basically I upgraded from Preview 1 to Preview 2 and got rid of a load of redundant stuff in relation to areas (as described by Phil Haack). It didn't work so I created a brand new project to check out how its dealt with in Preview 2. The file Default.aspx no longer exists which contains the following:
public void Page_Load(object sender, System.EventArgs e)
{
// Change the current path so that the Routing handler can correctly interpret
// the request, then restore the original path so that the OutputCache module
// can correctly process the response (if caching is enabled).
string originalPath = Request.Path;
HttpContext.Current.RewritePath(Request.ApplicationPath, false);
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(HttpContext.Current);
HttpContext.Current.RewritePath(originalPath, false);
}
The error I received points to the line httpHandler.ProcessRequest(HttpContext.Current); yet in newer projects none of this even exists. To test it, I quickly deleted Default.aspx but then absolutely nothing worked, I didn't even receive any errors. Here's some code extracts:
Global.asax.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace Intranet
{
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
}
protected void App_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
}
Notice the area registration as that's what I'm using.
Routes.cs
using System.Web.Mvc;
namespace Intranet.Areas.Accounts
{
public class Routes : AreaRegistration
{
public override string AreaName
{
get { return "Accounts"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute("Accounts_Default", "Accounts/{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" });
}
}
}
Check the latest docs for more info on this part. It's to register the area. The Routes.cs files are located in the root folder of each area.
Cheers
As per comment "Sorry, I was working by the example, you're meant to use Application_Start not App_Start. I have no idea why"

Resources