title: In ASP.NET Core, using OData, How to use a Route that differs from the Controller name?
body: <p>Utterly confused as to OData v8.0 Routing.</p>
  <ul>
  <li>For
  <ul>
  <li>security objectives (unecessarily avoiding exposing internal workings)</li>
  <li>availability objectives (avoiding inadvertently introducing breaking apis when refactoring and renaming Controllers),</li>
  </ul>
  </li>
  <li>The objective is to use OData's <code>AddRouteComponents()</code> to register Controllers
  <ul>
  <li>with a path that does not match/expose the actual name of the Controller.</li>
  </ul>
  </li>
  </ul>
  <p>In a test rig, have developed 3 controllers:</p>
  <ul>
  <li><code>PresentationLayerExampleAEntityOData01Controller</code>  with no <code>RouteAttribute</code>,</li>
  <li><code>PresentationLayerExampleAEntityOData02Controller</code>  with <code>[Route(&quot;PresentationLayerExampleAEntityOData02&quot;)]</code> (matching name of Controller)</li>
  <li><code>PresentationLayerExampleAEntityOData03Controller</code>  with a <code>[Route(&quot;AnyOtherRouteName&quot;)]</code> (not matching name of Controller)</li>
  </ul>
  <p>At startup (skipping showing the reflection used) the process is pretty standard, in that an Entity model is registered, along with the Controller name to use, along with a prefix (<code>api/odata/</code>):</p>
  <pre class="lang-cs prettyprint-override"><code>   blahblah...const string routePrefix = &quot;api/odata/&quot;;
     edmBuilder...blahblah...
     iterate through found some types, each time invoking....define...blahblah...
     ....
     and where required, use an extension method to specify the controller name based on the controller Type name, or its route path attribute if it sees one....
     string controllerName = 
                  typeof(PresentationLayerExampleAEntityOData01Controller)
                  .GetODataControllerModelName();
     ...etc...
     var catwalkModel = edmBuidler.GetEdmBuilder();
     ...done:
     opt.AddRouteComponents( routePrefix, catwalkModel);
  </code></pre>
  <p>As for the Controllers, they inherit from a base class that in turn derives from ODataController...therefore are already decorated with <code>[ODataAttributeRouting]</code></p>
  <p>Call <code>/Swagger</code>, and the above controller shows up, is invokable, and the results comeback as OData (ie, under a <code>value</code>, alongside <code>$odata.context</code>).</p>
  <p>Cool.</p>
  <p>But it's not yet what we are after....</p>
  <h3>Case 1: Using Conventions</h3>
  <pre class="lang-cs prettyprint-override"><code>    //No Route [Route(ApiConstants.OdataApiRoutePrefix + &quot;[controller]&quot;)]
      public class PresentationLayerExampleAEntityOData01Controller :
          ModuleQueryableODataControllerBase&lt;ExampleAEntityDto&gt;
      {
          [HttpGet(&quot;&quot;)]
          [HttpGet(&quot;Get&quot;)]
          [EnableQuery(PageSize = 100)]
          public IActionResult Get()
          {
              return Ok(_exampleEntityAService.Get());
          }     
      }
  </code></pre>
  <p>works fine in that one can Swagger finds them and:</p>
  <ul>
  <li><code>/api/odata/PresentationLayerExampleAEntityOData01/Get</code> or</li>
  <li><code>/api/odata/PresentationLayerExampleAEntityOData01/</code></li>
  </ul>
  <p>and the resuts will be a proper OData wrapped payload.</p>
  <p>That said, Swagger is utterly confused and is showing the OData Route (works) but also an alternate route (which doesn't).</p>
  <ul>
  <li><code>/PresentationLayerExampleAEntityOData01/Get</code> or</li>
  <li><code>/PresentationLayerExampleAEntityOData01/</code></li>
  </ul>
  <p>Have no idea yet what's going on there and how to exclude the above.</p>
  <p>But ...trucking on...</p>
  <h3>Case 2: Using Routing</h3>
  <p>Took me ages to figure out how to use the <code>RoutingAttribute</code> (I think), because for ages it was returning data, but <em>not</em> wrapped as an OData returnable...</p>
  <p>The error I would see during build was something to the effect that it could not determine the Controller part from the Path template given.
  It appears that defining the Route within the prefix <em>does not</em> work:</p>
  <pre class="lang-cs prettyprint-override"><code>// Does not work, and data comes back as non-wrapped data array:
      [Route(ApiConstants.RestApiRoutePrefix+&quot;[controller]&quot;)]
      [ApiController]
      [ForDemoOnly]
      public class PresentationLayerExampleAEntityController : ControllerBase 
  </code></pre>
  <p>(and I was unable to figure out a way to use <code>ODataRouteComponent</code> to produce a better outcome) -- whereas the following <em>does</em> behave more correctly:</p>
  <pre class="lang-cs prettyprint-override"><code>// remove the prefix and it seems happier:
  // data DOES comes back as wrapped OData data:
      [Route(ApiConstants.RestApiRoutePrefix+&quot;[controller]&quot;)]
      [ApiController]
      [ForDemoOnly]
      public class PresentationLayerExampleAEntityController : ControllerBase 
  </code></pre>
  <h3>Case 3: Using Routing to Achieve the Desired Outcome</h3>
  <p>But although I've got the <code>RouteAttribute</code> to be accepted, I've gotten no closer to the objective -- using any other <code>Route</code> to get to the OData <code>Controller</code>. As in:</p>
  <pre class="lang-cs prettyprint-override"><code>    [Route(&quot;AnyOtherRouteName&quot;)]
      public class PresentationLayerExampleAEntityOData03Controller :
          ModuleQueryableODataControllerBase&lt;ExampleAEntityDto&gt;
      {
      ...
      }
  </code></pre>
  <p>Except ... it doesn't work.</p>
  <p>OData never found the controller, so doesn't offer it (Swagger, ever so cheerful and helpful does present some options but they are not prepended with <code>api/odata/</code> ... and of course fails ... a false herring of useless documentation :-(</p>
  <h3>Conclusion</h3>
  <p>I don't have one.
  Except that after a day fighting all this, I do need some advice to achieve the outcome of case 3.</p>
  <p>Thank you.</p>