5

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.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
CuriousDeveloper
  • 849
  • 2
  • 8
  • 27
  • The relevant part of catch-all seems to be the wildcard component `{*url}` and I don't see anything similar in your `{module}/` route. Maybe have a look at the following for related information: https://stackoverflow.com/questions/7515644/infinite-url-parameters-for-asp-net-mvc-route – grek40 Oct 02 '17 at 17:02

2 Answers2

0

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. enter image description here

-- 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.

David Liang
  • 20,385
  • 6
  • 44
  • 70
  • Wouldnt let me edit: I already have areas working. I am wanting `/area/` to route to a catch-all like controller which gives the opportunity to figure out which controller they should go to. Each area has two main controllers, Dashboard and Detail. Rather a specific person gets dashboard or detail depends on the get parameter sent to `/area/` `/Apple/?location=foo` will go to the `Apple` area and the Dashboard controller and `/Apple/?location=bar` will go to the `Apple` area and the Detail controller. Just as an example. – CuriousDeveloper Oct 05 '17 at 23:52
  • 1
    You could have just created routes for that. No need of a controller to handle this – Raphael Oct 06 '17 at 18:09
  • Take a look at my updates and see if that's what you're trying to accomplish? @CuriousDeveloper – David Liang Oct 06 '17 at 18:38
  • @DavidLiang - `MapRoute` cannot be used to specify URL parameters. However, you can do it by subclassing `RouteBase` or `Route`. Frankly, if a redirect is what CuriousDeveloper is really after, I would create a global filter for it rather than creating a frivolous controller whose only purpose is to redirect. – NightOwl888 Oct 07 '17 at 09:23
0

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).

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • I already have areas working. I am wanting `/area/` to route to a catch-all like controller which gives the opportunity to figure out which controller they should go to. Each area has two main controllers, Dashboard and Detail. Rather a specific person gets dashboard or detail depends on the get parameter sent to `/area/` `/Apple/?location=foo` will go to the `Apple` area and the Dashboard controller and `/Apple/?location=bar` will go to the `Apple` area and the Detail controller. Just as an example. – CuriousDeveloper Oct 05 '17 at 23:52
  • So why not just route there directly instead of redirecting? Do you need `/Apple/?location=foo` to change to `/Apple/Foo` for some reason? – NightOwl888 Oct 06 '17 at 15:40
  • `location=` was holder for some user identifier, where it redirects to either a splash screen or not based on a property stored in the db for that user. So its not pointing to a controller per say, but to something that can be used to find which controller to redirect to – CuriousDeveloper Oct 06 '17 at 21:56
  • You shouldn't just depend on the user identifier on the request query string. It won't be safe because logged in user can easily change to somebody else. Instead, you should get the logged in user identity on the server side, and based on the properties of the user you can redirect the request to other areas. – David Liang Oct 07 '17 at 02:21
  • Thanks David. CuriousDeveloper said "user", not "logged in user". There isn't enough specificity here to determine what he really wants. Redirects are bad for SEO and performance, not to mention if you POST to the URL and there is a redirect to a GET the data that was POSTed is lost. But if he is trying to create some kind of "short URL" redirect service such as https://goo.gl/, it might be the right way to go. – NightOwl888 Oct 07 '17 at 09:19