How to catch all areas with MapRoute - c#

I am new to MVC and editing an existing application. Currently I see the following in RouteConfig.cs:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Util",
"util/{action}",
new {controller = "util"});
routes.MapRoute(
"Catchall",
"{*url}",
new {controller = "Main", action = "CatchUrl"});
}
}
Inside the Main controller there is logic on that basically does a RedirectToRoute and sets the area, controller, action, and querystring called location to a certain value.
public class MainController : Controller
{
public ActionResult CatchUrl()
{
var agencyId = 9;
var routeValues = new RouteValueDictionary
{
{"area", "area1"},
{"controller", "dashboard"},
{"action", "index"},
{"location", "testLocation"}
};
return RedirectToRoute(routeValues );
}
}
This seems to work fine, when you give it an invalid area it correctly goes to the default one.
I also see a file called CustomAreaRegistration.cs:
public abstract class CustomAreaRegistration : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
AreaName + "Ajax",
AreaName + "/{controller}/{action}",
new { action = "Index" }
);
context.MapRoute(
AreaName + "ShortUrl",
AreaName + "/{controller}",
new {action = "Index"}
);
}
}
I am having trouble understanding how the Area routing works and how it knows how to go to the correct controller.
Furthermore, I am trying to get it so that when you visit
/{area}/ it does some logic and redircts you to the correct controller. Similar to how CatchUrl works
My attempt:
routes.MapRoute(
"AreaIndex",
"{module}/",
new {controller = "Main", action = "Index"});
MainController :
public class MainController : Controller
{
public ActionResult Index()
{
var requestHost = HttpContext.Request.Url?.Host;
var location= requestHost == "localhost" ? Request.QueryString["location"] : requestHost?.Split('.')[0];
var routeValues = new RouteValueDictionary
{
{"area", ControllerContext.RouteData.Values["module"]},
{"controller", "dashboard"},
{"action", "index"},
{"location", location}
};
return RedirectToRoute(routeValues );
}
public ActionResult CatchUrl()
{
var routeValues = new RouteValueDictionary
{
{"area", "area1"},
{"controller", "dashboard"},
{"action", "index"},
{"location", "testLocation"}
};
return RedirectToRoute(routeValues );
}
}
And I get
No route in the route table matches the supplied values.
I am not sure why CatchUrl works and mine does not.

I actually don't get what you're asking, but by just looking at the code, that's not the standard way to create/use Areas in MVC 3,4 and 5.
You shouldn't need to write logics inside each controller and do the redirects.
In my RouteConfig, I usually just have the default route mapping. And when you have the needs for Areas, you can right click on the MVC web project in Visual Studio and click'Add -> Area'. That will create a folder with the area name inside an Areas folder right under the root of the web project. And within the area folder, you should find the AreaRegistration.cs for the area name and mappings.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "dashboard", action = "index", id = UrlParameter.Optional },
namespaces: new[] { "Company.Project.Web.UI.Controllers" }
);
}
}
And let's say you want to create an area called '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 { action = "index", id = UrlParameter.Optional },
namespaces: new[] { "Company.Project.Web.UI.Areas.Admin.Controllers" }
);
}
}
Lastly, I think a screenshot might be helpful.
-- updates --
Based on the comments, if you want the route /apple?location=home to go to Apple Controller and its Home method, while the route /orange?location=dashbard to go to Orange Controller and its Dashboard method, it's better to define a route in RouteConfig.
Ideally you wish you can have something like this in RouteConfig:
routes.MapRoute(
name: "Area-CatchAll",
url: "{area}?location={controller}"
);
But that's not possible as MVC will error out saying "The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.".
Instead, you can direct the traffic to a controller and you can define the area and location as parameters.
routes.MapRoute(
name: "Area-CatchAll",
url: "{area}",
defaults: new { controller = "Area", action = "Translate" }
);
public class AreaController : Controller
{
// The {area} from route template will be mapped to this area parameter.
// The location query string will be mapped to this location parameter.
public ActionResult Translate(string area, string location)
{
// This also assumes you have defined "index" method and
// "dashboard" controller in each area.
if (String.IsNullOrEmpty(location))
{
location = "dashboard";
}
return RedirectToAction("index", location, new { area = area });
}
}
Again, I wouldn't create route like that to redirect traffic to areas, if I don't have to.

I am trying to get it so that when you visit
/{area}/ it does some logic and redircts you to the correct controller. Similar to how CatchUrl works
First of all, lets be clear about this. The catch-all route is not routing it is redirecting. It is issuing an HTTP 302 response to the browser, which tells the browser to lookup a new location on your server. This sort of thing is not very efficient because it requires an extra round trip across the network, but it is the only way to get the URL in the browser to change from the server side (if that is what you intend to do).
Routing, on the other hand, is taking the initial request and sending it directly to a specific resource (controller action) without the extra round trip across the network and without changing the URL in the browser.
Your routes are already setup to use
/Area/Controller
to route to the correct controller here:
public abstract class CustomAreaRegistration : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
AreaName + "Ajax",
AreaName + "/{controller}/{action}",
new { action = "Index" }
);
context.MapRoute(
AreaName + "ShortUrl",
AreaName + "/{controller}",
new {action = "Index"}
);
}
}
(assuming you have a subclass of this for each area that sets the AreaName).
If you indeed want to route (not redirect) the /module/ URL to the DashboardController.Index method in the corresponding Area, you change your ShortUrl to make the controller optional and default it to the DashboardController.
public abstract class CustomAreaRegistration : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
AreaName + "Ajax",
AreaName + "/{controller}/{action}",
new { action = "Index" }
);
context.MapRoute(
AreaName + "ShortUrl",
AreaName + "/{controller}",
new { controller = "Dashboard", action = "Index" }
);
}
}
This will send the URL /Foo/ to the Foo area's DashboardController.Index method, but send the URL /Foo/Bar/ to the Foo area's BarController.Index method.
The above assumes that you are diligent enough to ensure that none of your AreaNames are the same as controller names in the non-area part of your project. If they are, you will never be able to reach those non-area controllers without some extra configuration such as a route constraint.
The MainController.Index method isn't needed at all (unless you have some specific reason why you want to change the URL in the browser).

Related

ASP MVC 1 Custom Routing for Folders and SubFolders

I'm developing a simple CRUD application using ASP MVC 1 that will store data across multiple tables for multiple student programs. In doing so, I'm trying to figure out how to structure the URL to accommodate each program, their tables, and their actions.
For example, something I am trying to achieve is:
site.com/StudProg1/Participant/Create will be the URL for inserting an entry for the Participant Table for Student Program 1
site.com/StudProg2/Course/ will be the URL for the Index page for the Course Table for Student Program 2
In my attempt to create custom routes to accommodate this, my Global.asax.cs file stands as follows:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"StudProg1",
"StudProg1/{pageName}/{action}",
new { controller = "StudProg1", pageName="Index", action = "Index" }
);
routes.MapRoute(
"StudProg2",
"StudProg2/{pageName}/{action}",
new { controller = "StudProg2", pageName="Index", action = "Index" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
}
Where pageName is supposed to be the table name.
Not surprisingly, the above structure does not return the output I am aiming for (i.e. it always points to the Index page for StudProg).
Additionally, my file directory is structured as such:
Views
StudProg1
Index
Participant
Index
Create
StudProg2
Index
Courses
Index
Create
etc.
My question is, how can I improve my routes to correctly achieve the URL structure I desire. Additionally, besides the Microsoft ASP.NET Developer Network Site, are there any good tutorials on custom routes?
You should not be creating a controller for each student program (StudProg1,StudProg2 etc). You can accept that as a parameter.
You need only 2 controlellers, one for Particiant and one for Course.
public class ParticipantController : Controller
{
public ActionResult Index()
{
return Content("Reques for partiicpant index page if needed");
}
public ActionResult create(string studentProgram)
{
return Content("Partiicpant create for "+studentProgram);
}
}
public class CourseController : Controller
{
public ActionResult Index()
{
return Content("Course:");
}
}
and in your RouteConfig's RegisterRoutes method, specify the 2 specific routes before the generic default route.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("part","{studentProgram}/Participant/{action}",
new { controller = "Participant", action = "Index" }
);
routes.MapRoute("course", "{studentProgram}/Course/{action}",
new { controller = "Course", action = "Index" }
);
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}

How to Create a dynamic Controller to handle many static Views in ASP.NET MVC?

I have a bunch of mostly-static pages (about 40),
like: order-form01.html, order-form02.html, orderform03.html etc..
Should each of them have its own Controller/Action, or is that possible to have one dynamic Controller/Action for all of them?
My Url should look like this: http://MyProject/GlobalController/IndividualView and for the above example: http://MyProject/OrderForm/order-form01, http://MyProject/OrderForm/order-form02 etc..
Thanks in advance.
Yes it's very easy AND you don't need a switch statement or any other redundant logic.
public class MyController
{
public ActionResult Page(string file)
{
return View(file);
}
}
The magic is in the Route Map:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// New MapRoute for your 40+ files..
routes.MapRoute(
"OrdeForm",
"OrderForm/{file}",
new { controller = "MyController", action = "Page", {file} = "" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
}
Additionally:
I pass the View name in the query string.
Is not required, but is supported. The following urls will work:
// Url Parameters
http://MyProject/OrderForm/order-form01
http://MyProject/OrderForm/order-form02
// Querystring Parameters
http://MyProject/OrderForm?file=order-form01
http://MyProject/OrderForm?file=order-form02
The only catch is that you need to rename your html files to cshtml and place them in the correct directory for the ViewEngine to find.
#Erik, I also bit of new to mvc . Could you please explain your route map as of how is it possible with default raute again and again
Routes are broken down into 3 values:
Controller
Action
Parameter(s)
At a bare minimum, the controller and action are required. Where the values come from is not dependent on the Url. For example, in the following Url and Map Route...
// Url
http://MyProject/
// MapRoute
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = "" }
);
// Controller named "Home" matches the default in the above route
// Method named "Index" matches the default in the above route
public class HomeController {
public ActionResult Index() {
return new EmptyResult();
}
}
... everything still works because we provided a default value for the controller and action.
Ok let's break down the URL you want:
http://MyProject/OrderForm/order-form01
http://MyProject/OrderForm/order-form02
http://MyProject/<identifier>/{parameter}
You have one identifier that tells me route (OrderForm) and one changing value that because it changes and you want one value, should be a parameter.
http://MyProject/<identifier>/{file}
The name of the parameter makes no difference as long as it matches the signature of the controller method:
http://MyProject/{Controller}/{file}
public class HomeController {
public ActionResult Index(string file) {
return new EmptyResult();
}
}
or
http://MyProject/{Controller}/{einstein}
public class HomeController {
public ActionResult Index(string einstein) {
return new EmptyResult();
}
}
I named the parameter file, because it tells me it's the parameter is a name of a file, whereas the name einstein has no inherent description so is a terrible name for a variable.
http://MyProject/{Controller}/{file}
// MapRoute
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = "" }
);
// Controller named "Home" matches the default in the above route
// Method named "Index" matches the default in the above route
public class HomeController {
public ActionResult Index() {
return new EmptyResult();
}
}
Now we only want this route to run when the identifier is OrderForm so we don't allow that to be a value, we hard code it.
url: "OrderForm/...
Next we have a value that keeps changing, so we to add url parameter:
url: "OrderForm/{file}"
Now we have an issue because we aren't allowing MVC to parse values from the url to populate Controller nor Action so we must supply them.
routes.MapRoute(
name: "",
url: "OrderForm/{file}",
defaults: new { controller = "Home", action = "Index", file = "" }
);
Here we've mapped the url http://MyProject/OrderForm/{file} to:
public class HomeController {
public ActionResult Index(string file) {
return new EmptyResult();
}
}
Now I would choose to to update the defaults to something more specific and descriptive:
routes.MapRoute(
name: "",
url: "OrderForm/{file}",
defaults: new { controller = "OrderForm", action = "Index", file = "" }
);
public class OrderFormController {
public ActionResult Index(string file) {
return new EmptyResult();
}
}
Hope that all makes sense.
After the question edited :my solution is, you can have one controller/action and it should call view (cshtml). Your querystring data should be pass to view as of viewbag variable and partial views should be called acording to the viewbag variable. noo need of editing routing table even(if you are willing to pass it as a query string).
//your routeconfig will be
routes.MapRoute(
name: "default",
url: "{controller}/{file}",
defaults: new { controller = "OrderForm", action = "Index", file = "" }
);
//here no need to have 40 routing table one is enough
//your controller/action will be
public class OrderForm
{
public ActionResult Index(string file)
{
ViewBag.Orderform=file
return View(file);
}
}
//view bag variable is accessible in view as well as in javascript
But I would say as best practice, you can modify default routing to access all urls and navigate it to same controller/action and let that action to return the view. After that use angular / knockout js to handle client side routing and based on it the partial views should be loaded.(still your url will be different for your 40 pages but noo need to pass it as query string)
//your route table will be
routes.MapRoute(
name: "default",
url: "{controller}/{file}",
defaults: new { controller = "OrderForm", action = "Index"}
);
//your controller will be
public class OrderForm
{
public ActionResult Index()
{
return View(file);
}
Navigation should be handled by client side routing

Areas is not working in mvc 4

i tried to split my module in areas
my modules are
1. Login
2. User Admin
3. Master
i created the area and my first start up page is (area = login, controller = login, action = loginpage)
My code route config codes are
RouteConfig.cs
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login", action = "Login", id = UrlParameter.Optional },
namespaces: new[] { "SRR.Areas.Login.Controllers" }
);
}
}
LoginAreaRegistration.cs
public override string AreaName
{
get
{
return "Login";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
//Trace.WriteLine("Login");
context.MapRoute(
"Login_default",
"{controller}/{action}/{id}",
new { controller = "Login", action = "Login", id = UrlParameter.Optional },
namespaces: new[] { "SRR.Areas.Login.Controllers" }
);
}
UserAdminAreaRegistration.cs
public class UserAdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "UserAdmin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"UserAdmin_default",
"UserAdmin/{controller}/{action}/{id}",
new { controller = "Menu", action = "MenuPrivilages", id = UrlParameter.Optional },
namespaces: new[] { "SRR.Areas.UserAdmin.Controllers" }
);
}
}
Here my starting page login is coming fine but the useradmin module is views are not showing it through the error has "The resource cannot be found".
Thanks in advance
Login area registration missies the area specification in the pattern definition; change "{controller}/{action}/{id}" to "Login/{controller}/{action}/{id}"
What happens if your remove the namespaces constraints from the route registrations? Are the controllers (all of them Login and UserAdmin) in correct namespaces? I guess the Login controllers are showing because of the missing area specification as i mentioned above and the Login* controllers aren't in the correct namespaces.
You RegisterRoutes method misses a line before the default route registration:
AreaRegistration.RegisterAllAreas();
The issue that I believe is you have the controller 'login' and action method 'login' twice, one in area and other in root. So that the namespace must differ but you have the similar namespace for both the routes which is wrong. By default it is not necessary to specify the namespace for the default route since it'll map the root files. This might be the reason why the application could not find which log in to call.

How To Create Custom Route in MVC

I have an Area named Admin with a model named CMSPage. My controller is named CMSPagesController. I would like to create a custom route so I can use simply Page instead of CMSPage, so I thought by creating the following custom route, it would work but nope:
routes.MapRoute(
"AdminPages",
"Admin/Pages/{action}/{id}",
new { controller = "CMSPages", action = "Index", id = UrlParameter.Optional }
);
Could someone please lead me in the right direction?
using System.Web.Mvc;
using System.Web.Routing;
namespace MvcApplication1
{
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Blog", // Route name
"Archive/{entryDate}", // URL with parameters
new { controller = "Archive", action = "Entry" } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
}
The order of the routes that you add to the route table is important. Our new custom Blog route is added before the existing Default route. If you reversed the order, then the Default route always will get called instead of the custom route.
The custom Blog route matches any request that starts with /Archive/. So, it matches all of the following URLs:
/Archive/12-25-2009
/Archive/10-6-2004
/Archive/apple
The custom route maps the incoming request to a controller named Archive and invokes the Entry() action. When the Entry() method is called, the entry date is passed as a parameter named entryDate.
Oops on my part. I forgot that there was the area registration process that happens as well. The issue is that I want to access this controller from the area in which I created it (Admin). So, the custom route registration has to happen there. Not in my RouteConfig.cs (see below). Thanks for the response Neeraj, you're answer is not wrong, just not correct for my question which was in regards to an area.
using System.Web.Mvc;
namespace WebApplication1.Areas.Admin
{
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
// This is where the custom route has to be registered for me to access
// it from my area.
context.MapRoute(
"Admin_pages",
"Admin/Pages/{action}/{id}",
new { action = "Index",
controller = "CMSPages",
id = UrlParameter.Optional }
);
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
}

How do I pass string to a controller?

So I am trying to do this:
http://localhost:43751/api/doWork/asdfasfsaf
And here is Controller:
public class DoWorkController : ApiController
{
public User GetWork(String input) {
Dictionary<int, Work> users = DataObjects.Work.find(input);
return users.Values.First<Work>();
}
}
I run it and it doesn't work:
No HTTP resource was found that matches the request URI 'http://localhost:43751/api/doWork/asdfasfsaf'.
and
No action was found on the controller 'DoWork' that matches the request.
What am I doing wrong?
Here is my Routing Configuration:
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
It seem that all I needed to do to get this to work is this:
public class DoWorkController : ApiController{
public User GetDoWork(String ID) {
Dictionary<int, Work> users = DataObjects.Work.find(input);
return users.Values.First<Work>();
}
}
Because MVC believes you're referring to a different URL. You need to either define a method that is listening to your requests of type HttpGet or HttpPost:
[HttpGet]
public ActionResult doWork(string input) {
// do processing and return the selected user as a model back to the view
User user = YourGetUserMethod(input);
return View(user);
}
so you could invoke it as: /api/doWork?input=asdfasfsaf
Or if what you want is to change your URL routing to accept the last portion of your URL as a parameter, you'd need to add something like this to your RouteConfig.cs file:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "DoWork", action = "doWork", id = UrlParameter.Optional }
);
You might want to follow this post for additional information regarding route mapping

Categories