asp.net web api route for controllers with same name - asp.net

I am integrating my project with another one
Multiple types were found that match the controller named 'XXXXX'.
This can happen if the route that services this request ('api/{controller}/{action}/{id}') found multiple controllers defined with the same name but differing namespaces, which is not supported.
The request for 'XXXXX' has found the following matching controllers:
COM.example.host.XXXXXController
COM.example.parasite.XXXXXController
Is there a way to make this somehow work with the least amount of edit from either side?
I would like to avoid having to make modifications to all of my controllers.
Thank you

Unfortunately, that is not very simple because default controller selector (DefaultHttpControllerSelector) does not look for namespace in the full controller name when it selects controller to process request.
So, there are at least two possible solutions to your problem:
Write your own IHttpControllerSelector which takes controller type namespace into account. Sample can be found here.
Rename one of controller types to make then unique.
TL;DR Default controller selector uses the cache of controller types (HttpControllerTypeCache) which holds something like:
{
"Customers" : [
typeof(Foo.CustomersController),
typeof(Bar.CustomersController)
],
"Orders" : [
typeof(Foo.OrdersController)
]
}
So it uses controller name as dictionary key, but it can contain several types which have same controller name part. When request is received, controller selector gets controller name from route data. E.g. if you have default route specified "api/{controller}/{id}" then request api/customers/5 will map controller value to "customers". Controller selector then gets controller types which are registered for this name:
if there is 1 type, then we can instantiate it and process request
if there is 0 types, it throws NotFound exception
if there is 2 or more types, it throws AmbiguousControllerException exception (your case)
So.. definition of another named route will not help you, because you can only provide controller name there. You cannot specify namespace.
Even if you'll use Attribute Routing and specify another route for one of the controller actions
[RoutePrefix("api/customers2")]
public class CustomersController : ApiController
{
[Route("")]
public IHttpActionResult Get()
//...
}
you will still face the controller selector problem because attribute routes are applied to actions - there will be no controller value in route data for those requests. Attributed routes are treated differently - they are processed as sub-routes of the "MS_attributerouteWebApi" route.

The easiest path for you might be to add action based routing to your web api:
RouteTable.Routes.MapRoute(
"WithActionApi",
"api/{controller}/{action}/{id}"
);
To your WebApiConfig.cs, be sure to put it above the default rule. Then, change how you call your controllers by including the method name, which at this point should be or should be made unique.

Related

How to get rid of route-name conflicts when ensuring api works both with and without the api-version?

I want to apply asp.net api-versioning to my web app (which didn't have versioning). However, the tricky issue is that I must ensure that APIs should work both with and without the api-version.
[ApiVersion("1.0")]
[Route("api/products/{productId}/[controller]")]
[Route("api/v{version:apiVersion}/products/{productId}/[controller]")]
[ValidateModel]
[Produces("application/json")]
public partial class ProductController : ControllerBase {
internal const string GetLatestRoute = "GET Product/GetLatestAsync";
[HttpGet(Name = GetLatestRoute)]
public async Task<IActionResult> GetLatestAsync() {
}
}
I have a controller with multiple actions, each of them is defined with a unique route name. When I add two routes (with and without versions) to the controller, there comes a route-name conflict error:
Attribute routes with the same name 'GET Products/GetLatestAsync' must have the same template:
Action: 'Service.Controllers.ProductController.GetLatestAsync (ProductFD)' - Template: 'api/products/{productId}/Product'
Action: 'Service.Controllers.ProductController.GetLatestAsync (ProductFD)' - Template: 'api/v{version:apiVersion}/products/{productId}/Product'
There are several answers on StackOverflow that say the issue can be solved by removing the route names defined for the action methods. However, in my scenario, the route names are used to create Url Links in several places in the project.
Is there an approach that I can get rid of the issue? I'm wondering whether I could append version to the route name variable or mapping the non-version api to the version/1.0 ...? On the other hand, there is a rare case that I update all the methods in a controller. So is it possible that I only define a route-prefix on the top-level of the controller and only apply the api-version on the method-level?
Route names and the route table are not API version aware. In order for this to work, you need to use double route registration like you have because you are versioning by URL segment (not recommended). If clients are properly following the links returned by the server, then always using the route generated with the explicit version in it will do. If the client doesn't honor that and just calls the APIs directly without the API version, the second template will handle that for you. If you are only generating links with the same controller, then I would suggest using CreatedAtAction instead because it will not rely on the route name. If memory serves me correct, you can specify the order of each [Route] for precedence. If unspecified, it will be the first attribute specified - which matters.
You'll also need to enable:
services.AddApiVersioning(options => options.AssumeDefaultVersionWhenUnspecified = true);
If you haven't already.
Last, but not least, beware the known, breaking change: Async suffix trimmed from controller action names. This has snared many people.

Attribute routing default url

I have the following method in my controller, using C# MVC with Attribute roouting:
[Route("")]
[Route("/something-else")]
public IActionResult Index(){
}
Im using two different routes to access this functionallity, since i want customers with bookmarks to the previous implementation to work.
The problem is that i cant specify which of these Routes will be the default one when i issue the action like this:
<a asp-controller="FOO" asp-action="Index">
Everythiing works as expected, both URLs work, but i cant specify which of these routes will be used when navigated to by the action, via the action above.
I would like that the first route be used everytime i navigate to this action, except when someone explicitly writes the old url into the browser.
Are there any default attributes to the [Route("")] tag?
The RouteAttribute class has an Order property. From the docs:
Gets the route order. The order determines the order of route execution. Routes with a lower order value are tried first.
For example:
[Route("/something-else", Order = 1)]
[Route("", Order = 2)]
public IActionResult Index(){
}
As an aside, I would strongly discourage you from serving the same page with multiple URLs. Google's indexing will give you worse ranking because of it. Instead, consider returning a redirect to the new URL instead.

Can I use controller name in controller destination

I want a route that will take requests from
/api/v1/job
to
Controller that lives in
/api/v1/jobController
where the controller name is jobv1Controller
configuration.Routes.MapHttpRoute(
"v1",
"Api/v1/{controller}/{id}",
new { controller = "{controller}v1", id = RouteParameter.Optional });
short version:
the in-built DefaultControllerFactory may not be able to create the controller you need, since it treats the value as a plain name string, and doesn't do any token replacement.
the solution would be to extend DefaultControllerFactory and override the CreateController method. Change the controllerName value and then use the base functionality to get you that controller.
Register your custome factory in Global.asax
ControllerBuilder.Current.SetControllerFactory(
typeof(YourCustomControllerFactory));
long version:
As you know the flow is as follows:
Request >> Routing System >> MVC's Default Controller Factory >> Create Controller >> Invoke it.
I don't think it is possible to give dynamic values in the default route data dictionary and expect the default controller factory to find that controller.
This is because the built-in controller factory works off the direct value of the controller key, given in the route data dictionary.
The MVC Handler does no magic there of trying to parse the controller value provided as some formatted string, and associating Url Fragment values (controller/action etc.) in this format. it sees it as a straight up value.
By default, the DefaultControllerFactory is invoked to get the controller type, and it uses the string value as-is to activate a controller type.
So one way to solve your problem is to define a custom factory implementing IControllerFactory
Once you do that, your factory's
CreateController method will be called, with the RequestContext and ControllerName string as the 2 parameters.
The RequestContext has the RequestContext.RouteData.Values dictionary, which has all the routing data, tokens, constraints, namespaces etc.
Once you have the incoming controller name, you can massage it to whatever format you need (controllername + "v1") and then instantiate that controller (using DI containers or Service Locator or Activator.CreateInstance etc. whatever you wish) and return it. Try to use some sort of look-up cache for this.
Once you have implemented your custom factory, you can register your custom controller factory with MVC as follows in Global.asax:
ControllerBuilder.Current.SetControllerFactory(
typeof(YourCustomControllerFactory));

Overriding RequestMapping on SpringMVC controller

Looking through the source for our applications, I found a common Spring MVC controller which display key and values of configuration copied and pasted several times. The class definitions are exactly the same, except for the RequestMapping value, since each application want to have this page available under different URLs.
I want to move this controller into a common library, and provide a default RequestMapping value.
#Controller
#RequestMapping (value="/property")
public class CommonPropertyController {
....
}
How would each application override this value if they want to use their own url pattern?
Looking at the source code I got an idea how to do it without having to go back to manual (pre-annotation) handler definition (which is also a way how to implement what you need).
Spring allows you to use property placeholder configurers in #RequestMapping values. So it is possible to use that fact and define #RequestMapping like:
#Controller
#RequestMapping("${routing.property.path}")
public class CommonPropertyController {
....
}
Then you can simply define PropertySourcesPlaceholderConfigurer with the right properties in your application context and you are good to go.
UPDATE You can also specify a default value using property placeholder if you want to have fallback mapping in case the property is not speciefied:
#RequestMapping("${routing.property.path:/property}")

How to pass arguments to controller from route in Symfony2

I'm working on a Symfony2 project and am trying to figure out how to pass parameters from the route configuration to the controller. I know I can configure default values in the route configuration, and retrieve the values in the controller using the appropriate var name in the function declaration, but that isn't exactly what I want.
My use case is the following. I have a standard method in my controller that I want to access from 2 or 3 different routes. Depending on which route is being called, I want to "configure" the method differently. I can accomplish this in a few ways:
In my controller, check the route name using `$this->container->get("request")->get("_route"), but that is ugly, and then I am hardcoded to the route name. Moves configuration to the controller, which should just be logic - not configuration.
Create a base controller class, and subclass each method for my different routes. Each subclassed method would then have the necessary configuration within the method. Cleaner soln than #1, but still "heavy" in the sense of having multiple classes for a simple need and still pushes configuration data into the business logic.
Put configuration data into the route configuration. In the controller, access the configuration data as required. Ideal solution, but don't know how.
I can use the route default array to specify my arguments, but then must make sure to use a regex to ensure that the params are not overridden at the URL level (security risk). This is functional, but still kinda cludgy and not a pretty hack.
I presume that there must a better way to do this, but I can't seem to figure it out. Is there a way to access the routing object from the controller, and access the different configuration parameters?
You can pull the actual route from the router service. Something like:
$routeName = $this->container->get("request")->get("_route");
$router = $this->container->get("router");
$route = $router->getRouteCollection()->get($routeName);
Not sure if this would be such a great design though. Consider passing a $configName to your controller method, adding a parameter with the same name in a config file then using getParameter to access it. That would eliminate the route stuff from the equation.
Something like:
zayso_arbiter_import:
pattern: /import
defaults: { _controller: ZaysoArbiterBundle:Import:index, configName: 'someConfigName' }
public function importAction(Request $request, $configName)

Resources