Sandeep G B
Sandeep G B

Reputation: 4015

ASP.net MVC routing with optional first parameter

I need to provide following functionality for one of the web sites.

http://www.example.com/[sponsor]/{controller}/{action}

Depending on the [sponsor], the web page has to be customized.

I tried combination of registering the routes with Application_Start and Session_Start but not able to get it working.

public static void RegisterRoutes(RouteCollection routes, string sponsor)
{
        if (routes[sponsor] == null)
    {
      routes.MapRoute(
     sponsor, // Route name
     sponsor + "/{controller}/{action}/{id}", // URL with parameters
     new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
     );
    }
}

Also, the default behavior without [sponsor] should also function. Can someone please let me know if it is technically feasible to have an optional first parameter in the MVC3 URL. If yes, please share the implementation. Thank you.


Updated Code After making the changes as suggested by Sergey Kudriavtsev, the code works when value is given. If name is not provided then MVC does not route to the controller/action.

Note that this works only for the home controller (both and non-sponsor). For other controllers/actions, even when sponsor parameter is specified it is not routing.

Please suggest what has to be modified.

public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.MapRoute(
             "SponsorRoute",
             "{sponsor}/{controller}/{action}/{id}", // URL with parameters
             new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

        routes.MapRoute(
            "NonSponsorRoute",
            "{controller}/{action}/{id}",
            new { controller = "Home", action = "Index", id = UrlParameter.Optional, sponsor = string.Empty }
        );
    }

Action Method

public ActionResult Index(string sponsor)
    {
    }

Upvotes: 13

Views: 18307

Answers (4)

saurav Sinha
saurav Sinha

Reputation: 448

i will show you in simple example you don't have to change in Route.config.cs only you have to do in Route.config.cs just put in

Optional URI Parameters First and Default Values

Route.config.cs

routes.MapMvcAttributeRoutes();

Controller

[Route("{Name}/Controller/ActionName")]
        public ActionResult Details(string Name)
        {            

              // some code here 

            return View();
        }

Results

localhost:2345/Name/controllername/actionname/id(optional)

Upvotes: 1

Sergii Kudriavtsev
Sergii Kudriavtsev

Reputation: 10532

In your case sponsor should not be treated as a constant part of URL, but as a variable part.

In Global.asax:

public static void RegisterRoutes(RouteCollection routes)
{
...
     routes.MapRoute(
     "SponsorRoute",
     "{sponsor}/{controller}/{action}/{id}", // URL with parameters
     new { controller = "Home", action = "Index", id = UrlParameter.Optional }
     );
     routes.MapRoute(
     "NonSponsorRoute",
     "{controller}/{action}/{id}",
     new { controller = "Home", action = "Index", id = UrlParameter.Optional, sponsor=string.Empty }
     );

...
}

In your controllers, for example, HomeController.cs:

namespace YourWebApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index(string sponsor)
        {
            // Here you can do any pre-processing depending on sponsor value, including redirects etc.
        }
        ...
    }
}

Note that type of this parameter will always be System.String and the name of route template component {sponsor} must exactly match the name of action parameter string sponsor in your controllers.

UPD: Added second route for non-sponsor case.

Please note that such setup will complicate your logic, because you might confuse different urls, for example URL

http://www.example.com/a/b/c

could be matched by both routes: first one will have sponsor=a, controller=b and action=c; second one will have controller=a, action=b and id=c.

This situation can be avoided if you specify more strict requirements to URLs - for example, you may want IDs to be numerical only. Restrictions are specified in fourth parameter of routes.MapRoute() function.

Another approach for disambiguation is specifying separate routes for all of your controllers (usually you won't have much of them in your app) before generic route for sponsors.

UPD:

Most straightforward yet least maintainable way to distinguish between sponsor and non-sponsor routes is specifying controller-specific routes, like this:

public static void RegisterRoutes(RouteCollection routes)
{
...
     routes.MapRoute(
     "HomeRoute",
     "Home/{action}/{id}", // URL with parameters
     new { controller = "Home", action = "Index", id = UrlParameter.Optional, sponsor=string.Empty }
     );
     routes.MapRoute(
     "AccountRoute",
     "Account/{action}/{id}", // URL with parameters
     new { controller = "Account", action = "Index", id = UrlParameter.Optional, sponsor=string.Empty }
     );

     ...

     routes.MapRoute(
     "SponsorRoute",
     "{sponsor}/{controller}/{action}/{id}", // URL with parameters
     new { controller = "Home", action = "Index", id = UrlParameter.Optional }
     );

...
}

Note that here all controller-specific routes must be added before SponsorRoute.

More complex yet more clean way is implementing RouteConstraints for sponsor and controller names as described in answer from @counsellorben.

Upvotes: 17

ob1dev
ob1dev

Reputation: 1230

In my case, I've resolved this issue using the following two routers:

public class RouteConfig
{
  public static void RegisterRoutes(RouteCollection routes)
  {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "MultiCulture",
        url: "{culture}/{controller}/{action}",
        defaults: new { controller = "Home", action = "Index" },
        constraints: new { culture = new CultureConstraint(CultureFactory.All.Select(item => item.UrlPrefix).ToArray()) }
    ).RouteHandler = new MultiCultureMvcRouteHandler();

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}",
        defaults: new { controller = "Home", action = "Index" }
    );
  }
}

Where CultureConstraint class looks like below:

public class CultureConstraint : IRouteConstraint
{
  private readonly string[] values;

  public CultureConstraint(params string[] values)
  {
    this.values = values;
  }

  public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary routeValues, RouteDirection routeDirection)
  {
    string value = routeValues[parameterName].ToString();

    return this.values.Contains(value);
  }
}

And MultiCultureMvcRouteHandler like this:

public class MultiCultureMvcRouteHandler : MvcRouteHandler
{
  protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
  {
    var culture = CultureManager.GetCulture(requestContext.RouteData);

    if (culture != null)
    {
      var cultureInfo = new CultureInfo(culture.Name);

      Thread.CurrentThread.CurrentUICulture = cultureInfo;
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);
    }

    return base.GetHttpHandler(requestContext);
  }
}

Upvotes: 9

counsellorben
counsellorben

Reputation: 10924

In addition to adding a second route before the default route, as Sergey said in his answer, you also must add a RouteConstraint to the initial route, to enforce that the {sponsor} token is the name of a valid sponsor.

You can use the RouteConstraint in this answer: Asp.Net Custom Routing and custom routing and add category before controller

Remember that you must also enforce a rule that a sponsor name cannot be the same as any of your controller names.

Upvotes: 4

Related Questions