Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC Alternative routing is failing with optional parameter

I am trying to implement alternative routing in my MVC 5 web app.

I have the controller code:

namespace MyMvcProject.Web.Controllers
{
    [RoutePrefix("account")]
    public class AccountController : Controller {


        [Route("{param}")]
        [Authorize]
        public ActionResult Index(string param = null) {

...

which works great when hitting the url: http://example.com/account/testparam. However, I would like to be able to have the value param as an optional parameter.

I have tried changing [Route("{param}")] to [Route("{param}*")] but then the Index() method is never entered. I have also tried changing it to [Route("{param:string=Test}")] but I get a routing runtime error with the term string.

My RoutConfig.cs contains:

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

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

Does anyone have any idea how I could force Index() to have an optional parameter using this alternative routing syntax? It's quite useful in other parts of my app, so I'd like to keep the RoutePrefix and Route decorators.

UPDATE

I'm still trying to figure this out, so I changed my route decorator to [Route("{param:int=0}")] and my constructor to public ActionResult Index(int id) and it works as expected (i.e., http://example.com/account behaves as if http://example.com/account/0 was entered. This is exactly what I want, only using string datatypes.

When I change the decorator to: [Route("{id:string=\"\"}")] and the constructor to public ActionResult Index(string id) and I see the runtime error:

The inline constraint resolver of type 'DefaultInlineConstraintResolver' was unable to resolve the following inline constraint: 'string'.

like image 601
Brett Avatar asked Oct 08 '14 01:10

Brett


2 Answers

Found the answer here. I need to make param nullable using ?.

[Route("{param?}")]
[Authorize]
public ActionResult Index(string param = null) {
   ...
}

Hopefully this will help someone in the future. Not many references that I could find on the topic.

like image 89
Brett Avatar answered Nov 09 '22 14:11

Brett


@Brett's answer is great: adding ? to the parameter in the Route attribute and then providing a default value in the Action signature allows that parameter to be optional.

And here's an excellent article from Mike Wasson on Attribute Routing in Web API 2, which includes a piece on optional parameters.

Mike also talks about applying multiple constraints, and says:

You can apply multiple constraints to a parameter, separated by a colon.

  [Route("users/{id:int:min(1)}")]
  public User GetUserById(int id) { ... }

which also works great...until you want multiple constraints and an optional parameter.

As we know, applying ? allows the parameter to be optional. So taking the above example, one might assume that

[Route("users/{id:int?:min(1)}")]
public User GetUserById(int id = 1) { ... }

constrains the id parameter to be integers greater than 0, or empty (in which case the default of 1 is used). In reality, we get the error message:

The inline constraint resolver of type 'DefaultInlineConstraintResolver' was unable to resolve the following inline constraint: 'int?'.

It turns out that the order of the constraints matters! Simply putting the optional constraint ? after all other constraints gives the intended behavior

[Route("users/{id:min(1):int?}")]
public User GetUserById(int id = 1) { ... }

This works for more than 2 constraints as well, e.g.

[Route("users/{id:min(1):max(10):int?}")]
public User GetUserById(int id = 1) { ... }
like image 6
kaveman Avatar answered Nov 09 '22 12:11

kaveman