ASP MVC Rounting, Remove /Areas/ Prefix from uri - asp.net-mvc

By default Areas have a /Area/ routing prefix. For instance a Blog area would be :
/Areas/Blog/Blog/Show/myId
or
/Areas/{area}/{controller}/{action}/{id}
The result is a really ugly and redundent uri for the web application. What I would like is something along the lines of :
/Blog/Show/myId
or
/{area / controller}/{action}/{id}
How can I achieve this ?
The reason I am using areas is because my 'blog' area is not a standard MVC application, but, a single-page application. It has a different project structure than the rest of the app, and so, I would like to just partition it into its own area.
Edit : source for Area Registration
public class BlogAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Blog";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Blog_default",
"Blog/{action}/{id}",
new { controller ="blog", action = "Index", id = UrlParameter.Optional }
);
}
}
Edit Source for Routeing
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Edit
After speaking /u/Eric Philips I have come up with a solution. The solution was rework my file structure to MVC compatible. Once I setup a controller and moved my index.cshtml page into a valid location, /Blog/ worked fine.
public class BlogController : Controller
{
// GET: Blog/Blog
public ActionResult Index()
{
return View();
}
}

Related

I added an area called home, moved my controller and view to it and now I can't connect to the index view

I already have controllers and views in folders under the project name. I added an Area folder and then an area inside it and called it Home and then moved my controller and index view into it. But when I connect to the index I get an error and it looks like the path where it's looking for the index is the old path, how do I change this to the new path?
Here is what I created
In 'HomeAreaRegistration' I see this under RegstrationArea
public class HomeAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Home";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Home_default",
"Home/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
But when I run the application in IE, here is what I see in the browser! It looks like it's looking for the index.cshtml in the old path location, not the new path location in the new area 'Home'
It looks like the route engine is looking in the wrong location. So here is what my RouteConfig.cs file looks like.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Even when I try 'https://localhost:44301/Home/Index.cshtml' it throws a HTTP 404 error.
The 404 error shows the main problem itself: default routing and view engine search cannot found default Index.cshtml view file in Views directory on your project (i.e. ProjectName/Views/Index.cshtml pointed by route ~/Views/Home/Index).
First, create a class to include view location search for your custom area like this example:
public class CustomView : RazorViewEngine
{
public CustomView()
{
MasterLocationFormats: new[]
{
"~/Areas/Home/Views/{0}.cshtml",
"~/Areas/Home/Views/{1}/{0}.cshtml"
}
ViewLocationFormats: new[]
{
"~/Areas/Home/Views/{0}.cshtml",
"~/Areas/Home/Views/{1}/{0}.cshtml"
}
PartialViewLocationFormats = ViewLocationFormats;
FileExtensions = new[]
{
"cshtml"
};
}
}
Then, include all areas and your custom view engine into Global.asax:
protected void Application_Start()
{
// register all area locations
AreaRegistration.RegisterAllAreas();
// clear default view engine
ViewEngines.Engines.Clear();
// add your custom view engine here
// the custom view engine should loaded before default view engine (e.g. Razor)
ViewEngines.Engines.Add(new CustomView());
ViewEngines.Engines.Add(new RazorViewEngine());
}
If you have RouteConfig class on App_Start directory, make sure RegisterAllAreas has included before default route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Additionally, add namespace of the controller name when required or the above solution still doesn't work:
public class HomeAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Home";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Home_default",
"Home/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "ProjectName.Areas.Home.Controllers" }
);
}
}
NB: Create Home directory under Views if you want to follow route convension ~/Areas/Views/Home/Index, and put Index.cshtml file into it.
References:
How to set a Default Route (To an Area) in MVC
How to register areas for routing
your area folder structure look like this
Reister your area in Global.asax
AreaRegistration.RegisterAllAreas();
and try with this url
http://localhost:44301/Home/Home/Index
you have to do below corrections in your solution:
1) Add a Folder Home in Views and place index.cshtml in it.
Folder structure for view must be: Home(Area name) > Views > Home (same name as controller) > index.cshtml (as shown in picture)
2)change namespace of your Homecontroller to (Solution name).Areas.Home.Controllers
3)Also you have to refer following route pattern for area:
localhost/AreaName/Controller/Action
which in your case:
https://localhost:44301/Home/Home/Index
Hope this might solve your problem

MVC 4.0 Routing "Area" Prefix

I Guess my question is very stuppied, but i could not find any awenser or link that worked fine, so.
The point is, I want my url to reflect my folder strcuture. Witch is:
~/Controllers/Admin/
~/Controllers/Supply/
My routeconfig look like this:
routes.MapRoute(
name: "Admin",
url: "Admin/{controller}/{action}",
namespaces: new [] {"Web_Intranet.Controllers.Admin"}
);
routes.MapRoute(
name: "Supply",
url: "Supply/{controller}/{action}",
namespaces: new [] {"Web_Intranet.Controllers.Supply"}
);
The problem is that if you type on the brower url the following:
localhost:4342/admin/users/showall
OR
localhost:4342/supply/users/showall
Both will work! I assume that the routing checks wheter you pass a valid "controller" and "action" and chooses the first one that matches the condition.
How can I create a valid routing config that will not work if the corresponding prefix doesn´t match?
Here's the configuration you should add for each Area;
~/Areas/Admin/AdminAreaRegistration.cs
using System.Web.Mvc;
namespace MyApp.Areas.Admin
{
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
Make sure that Route_Name is unique

ASP.NET MVC routing parameters not working with areas

I'm currently playing around with areas and routing within them. What I'm trying to achieve is to be able to have a URL that looks like this;
PracticeAdmin/Practice/[Practice Name]
which I would then be able to add things like Edit and Delete to the end of.
I have achieved this in the past when not working with areas by adding this annotation to the action
[Route("PracticeAdmin/Practices/{practiceName}")]
public ActionResult Details(string practiceName)
this would produce the URLs that I would like. The problem I am having is that when I am trying to do this when using areas I get links that look like this;
PracticeAdmin/Practices?practiceName=Practice1
which is not what I am looking for.
The code that I am using to try and produce this with is
PracticeAdminAreaRegistration.cs
using System.Web.Mvc;
namespace TrainingPortal.Areas.PracticeAdmin
{
public class PracticeAdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "PracticeAdmin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"PracticeAdmin_default",
"PracticeAdmin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "TrainingPortal.Areas.PracticeAdmin.Controllers" }
);
}
}
}
RouteConfig.cs
namespace TrainingPortal
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "TrainingPortal.Controllers" }
);
}
}
}
I have called MapMvcAttributeRoutes here which I believe should mean that the routes are registered even within areas. I have also tried putting the necessary code within PracticeAdminAreaRegistration to do the same thing with no effect.
PracticeAdminController.cs
namespace TrainingPortal.Areas.PracticeAdmin.Controllers
{
public partial class PracticesController : Controller
{
private TpContext db = new TpContext();
// GET: PracticeAdmin/Practices
public virtual ActionResult Index()
{
return View(db.Practices.ToList());
}
[Route("PracticeAdmin/Practice/{practiceName}")]
public virtual ActionResult Details(string practiceName)
{
if (string.IsNullOrWhiteSpace(practiceName))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Practice practice = db.Practices.FirstOrDefault(m => m.PracticeName.ToLower() == practiceName);
if (practice == null)
{
return HttpNotFound();
}
return View(practice);
}
...
Obviously it carries on with other methods but they all follow the same approach as this one.
Index.cshtml snippet
#Html.ActionLink("Delete", MVC.PracticeAdmin.Practices.Delete(item.PracticeName))
#Html.ActionLink("Delete2", "Delete", new { practiceName = item.PracticeName })
Within PracticeAdminArea/Views/Practices/Index.cshtml I have tried using both T4MVC and the normal ActionLink approach which generate exactly the same link (unsurprisingly).
Summary
I have no idea why the Routes I have specified don't appear when trying to create an ActionLink in an area, so I was wondering whether anyone is able to point me in the direction of how I would be able to fix this and get the URL to look how I would like it to?
After a bit of playing around I managed to get it working. The way that I ended up fixing it was by calling AreaRegistration.RegisterAllAreas() from the RouteConfig.RegisterRoutes() after having called MapMvcAttributeRoutes()
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "TrainingPortal.Controllers" }
);
}
}
As you're not allowed to call this method twice (or ASP.NET gets rather upset with you having registered the same route names twice) I removed the call to AreaRegistration.RegisterAllAreas() from Global.asax leaving it looking like this;
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
Unfortunately this alone didn't solve the problem, I also had to make a couple modifications to the Controller as well. The modifications were to add the RoutePrefix and RouteArea attributes to the Controller like this;
[RouteArea("PracticeAdmin")]
[RoutePrefix("Practice")]
public partial class PracticesController : Controller
{
This had the added benefit that when specifying the route for a particular action through the Route attribute you didn't have to specify those parts any more, so originally an action's signature would have looked like this;
// GET: PracticeAdmin/Practices/{practiceName}/Members
[Route("PracticeAdmin/Practices/{practiceName}/Members")]
public virtual ActionResult Members(string practiceName)
{
it would now look like this;
// GET: PracticeAdmin/Practices/{practiceName}/Members
[Route("{practiceName}/Members")]
public virtual ActionResult Members(string practiceName)
{
After making all those changes, the website is behaving as expected.

How do I use routes for multi-tenancy in all but one controller?

Our app has multiple tenants. Every tenant has a short code assigned to them that users know them by. I want to use that code in my URLs as a route parameter, and have Ninject inject a DbContext with the tenant's database connection string into the tenant-specific controllers.
So for examine I have a CarController, and every tenant has their own products. The URLs would look like {tenantcode}/{controller}/{action}. I understand how to do this part.
However, I have several controllers that should NOT be instanced by tenant. Specifically, the home controller, and account controller for login/registration. These don't matter.
So example URLs I need:
myapp.com/ - HomeController
myapp.com/Account/Login - AccountController
myapp.com/GM/Car/Add - CarController that has GM's DbContext injected
myapp.com/Ford/Car/Add - CarController that has Ford's DbContext injected
How can I exclude certain controllers from routes? Running ASP.NET MVC 5.
Many thanks to Darko Z for starting me in the right direction. I ended up using a hybrid of traditional routes, and the new attribute based routing in MVC 5.
First, the "excluded" routes got decorated with the new RouteAttribute class
public class HomeController : Controller
{
private readonly TenantContext context;
public HomeController(TenantContext Context)
{
this.context = Context;
}
//
// GET: http://myapp.com/
// By decorating just this action with an empty RouteAttribute, we make it the "start page"
[Route]
public ActionResult Index(bool Error = false)
{
// Look up and make a nice list of the tenants this user can access
var tenantQuery =
from u in context.Users
where u.UserId == userId
from t in u.Tenants
select new
{
t.Id,
t.Name,
};
return View(tenantQuery);
}
}
// By decorating this whole controller with RouteAttribute, all /Account URLs wind up here
[Route("Account/{action}")]
public class AccountController : Controller
{
//
// GET: /Account/LogOn
public ActionResult LogOn()
{
return View();
}
//
// POST: /Account/LogOn
[HttpPost]
public ActionResult LogOn(LogOnViewModel model, string ReturnUrl)
{
// Log on logic here
}
}
Next, I register the tenant generic route that Darko Z suggested. It's important to call MapMvcAttributeRoutes() before making other routes. This is because my attribute based routes are the "exceptions", and like he said, those exceptions have to be at the top to make sure they are picked up first.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// exceptions are the attribute-based routes
routes.MapMvcAttributeRoutes();
// tenant code is the default route
routes.MapRoute(
name: "Tenant",
url: "{tenantcode}/{controller}/{action}/{id}",
defaults: new { controller = "TenantHome", action = "Index", id = UrlParameter.Optional }
);
}
}
So as I'm sure you know you specify routes in MVC in the order from most specific to most generic. So in your case I would do something like this:
//exclusions - basically hardcoded, pacing this at the top will
//ensure that these will be picked up first. Of course this means
//you must make sure that tenant codes cannot be the same as any
//controller name here
routes.MapRoute(
"Home",
"Home/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
routes.MapRoute(
"Account",
"Account/{action}/{id}",
new { controller = "Account", action = "Index", id = "" }
);
//tenant generic route
routes.MapRoute(
"Default",
"{tenantcode}/{controller}/{action}",
new { tenantcode = "Default", controller = "Tenant", action = "Index" }
);
//default route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
This is obviously only good if there are less excluded controllers than controllers that need the tenant code. If not then you can take the opposite approach and reverse the above. Main takeaway here is that (happy to be proven wrong) there is no way to have a generic ignore within an AddRoute call. While there is an IgnoreRoute, that just completely doesn't apply any routing rules and is used for static resources. Hope that helps.

Adding area to mvc project

I have an mvc project and I added a new area with the name BEK
and BEKAreaRegistration.cs was created.
public class BEKAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "BEK";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"BEK_default",
"BEK/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
and my global.asax file is as follows:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
RouteTable.Routes.IgnoreRoute("{*allAspx}", new { allAspx = #".*\.aspx(/.*)?" });
RouteTable.Routes.IgnoreRoute("{*allAsmx}", new { allAsmx = #".*\.asmx(/.*)?" });
RouteTable.Routes.IgnoreRoute("{*allAshx}", new { allAshx = #".*\.ashx(/.*)?" });
RouteTable.Routes.IgnoreRoute("Services/{*pathInfo}");
RouteTable.Routes.IgnoreRoute("");
RouteTable.Routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
but when i try to go to BEK/Home/Index page I get an error page. What else should I do?
This is the error:
The resource cannot be found. Description: HTTP 404. The resource
you are looking for (or one of its dependencies) could have been
removed, had its name changed, or is temporarily unavailable. Please
review the following URL and make sure that it is spelled correctly.
Requested URL: /LMS_WEB_APP/BEK/Home
-------------------------------------------------------------------------------- Version Information: Microsoft .NET Framework Version:4.0.30319;
ASP.NET Version:4.0.30319.18213
When you add BEK Area.
MVC will create These for you.
Mvc will not create any Controller and Actions and Views.
So, you do have to do that manually that which controllers, actions and views you want to add.
So, now you do have to Add the Controller by Right Clicking on Controller and Add Controller.
After Adding the Controller.
You can do right click on the Action and Add View like this to add the View :
Ok....
So, you do have Both Controller and Action and Views required.
Now you might have to Resolve the Controller Duplicacy, if any, which i had told you earlier.
Happy Coading...
I think problem is that, You have Same Name controller in Both Area and and application. Like You have Home Controller in Normal Application and Also in AREA
And, it is causing the Duplicate Declaration of Same Controller.
The way doing that is, specifying the NAMESPACE of the Controller Like the Following :
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"BEK_default",
"BEK/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new string[] { "MyAppName.Areas.BEK.Controllers" } // specify the new namespace
);
}
If not that Case, Please post the Error Message you are getting.

Resources