asp.net-web-apiasp.net-web-api-routingattributerouting

Web Api Routing: Multiple controller types were found that match the URL for parameter VS constant paths


My issue is similar to Web Api Routing : Multiple controller types were found that match the URL but I want to keep them in separate controllers.

From the comments, 2 preexisting answers are good workarounds but do not solve the actual issue I'm trying to resolve.

The URLs I'm making up are similar to nested directories in a file system OR are very similar to Firebase URLs.

/BiggestSet/{BiggestSetCode}/Subset1/{Subset1Code}/SubsetOfSubset1/{SubsetOfSubset1}

... etc all the way down to where ever the tree stops. Think of it as a tree of data.

/Collection/{Instance}/Collection/{Instance}

The issue I have is that at the /Collection level I want to also provide specific collection level operations. Like Add and search and other collection specific Operations Collection/ProccessData

Collection Controller:
/Collection/Add 
/Collection/ProcessDataOnTheColleciton

Instance Controller:
/Collection/{InstanceCode}
/Collection/{InstanceCode}/ProcessOnTheInstance

The problem I'm having is the Collection/ProcessData clashes with the instance Collection/{InstanceCode}

NOTE: 1 is an parameter and the other is a constant.

If you setup the controllers so that collection and Instance are in the same controller. the /{InstanceCode} doesn't clash with the /ProcessData

BUT

If you setup so the controllers are split into logical functions WebAPI gives the error Multiple controller types were found that match the URL.

Does anyone know how to modify attribute routing to somehow behave as if they are in the same controller OR to prioritize the constant over the parameter across controllers?


Solution

  • To keep two separate controllers and still have such routes you can use regular expression route constraints. This way you can specify for the instanceCode you accept everything except the actions from the other controller.

    Here is a sample of how to configure routes like that:

    public class CollectionController : ApiController
    {
       [HttpGet]
       [Route("Collection/Add")]
       public string Add()
       {
          return $"CollectionController = Collection/Add";
       }
    
       [HttpGet]
       [Route("Collection/Process")]
       public string Process()
       {
          return $"CollectionController = Collection/Process";
       }
    }
    
    public class InstanceController : ApiController
    {
       [HttpGet]
       [Route("Collection/{instanceCode:regex(^(?!Add$|Process$).*)}")]
       public string Get(string instanceCode)
       {
          return $"InstanceController = Collection/{instanceCode}";
       }
    
       [HttpGet]
       [Route("Collection/{instanceCode:regex(^(?!Add$|Process$).*)}/Process")]
       public string Process(string instanceCode)
       {
          return $"InstanceController = Collection/{instanceCode}/Process";
       }
    }
    

    Here is also a link to the post that explains the regular expression used in the sample.

    An even better option would be if you have a specific format for the instanceCode and set the regular expression to accept only this specific format. Then you would not need to modify the regular expression for every new action added. I include also a link to the documentation for all available Route constraints. There you can see all the available options. For example if your instance code is a number you don't even need a regular expression you can just restrict with the int constraint like this [Route("Collection/{instanceCode:int}")].