Attribute routes missing from routing table when using multiple namespaces - asp.net

I am using namespace versioning in my web api (4.0) project. I'm using Attribute routing to define custom routes for our actions. This works just fine when a controller only exists in one namespace/version.
But when a controller exists in both namespaces/versions, the attribute route is ignored completely. I am using the solution here to look at what routes are in the routing table. And it only contains one route, for my "single" controller. If I delete/comment out/change the name of the controller in V2, then suddenly the "double" controller's route appears in the routing table as well.
Is there any reason that an attribute route would be ignored because the same class/controller name exists in 2 different namespaces? Here is my code, broken down to the simplest failing example:
namespace API.v1
{
public class SingleController : ApiController
{
[HttpGet]
[Route("api/{version}/Single")]
public string Test()
{
return "Response";
}
}
}
This class/route works just fine. http://localhost:57560/api/v1/Single returns "Response".
namespace API.v1
{
public class DoubleController : ApiController
{
[HttpGet]
[Route("api/{version}/Double")]
public string Test()
{
return "Response";
}
}
}
namespace API.v2
{
public class DoubleController : ApiController
{
[HttpGet]
[Route("api/{version}/Double")]
public string Test()
{
return "Response";
}
}
}
These 2 classes fail; the attribute route is missing from the routing table, and http://localhost:57560/api/v1/Double returns 404, as well as http://localhost:57560/api/v2/Double. If I just change the name of the v2.Double class, then it works just fine.
In this example, I have no default routes set up at all; only the Attribute routing. When using default routes, the versioning and routing work just fine:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
This route works perfectly across multiple versions. But I need it to work with attribute routing.

I have been able to solve the issue using the solution here: http://abhinawblog.blogspot.com/2014/12/web-api-versioning-using.html. The problem appears to be related to the way that attribute routing differs from using the default routes table. It is required that the routes have unique names, and that the version numbers in the routes have custom constraints put on them.

In order to make the namespace versioning possible, you need to override the default behavior of routing. Check this MSDN Link for more details
Also check this sample code to see how it's done

Related

Url.Action returning incorrect URL for webapi action with Route attrubute

I have a problem with the behaviour of Url.Action();
I have a webapi where all controllers require explicit route prefix attribute and all actions require a route attribute.
I register my routes in the WebApiConfig.cs
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
config.MapHttpAttributeRoutes(constraintResolver);
I have currently commented out the line below, but (because) it did not change the incorrect behaviour:
//config.Routes.MapHttpRoute(name: "DefaultApi",
//routeTemplate: "api/v{version:apiVersion}/{controller}/{action}/{id}", defaults: new {id = RouteParameter.Optional});
My controllers look as follows:
[RoutePrefix("api/v{version:apiVersion}/programs")]
public class ProgramsController : ApiController
{
[HttpGet, Route("{telemetryKey}/versions/latest")]
public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
{
// serious business logic
}
}
I expect that '#Url.Action("GetLatestVersionInfo", "Programs", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })'
should return /api/v1/programs/43808405-afca-4abb-a92a-519489d62290/versions/latest
however, I get /Programs/GetLatestVersionInfo?telemetryKey=43808405-afca-4abb-a92a-519489d62290 instead. So, my routeprefix and route attributes are ignored.
Swagger correctly discovers my routes and I can validate that requests to the expected routes work OK - it's only the Url.Action() that is confused.
What can be wrong...?
Well, it seems there were a few things wrong.
Wrong helper:
I should be using the Url.HttpRouteUrl for generating API links from a razor view (Url.Link is for generating link from within API controllers)
Conflict with aspnet-api-versioning library
For some reason (perhaps a bug?) the prefix that I have on the controller (apiVersion variable) breaks the URL helper mechanism.
For now, I have ditched the aspnet-api-versioning library, but created an issue on their github repo, in case its a bug.
Since I really hate the idea of creating and maintaing magic strings, so I took the following approach - each controller has a public static class which contains const values for the route names:
[RoutePrefix("api/v1/developers")]
public class DevelopersController : ApiController
{
[HttpGet, Route("{developerId}/programs", Name = Routes.GetPrograms)]
public async Task<IEnumerable<Program>> GetPrograms(Guid developerId){}
public static class Routes
{
public const string GetPrograms = nameof(DevelopersController) +"."+ nameof(DevelopersController.GetPrograms);
}
}
Now that can be used from a razor controller in a simple and relatively safe manner:
#Url.HttpRouteUrl(DevelopersController.Routes.GetPrograms, new { developerId = /* uniquest of guids */})
A bit better than magic strings. I've also added a bunch of unit tests for controllers where I validate that each route is unique and proper and that the routes class only contains routes for the action it contains.
Try the following:
Name your route:
[HttpGet, Route("{telemetryKey}/versions/latest", Name="LatestVersionInfoRoute")]
public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
{
// serious business logic
}
Use Url.Link method:
#Url.Link("LatestVersionInfoRoute", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })

override default method route from plugin in nopcommerce in 3.8

I want to override search controller. When I try to install a plugin, I get an error exception what multiple type were found for the controller named Catalog.
Multiple types were found that match the controller named 'Catalog'. This can happen if the route that services this request ('AdvanceSearch') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
And my route priority is most(100).
public void RegisterRoutes(RouteCollection routes)
{
// Product Search,
routes.MapRoute("Plugin.Misc.Twigoh.Search",
"Search",
new { controller = "Catalog", action = "Search" },
new[] { "Nop.Plugin.Misc.Twigoh.Search.Controllers" });
}
public int Priority
{
get
{
return 100;
}
}
You can override your route like this:
When you override route, then you should use MapLocalizedRoute(not MapRoute) which is override localized route. Here you are trying to define route which is already define.
Here do not use MapRoute use MapLocalizedRoute in this way.
routes.MapLocalizedRoute("Plugin.Misc.Twigoh.Search",
"search/",
new { controller = "Catalog", action = "Search" },
new[] { "Nop.Plugin.Misc.Twigoh.Search.Controllers" });
Edit:
I want same route and functionality but default controller can't have
"/" search feature little bit different
/search is default route of product search, you can see in Nop.Web > Infrastructure > RouteProvider.cs
Hope this helps!
May be you rename your project so that the file name of the assembly changes, it's possible for you to have two versions.
So remove old .dll from bin folder and build your project.

AttributeRouting with IHttpControllerSelector - Api Versioning

I am trying to achieve api versioning using a CustomHttpControlSelector and AttributeRouting on asp.net webapi.
What i am trying to do is distinguish controller's versions by it's namespaces.
if a request is made to /api/v2/foo/bar
i want it to match
namespace Web.Controllers.Api.v2
{
[RoutePrefix("foo")]
public class LongerThanFooController : ApiController
{
[HttpGet]
[Route("bar")]
public string BarFunction()
{
return "foobar";
}
}
}
but as i see when i don't use full url on RoutePrefix (/api/v2/foo) attribute routing doesn't kick in and i get null when i call
request.GetRouteData().GetSubRoutes();
on my CustomHttpControlSelector. i don't want to Repeat /api/v2 on every controller.
if i decide to remove attributeRouting and use manual routes like
config.Routes.MapHttpRoute(
name: "DefaultVersionedApi",
routeTemplate: "api/v{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional, version = Config.LatestVersion }
);
i lose all flexibility of naming my controllers and functions.
is there a way to get out of this limbo?
note: for the CustomHttpControlSelector i modified code on http://aspnet.codeplex.com/SourceControl/changeset/view/dd207952fa86#Samples/WebApi/NamespaceControllerSelector/NamespaceHttpControllerSelector.cs
I realize this is bit of an old question now, but it can answered using the ASP.NET API Versioning package for ASP.NET Web API. In the latest 3.0 version, you can achieve your scenario by updating your configuration with:
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
configuration.AddApiVersioning(
options =>
{
options.Conventions.Add( new VersionByNamespaceConvention() );
options.AssumeDefaultVersionWhenUnspecified = true;
options.ApiVersionSelector = new CurrentImplementationApiVersionSelector( options );
} );
configuration.MapHttpAttributeRoutes( constraintResolver );
You should also remove your convention-based routes. Those are unnecessary if you are using attribute routing.
The setup for your controller simply changes to:
namespace Web.Controllers.Api.v2
{
[RoutePrefix("api")]
public class LongerThanFooController : ApiController
{
[HttpGet]
[Route("foo/bar")]
[Route("v{version:apiVersion}/foo/bar")]
public string BarFunction()
{
return "foobar";
}
}
}
The reason you need two route definitions is that you cannot have default values in the middle of the route template. Default values can only be used at the end. This also means that you need to allow no API version to be specified and indicate the way to determine which API version should be selected is to use the current implementation (e.g. latest). I'm personally not a fan of this approach because I think things should be predictable to clients, but this will achieve your desired result.

Complex routing in ASP.NET MVC5

I want to know the best approach to create my controllers structure.
Let's say I have several events and for each event I can have several devices.
My idea would be to have something like:
http://mydomain/event/1/device/4
So I can access the deviceId 4 (belonging to eventId 1).
Should I have two different controllers? One for Event and for Device or device info has to be in EventController?
How can I have this routing in my RouteConfig?
It's entirely up to you how you want to set this up. You can use separate controllers or the same controller. It doesn't matter.
As far as routing goes, if you're using standard MVC routing, you'll need to create a custom route for this:
routes.MapRoute(
"EventDevice",
"event/{eventId}/device/{deviceId}",
new { controller = "Event", action = "Device" }
);
Which would correspond with something like this:
public class EventController : Controller
{
public ActionResult Device(int eventId, int deviceId)
{
...
}
}
Just make sure you place that before the default route, so it will catch first. For more about custom routes see: http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/creating-custom-routes-cs
Alternatively, in MVC5+ you can use attribute routing, which makes defining custom routes much easier if you're doing a lot of stuff like this. In RouteConfig.cs, uncomment the line:
// routes.MapMvcAttributeRoutes();
Then, on your action define the route like:
[Route("event/{eventId}/device/{deviceId}")]
public ActionResult Device(int eventId, int deviceId)
{
...
You can also use [RoutePrefix] on your controller class to move part of the route to apply to the whole controller. For example:
[RoutePrefix("event")]
public class EventController : Controller
{
[Route("{eventId}/device/{deviceId}")]
public ActionResult Device(int eventId, int deviceId)
{
...
}
}
For more about attribute routing see: http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx

No type was found that matches the controller named

UPDATE: I unloaded the project and re-did it again and it worked.
I'm trying to create a WebApi, my build works fine and I get the error message "No type was found that matches the controller named" when I try to go to URI
This is how my webapiconfig looks like,
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
And below is my controller,
public class ClientController : ApiController
{
public List<Client> Get()
{
ICRepository repository = new CRepository(new CContext());
return repository.GetAllClients().ToList();
}
And this is how my global.asax.cs file looks like,
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
I'm trying to browse to the url "http://localhost:50662/api/client"
The complete error is as below,
This XML file does not appear to have any style information associated with it. The document tree is shown below.
No HTTP resource was found that matches the request URI 'http://localhost:50662/api/Client'.No type was found that matches the controller named 'Client'.
My question is different from what it's been marked as duplicate of, the question uses just controller which is MVC and mine is "ApiController" and I did read that before creating this. Also, the marked answer there is similar to what I have in here and my problem still exists.
Any help would be highly appreciated. I'm really lost.
I was getting the same error:
Error><Message>No HTTP resource was found that matches the request URI 'http://localhost:53569/api/values'.</Message><MessageDetail>No type was found that matches the controller named 'values'.</MessageDetail></Error>
By default when I created new controller in asp.net web forms application, it was like this:
ValuesController1 : ApiController
I just simply removed "1", and make it:
ValuesController : ApiController
I don't know if it is a bug or whatever, but it works for me.
you are calling WebApiConfig twice.
WebApiConfig.Register(GlobalConfiguration.Configuration);
GlobalConfiguration.Configure(WebApiConfig.Register);
Remove one of them. if you are using Web Api Version >= 2.0, then remove the first one, otherwise remove the second one.
Edit 1
I think your issue may be similar to this Answer
You might be missing some reference libraries, may not be directly related to Your Controller or API, but the way Web Api finds the Controller, is it go through all assemblies referenced by the application and search for types matching a predicate.
Although it isn't the cause of your problem, mine was actually caused by adding a new item of type class through Visual Studios context-menu and not a Controller.
The generated file had the following structure:
class foo
{
}
I extended the class to inherit from ApiController, but forgot to mark it as public
This is the correct version:
public class FooController : ApiController
{
public string Get(string name="Bar")
{
return $"Hello {name}"};
}
}
Right now I am facing a very similar issue. My error message was No type was found that matches the controller named 'odata'. It seems really strange to me, that it seems to be searching for a controller named ODataController or something like that. So I started looking for the real issue. In my case, it was the fact, that I had forgotten to include a public parameterless contructor to the controller class. I added it and the error went away immediately.

Resources