Proper routing configuration for multiple apis sharing same endpoint - c#

I have a working API with a different endpoint -- a different controller -- for each type of XML post data that I expect to receive. Now, the client wants us to use the same endpoint for everything. The only determination of direction is the content of the XML data -- basically by the name of the root element.
I'd like to be able to leverage as much of my existing work as possible, so I am trying to add a new controller "Router" which does exactly that -- redirects to the proper route based on the content received. I've tried some things but can't seem to get any traction.
If there is a better way to handle this, I would be open to hearing about it.
// WebApiConfig.cs
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
//routeTemplate: "Router",
constraints: null,
defaults: new { id = RouteParameter.Optional }
);
//In RouteConfig.cs -- is this redundant to specific MapRoute and MapHttpRoute?
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(name: "First", url: "First/{action}/{id}" );
routes.MapRoute(name: "Second", url: "Second/{action}/{id}" );
routes.MapRoute(name: "Third", url: "Third/{action}/{id}" );
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home",
action = "Index",
id = UrlParameter.Optional }
);
But at any rate, I do have my APi traffic hitting the RouterController
(and I will admit I'm not completely clear why it's working) but once
there, how do I go about rerouting based on the XML data posted?
public RouterController()
{
log.Debug("Inside RouterController");
}
public IHttpActionResult Post(object postdata)
{
// how to determine the postdata contents / XML structure to get the
// root element and then redirect to the "true" controller?
}
Several questions here: First, why the (apparent) redundancy in WebApiConfig.cs route definition and RouteConfig.cs routing? Second, what's the best way to recognize traffic by its content and route appropriately?
Thanks for your assistance. I'll admit to running fairly blind here, so any assistance is appreciated.

I think it's not a good idea to have some xml parsing logic in your routes (if it's even possible).
Based on you description I guess that you have many controller methods and models for them. You can keep them almost unchanged, maybe only making methods private.
public class First
{
public string Name { get; set; }
}
public class Second
{
public string Name { get; set; }
}
private void HandleFirst(First model)
{
// Your existing code
}
private void HandleSecond(Second second)
{
// Your existing code
}
My suggestion is to have one controller method (how your client wants) that will do the following things:
Accept raw request data
Parse XML and get know the type
Create model by deserializing XML
Call needed method
That is how it might look:
public void Post(HttpRequestMessage request)
{
using (var xmlReader = XmlReader.Create(request.Content.ReadAsStreamAsync().Result))
{
xmlReader.MoveToContent();
switch (xmlReader.Name)
{
case "First":
HandleFirst(Deserialize<First>(xmlReader));
break;
case "Second":
HandleSecond(Deserialize<Second>(xmlReader));
break;
default:
throw new NotSupportedException();
}
}
}
private T Deserialize<T>(XmlReader xmlReader)
{
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(xmlReader);
}
In this case you can keep you routing configuration very simple. All you need is to use default one. You might also take a closer look at attribute routing that was introduced in Web.API 2.

Related

Create Route for a specific URL without changing the URL with MVC

I have a MVC Web Application that runs on www.domain.com and I need to configure a different URL binding for another domain www.domain2.com for the same web application.
The new domain www.domain2.com will have to return a specific Controller Action View like /Category/Cars:
routes.MapRoute(
name: "www.domain2.com",
url: "www.domain2.com",
defaults: new { controller = "Category", action = "Cars", id = UrlParameter.Optional }
);
How can I achieve this without changing the URL, so the visitor inserts the url www.domain2.com and receives the view www.domain.com/category/cars but the url remains www.domain2.com?
EDIT:
I have tried this approach but it's not working:
routes.MapRoute(
"Catchdomain2",
"{www.domain2.com}",
new { controller = "Category", action = "Cars" }
);
Domains are normally not part of routes, which is why your examples don't work. To make routes that work only on specific domains you have to customize routing.
By default, all of the routes in your route configuration will be available on all domains that can reach the web site.
The simplest solution for this is to create a custom route constraint and use it to control the domains that a specific URL will match.
DomainConstraint
public class DomainConstraint : IRouteConstraint
{
private readonly string[] domains;
public DomainConstraint(params string[] domains)
{
this.domains = domains ?? throw new ArgumentNullException(nameof(domains));
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
string domain =
#if DEBUG
// A domain specified as a query parameter takes precedence
// over the hostname (in debug compile only).
// This allows for testing without configuring IIS with a
// static IP or editing the local hosts file.
httpContext.Request.QueryString["domain"];
#else
null;
#endif
if (string.IsNullOrEmpty(domain))
domain = httpContext.Request.Headers["HOST"];
return domains.Contains(domain);
}
}
Usage
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// This ignores Category/Cars for www.domain.com and www.foo.com
routes.IgnoreRoute("Category/Cars", new { _ = new DomainConstraint("www.domain.com", "www.foo.com") });
// Matches www.domain2.com/ and sends it to CategoryController.Cars
routes.MapRoute(
name: "HomePageDomain2",
url: "",
defaults: new { controller = "Category", action = "Cars" },
constraints: new { _ = new DomainConstraint("www.domain2.com") }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
// This constraint allows the route to work either
// on "www.domain.com" or "www.domain2.com" (excluding any other domain)
constraints: new { _ = new DomainConstraint("www.domain.com", "www.domain2.com") }
);
}
}
If you fire this up in a new project in Visual Studio, you will notice it shows an error. This is because localhost:<port> is not a configured domain. However, if you navigate to:
/?domain=www.domain.com
You will see the home page.
This is because for the debug build only, it allows you to override the "local" domain name for testing purposes. You can configure your local IIS server to use a local static IP address (added to your network card) and add a local hosts file entry to test it locally without the query string parameter.
Note that when doing a "Release" build, there is no way to test using a query string parameter, as that would open up a potential security vulnerability.
If you use the URL:
/?domain=www.domain2.com
it will run the CategoryController.Cars action method (if one exists).
Note that since the Default route covers a wide range of URLs, most of the site will be available to both www.domain.com and www.domain2.com. For example, you will be able to reach the About page both at:
/Home/About?domain=www.domain.com
/Home/About?domain=www.domain2.com
You can use the IgnoreRoute extension method to block URLs that you don't want (and it accepts route constraints, so this solution will work there, too).
This solution will work if you largely want to share functionality between domains. If you would rather have 2 domains in one web site, but make them act like separate web sites, it would be easier to manage if you use an Area for each "web site" in your project by using the above route constraint for the Area routes.
public class Domain2AreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Domain2";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
name: "Domain2_default",
url: "{controller}/{action}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional },
constraints: new { _ = DomainConstraint("www.domain2.com") }
);
}
}
The above configuration would make every URL (that is 0, 1, 2, or 3 segments long) for www.domain2.com route to a controller in the Domain2 Area.
in the default action of the application make sure that the url is the one of the second domain, then return the method that needs. something like:
public ActionResult Index()
{
if (Request.Url.Host.Equals("domain2"))
return AnotherAction();
}
Agreed with the answer above.
If you want more beautiful implementation - try action filters.
Sample of action filters usage from there.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = (SomeControllerBase) filterContext.Controller;
filterContext.Result = controller.RedirectToAction("index", "home");
}
Sample of getting the URL inside action filter from there.
var url = filterContext.HttpContext.Request.Url;
Put the things together and have fun :)

URL Routing MVC 5 Asp.net

I know about routing in MVC. I added a new MapRoute under RegisterRoute method in RouteConfig.cs class and successfully called my function with the URL http://localhost:53363/package/PackageDetail/mypackage/5.
However, my question is do i have to add different Map Routes for every method or is there any better way ? Like in PackageController class you can see i have two methods one methods takes PackageId and PackageName and the other takes only PackageId. So do i have to register different Map Routes or not ?
RouteConfig
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Package",
url: "Package/PackageDetail/{packageName}/{packageId}",
defaults: new { controller = "Package", action = "PackageDetail", packageName = UrlParameter.Optional, packageId = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
PackageController.cs :
[HttpGet]
public ActionResult PackageListing(int packageId = 0)
{
return View();
}
[HttpGet]
public ActionResult PackageDetail(string packageName = "", int packageId = 0)
{
return View();
}
Despite the fact that Muhammed's answer will work, it is very repetitive, especially if you're using the same style of routes for multiple types.
There are a few things to consider before deciding upon a single approach to routing. The main one is why have both the name and ID in the route? If you want a more SEO friendly URL structure, don't bother with the ID at all.
If you have multiple products within the same type that have identical names, then there's no point in including the name as part of the URL since that won't get a user where they want to go by itself. In that event, just leave the original route.
However, if you have several different controllers (or actions) with a similar name/id structure for the routes, you'll be far better served with making your custom route more generic.
routes.MapRoute(
name: "NameAndId",
url: "{controller}/{action}/{name}/{id:int}",
defaults: new
{
controller = "Package",
action = "PackageDetail",
name = UrlParameter.Optional,
id = UrlParameter.Optional
});
Keep this above the default route, and this will redirect not just
/Package/PackageDetail/Deluxe/5
but also allow you to have stuff like this:
/Meals/Menu/Dinner/3
That may not necessarily be applicable for you in this project, but since you're learning MVC, this is a good skill to pick up. The more generic you're able to maintain your route definitions, the less you'll need to repeat it. Of course, if this is a one-time special route, there's nothing wrong with using the attributes.
Also to answer your final question, you do not need to create another custom route, because your PackageListing method will be routed through the default route that was provided when you created your project.
If you want to override default route url and generate custom url then you need to register route in route config file.
You can pass Package name and package Id as below.
http://sitename/Package/PackageListing?packageId=1
http://sitename/Package/PackageDetail?packageName=packagename&packageId=1
but if you want to generate URL as below than you need to add route in route.config file.
http://sitename/Package/PackageListing/1
http://sitename/Package/PackageDetail/packageName/1

How to upload "text/plain" string in Web API

I'm desperately trying to upload a plain (text/plain) string in Web API and my Controller is simply refusing to do the routing correctly. All I'm getting is a 404 (Not Found) HTTP error (I was so happy all the "Get" methods were working out of the box :-( )
Here are my routes:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "IntegraApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ServComAdminApi",
routeTemplate: "admin/{action}",
defaults: new { controller = "admin", action = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ServComApi",
routeTemplate: "{id}/{action}",
constraints: new { id = #"\d+" },
defaults: new { controller = "servcom", action = "info" }
);
// Get rid of XML responses
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
}
The pertinent route is the last one that maps to the "servcom" controller. I'm porting a custom written HTTP server that followed that routing pattern ("id/action"). It is working for all "get" methods. I can get a list of "users" for the equipment with id "10" by just using: http://localhost:49410/10/users... But it's not working when I try to upload "string data". This is the pertinent method on my controller:
public class ServcomController : ApiController
{
[HttpPut, HttpPost]
public string Vis(long idTerm)
{
return "PUT/POST Vis for: " + idTerm;
}
}
It's stripped down to the bare minimum. I'm not even reading the actual string data. Since I won't be sending form encoded data, just a plain string (this API is currently used under 3G so any byte savings are great as we need to minimize data-plan usage), I didn't use [FromBody] attributes as it won't work at all.
This is the client code used to test it:
using System;
using System.Net;
namespace TestPutPostString
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Sending 'HELLO WORLD!'");
var wc = new WebClient();
var res = wc.UploadString("http://localhost:49410/101/vis", "PUT", "HELLO WORLD!");
Console.WriteLine("Response: " + res);
Console.ReadKey(true);
}
}
}
It fails with: "The remote server returned an error: (404) Not Found."
The above code works perfectly with my hand-written HTTP server, which uses TcpServer and was written from scratch for the specific needs of this API (I'm migrating it to Web.API after 2 years of use since it will be easier to host it on Azure this way). Using this same sample program with hand-written HTTP stack, the body of the message, when hitting the server, does indeed have "HELLO WORLD!". Why isn't it being routed to the ServComController.Vis() method?
Am I sending it wrong? Does WebClient.UploadString() works in other unpredictable ways? Or is my controller method signature wrong? Is the route wrong? What am I missing!?!? :-)
IF you want to avoid the pain of Action Selection you could change your signature to be,
public class ServcomController : ApiController
{
[HttpPut, HttpPost]
public HttpResponseMessage Vis(HttpRequestMessage request)
{
var idTerm = request.GetRouteData().Values["idTerm"];
var body = request.Content.ReadAsStringAsync().Result;
return "PUT/POST Vis for: " + idTerm;
}
}
Darrel Miller provided quite a nice solution which is not the actual answer, but I guess will end up with lots of more upvotes than my own answer to my own question. I wasn't aware we could "avoid the pain of Action Selection" by using using a parameter of the type HttpRequestMessage ! This is quite nice and I can see a lot of scenarios where it will be really, really useful.
But that's not the only reason his answer will get a lot more upvotes: this is because my question was silly! I did a very stupid mistake.
This is the route:
config.Routes.MapHttpRoute(
name: "ServComApi",
routeTemplate: "{id}/{action}",
constraints: new { id = #"\d+" },
defaults: new { controller = "servcom", action = "info" }
);
This is the Method I was trying to map the route to:
public class ServcomController : ApiController
{
[HttpPut, HttpPost]
public string Vis(long idTerm)
{
return "PUT/POST Vis for: " + idTerm;
}
}
Of course it won't match! In the route I've used "id" as the parameter, and in this method I've used "idTerm" as the parameter, so there was really no action on the ServComController which matched the route!
The solution was simply to change it to:
public class ServcomController : ApiController
{
[HttpPut, HttpPost]
public string Vis(long id) // << "idTerm" to "id"
{
return "PUT/POST Vis for: " + idTerm;
}
}
And since the body is not form encoded, I won't be able to use [FromBody]string data, because it expects form data. The final solution will be to read the body from the request using a good, old StreamReader for that. I'll update the solution with the full code later today.

Custom route with action in the controller

In my RouteConfig I have:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("ApiController", "api/{controller}");
routes.MapRoute("ApiControllerAndIntegerId", "api/{controller}/{id}", null, new { id = #"^\d+$" });
routes.MapRoute("ApiControllerActions", "api/{controller}/{action}");
}
I then have a LookupController.
public class LookupsController : ApiController
{
public string Get()
{
return "Default Get";
}
// /api/lookups/custom
[ActionName("custom")]
public string CustomLookup()
{
return "Hello, World";
}
}
If I navigate to /api/lookups/custom I still get Default Get instead of Hello, World. What am I doing wrong?
Edit
I have tried the different variants of MapHttpRoute and MapRoute. Neither seem to work.
MVC Routes are checked in the order you define them. You're having trouble with /api/lookups/custom, which is supposed to hit "api/{controller}/{action}". However, before that you have "api/{controller}/{id}" and "api/{controller}", so it looks like one of those is catching it. My guess would be that it's trying to parse "custom" as {id} in the first of those two. I notice you're not specifying default controllers or actions in your routes; it's possible you took them out before posting to take less space, but if you haven't specified defaults I recommend you do so - whether or not it's capable of picking its own defaults, it can be useful to know what your program's "I don't know what to do" behaviour is.
Reverse the order you're specifying those three routes in. When in doubt, put your longest and most specific routes up top, and then get gradually more vague. Putting more generic routes up top tends to result in those routes catching things that should have gone to more specific route definitions further down.
You are using the wrong config route for Web Api, it should be MapHttpRoute instead of MapRoute:
var configuration = GlobalConfiguration.Configuration;
configuration.Routes.MapHttpRoute(
name: "CustomizedApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Please note that: put the route "api/{controller}/{action}/{id}" on top of "api/{controller}/{id}"
Also, mark [HttpGet] in your CustomLookup to support GET method for this action:
// /api/lookups/get
public string Get()
{
return "Default Get";
}
// /api/lookups/custom
[ActionName("custom")]
[HttpGet]
public string CustomLookup()
{
return "Hello, World";
}
it will work

Multiple actions were found that match the request in Web Api

I keep getting this error when I try to have 2 "Get" methods
Multiple actions were found that match the request: webapi
I been looking around at the other similar questions about this on stack but I don't get it.
I have 2 different names and using the "HttpGet" attribute
[HttpGet]
public HttpResponseMessage Summary(MyVm vm)
{
return null;
}
[HttpGet]
public HttpResponseMessage FullDetails()
{
return null;
}
Your route map is probably something like this in WebApiConfig.cs:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
But in order to have multiple actions with the same http method you need to provide webapi with more information via the route like so:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
Notice that the routeTemplate now includes an action. Lots more info here: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Update:
Alright, now that I think I understand what you are after here is another take at this:
Perhaps you don't need the action url parameter and should describe the contents that you are after in another way. Since you are saying that the methods are returning data from the same entity then just let the parameters do the describing for you.
For example your two methods could be turned into:
public HttpResponseMessage Get()
{
return null;
}
public HttpResponseMessage Get(MyVm vm)
{
return null;
}
What kind of data are you passing in the MyVm object? If you are able to just pass variables through the URI, I would suggest going that route. Otherwise, you'll need to send the object in the body of the request and that isn't very HTTP of you when doing a GET (it works though, just use [FromBody] infront of MyVm).
Hopefully this illustrates that you can have multiple GET methods in a single controller without using the action name or even the [HttpGet] attribute.
Update as of Web API 2.
With this API config in your WebApiConfig.cs file:
public static void Register(HttpConfiguration config)
{
//// Web API routes
config.MapHttpAttributeRoutes(); //Don't miss this
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);
}
You can route our controller like this:
[Route("api/ControllerName/Summary")]
[HttpGet]
public HttpResponseMessage Summary(MyVm vm)
{
return null;
}
[Route("api/ControllerName/FullDetails")]
[HttpGet]
public HttpResponseMessage FullDetails()
{
return null;
}
Where ControllerName is the name of your controller (without "controller"). This will allow you to get each action with the route detailed above.
For further reading: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
In Web API (by default) methods are chosen based on a combination of HTTP method and route values.
MyVm looks like a complex object, read by formatter from the body so you have two identical methods in terms of route data (since neither of them has any parameters from the route) - which makes it impossible for the dispatcher (IHttpActionSelector) to match the appropriate one.
You need to differ them by either querystring or route parameter to resolve ambiguity.
After a lot of searching the web and trying to find the most suitable form for routing map
if have found the following
config.Routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id =RouteParameter.Optional }, new { id = #"\d+" });
config.Routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
These mapping applying to both action name mapping and basic http convention (GET,POST,PUT,DELETE)
This is the answer for everyone who knows everything is correct and has checked 50 times.....
Make sure you are not repeatedly looking at RouteConfig.cs.
The file you want to edit is named WebApiConfig.cs
Also, it should probably look exactly like this:
using System.Web.Http;
namespace My.Epic.Website
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
// api/Country/WithStates
config.Routes.MapHttpRoute(
name: "ControllerAndActionOnly",
routeTemplate: "api/{controller}/{action}",
defaults: new { },
constraints: new { action = #"^[a-zA-Z]+([\s][a-zA-Z]+)*$" });
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
I could have saved myself about 3 hours.
It might be possible that your webmethods are being resolved to the same url. Have a look at the following link :-
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
So, you might need to add your methodname to your routing table.
Without using actions the options would be:
move one of the methods to a different controller, so that they don't clash.
use just one method that takes the param, and if it's null call the other method from your code.
This solution worked for me.
Please place Route2 first in WebApiConfig. Also Add HttpGet and HttpPost before each method and include controller name and method name in the url.
WebApiConfig =>
config.Routes.MapHttpRoute(
name: "MapByAction",
routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
Controller =>
public class ValuesController : ApiController
{
[HttpPost]
public string GetCustomer([FromBody] RequestModel req)
{
return "Customer";
}
[HttpPost]
public string GetCustomerList([FromBody] RequestModel req)
{
return "Customer List";
}
}
Url =>
http://localhost:7050/api/Values/GetCustomer
http://localhost:7050/api/Values/GetCustomerList
I found that that when I have two Get methods, one parameterless and one with a complex type as a parameter that I got the same error. I solved this by adding a dummy parameter of type int, named Id, as my first parameter, followed by my complex type parameter. I then added the complex type parameter to the route template. The following worked for me.
First get:
public IEnumerable<SearchItem> Get()
{
...
}
Second get:
public IEnumerable<SearchItem> Get(int id, [FromUri] List<string> layers)
{
...
}
WebApiConfig:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{layers}",
defaults: new { id = RouteParameter.Optional, layers RouteParameter.Optional }
);
It is possible due to using MVC controller instead of Web API controller.
Check the namespace in Web API controller it should be as following
using System.Net;
using System.Net.Http;
using System.Web.Http;
If the namespace are as following then it is give above error in web api controller method calling
using System.Web;
using System.Web.Mvc;
Please check you have two methods which has the different name and same parameters.
If so please delete any of the method and try.
I've stumbled upon this problem while trying to augment my WebAPI controllers with extra actions.
Assume you would have
public IEnumerable<string> Get()
{
return this.Repository.GetAll();
}
[HttpGet]
public void ReSeed()
{
// Your custom action here
}
There are now two methods that satisfy the request for /api/controller which triggers the problem described by TS.
I didn't want to add "dummy" parameters to my additional actions so I looked into default actions and came up with:
[ActionName("builtin")]
public IEnumerable<string> Get()
{
return this.Repository.GetAll();
}
for the first method in combination with the "dual" route binding:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "builtin", id = RouteParameter.Optional },
constraints: new { id = #"\d+" });
config.Routes.MapHttpRoute(
name: "CustomActionApi",
routeTemplate: "api/{controller}/{action}");
Note that even though there is no "action" parameter in the first route template apparently you can still configure a default action allowing us to separate the routing of the "normal" WebAPI calls and the calls to the extra action.
In my Case Everything was right
1) Web Config was configured properly
2) Route prefix and Route attributes were proper
Still i was getting the error. In my Case "Route" attribute (by pressing F12) was point to System.Web.MVc but not System.Web.Http which caused the issue.
You can add [Route("api/[controller]/[action]")] to your controller class.
[Route("api/[controller]/[action]")]
[ApiController]
public class MySuperController : ControllerBase
{
...
}
I know it is an old question, but sometimes, when you use service resources like from AngularJS to connect to WebAPI, make sure you are using the correct route, other wise this error happens.
Make sure you do NOT decorate your Controller methods for the default GET|PUT|POST|DELETE actions with [HttpPost/Put/Get/Delete] attribute. I had added this attibute to my vanilla Post controller action and it caused a 404.
Hope this helps someone as it can be very frustrating and bring progress to a halt.
For example => TestController
[HttpGet]
public string TestMethod(int arg0)
{
return "";
}
[HttpGet]
public string TestMethod2(string arg0)
{
return "";
}
[HttpGet]
public string TestMethod3(int arg0,string arg1)
{
return "";
}
If you can only change WebApiConfig.cs file.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/",
defaults: null
);
Thats it :)
And Result :
Have you tried like:
[HttpGet("Summary")]
public HttpResponseMessage Summary(MyVm vm)
{
return null;
}
[HttpGet("FullDetails")]
public HttpResponseMessage FullDetails()
{
return null;
}

Categories