16

Please excuse my ignorance in this area. I have read many threads and still cannot get my routing correct.

I have a ProductsController like this:

public class ProductsController : ApiController
{
    [ActionName("GetListOfStudents")]
    public static List<Structures.StudentInfo> GetListOfStudents(string Username, string Password)
    {
        List<Structures.StudentInfo> si = StudentFunctions.GetListOfStudents(Username, Password);
        return si;
    }
}

I have a console test program where I have defined the route:

config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/products/GetListOfStudents",
defaults: new { controller = "products", action = "GetListOfStudents" });

But when I run call

GET http://localhost:8080/api/Products/GetListOfStudents

I get the error message:

MessageDetail=No action was found on the controller 'Products' that matches the name 'GetListOfStudents'.

I have been pulling my hair out and cannot work out what the correct route should be.

Would any kind person care to help me out?

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
Trevor Daniel
  • 3,785
  • 12
  • 53
  • 89
  • 1
    You should never send the password! This is dangerous because eavesdropperse can read it. And if you pass along a deep link, you're giving away your password too. See my **comment** below the [accepted answer](http://stackoverflow.com/a/22916092/1016343) to see how to solve this issue. – Matt Dec 22 '15 at 10:01
  • 2
    Passing the password is fine if it's over https. How do you think login forms work? – Kevin Doyon Dec 14 '17 at 18:56
  • 1
    @Kevin (Good) login forms don't pass the password in the URL, but rather they POST the password as part of the body. There are several problems with sending the password in the URL (even over https), including keeping the user's password in the unsecured history of the browser, inadvertently sharing the user's password if they share the page, and having the password be strewn about server logs and caches, among others. – Seafish Jan 09 '18 at 15:35
  • 1
    @Seafish Yeah I was to hasty with my reply, didn't really notice it was passed in the URL - I just read the comment "never send the password" and was confused. Thanks for pointing out my error. – Kevin Doyon Jan 09 '18 at 15:53
  • Does this answer your question? [WebAPI No action was found on the controller](https://stackoverflow.com/questions/14481592/webapi-no-action-was-found-on-the-controller) – Michael Freidgeim May 24 '21 at 07:55

8 Answers8

13

Ok- thanks for the help peeps!

This what I did to get it working:

  1. Removed the "static" from the GetListOfStudents function.
  2. Added the route below.
config.Routes.MapHttpRoute(
  name: "ApiByAction",
  routeTemplate: "api/products/GetListOfStudents/{username}/{password}",
  defaults: new { controller = "products", action = "GetListOfStudents" }
);

Thanks everyone for your help!

SoftSan
  • 2,482
  • 3
  • 23
  • 54
Trevor Daniel
  • 3,785
  • 12
  • 53
  • 89
  • 18
    Just keep in mind that it is considered bad practice from security standpoint to pass password as part of URI... – LB2 Apr 07 '14 at 15:02
  • 1
    @LB2 that's my next problem!! - well spotted. need to work out how to send those in the body don't I? – Trevor Daniel Apr 07 '14 at 15:05
  • After researching for 1 day, I must admit that routing in Web API 1.0 ASP .NET is a complete mess. What if there is the need for several optional parameters? How can it tell from an url like doABC/x/y/z/...? – Hoàng Long Oct 28 '15 at 03:29
  • The problem here lies in how they organize the route table: they use 2 different API to register MVC routing and Web Api routing, but they record in the same table!! That leads to unexpected conflicts between the two, especially in case like this http://stackoverflow.com/questions/22401403/404-error-after-adding-web-api-to-an-existing-mvc-web-application – Hoàng Long Oct 28 '15 at 03:33
  • Instead of passing user credentials via URL, which should not be done as mentioned by LB2, consider using Windows Integrated Security. Then you can remove the username and password parameters completely and use `System.Security.Principal.WindowsIdentity.GetCurrent().Name` to get the authenticated user in the controller method. You can then assume the user is authenticated by Windows and IIS so there is no need to transmit the password to the controller. – Matt Dec 22 '15 at 09:28
11

When registering your global api access point, you should tell the config which route to use in the following manner:

config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/{controller}/{action}
defaults: new { controller = "products", action = "GetListOfStudents" });

In this sample you explicitly tell the controller it should only go to the "products" controller, you can make it generic without specifying the control or the action, just omit the defaults, like this:

config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/{controller}/{action}

That should do the job :)

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
6

Your GetListOfStudents action requires two parameters, username and password. Yet, the route definition contains neither specification in the route template where the values for those parameters should come from, nor specification for those parameter defaults in the defaults: parameter definition.

So when request comes in, routing is able to find your controller, but it is unable to find the action that it can call with the request and route context that it has because it has no information for the username and password parameters.

LB2
  • 4,802
  • 19
  • 35
6

The most important is: ASP.Net's mvc not only seek action by name, also it will check method's signature, only the method is non-static, name matches and parameters matches, the action will be executed.

for your case, there are two ways to correct it. one way is declare default value, mvc will use default value when parametr not found.

public List<Structures.StudentInfo> GetListOfStudents(string Username = null, string Password = null)
{
    List<Structures.StudentInfo> si = StudentFunctions.GetListOfStudents(Username, Password);
    return si;
}

the second way is use override

public List<Structures.StudentInfo> GetListOfStudents()
{
   return GetListOfStudents(null, null);
}

public List<Structures.StudentInfo> GetListOfStudents(string Username, string Password)
{
    List<Structures.StudentInfo> si = StudentFunctions.GetListOfStudents(Username, Password);
    return si;
}
dexiang
  • 1,345
  • 16
  • 23
0

I had this problem and solved it by including the verb as part of the action (i.e. GetThis, GetThat) and manually creating routes. I was attempting to create routes using attributes, but that did not work. This SO question may be the answer as to why the attributes aren't working, I haven't gotten that straightened out yet. As an additional note for anyone else having the same problem, when debugging it locally, IE was crashing when the "no action found" xml was returned. Once I gave up and switched to Chrome, the message detail was returned, and it was obvious that my controller at least was being found, it was just a matter of getting the action to work...

Community
  • 1
  • 1
jmoreno
  • 12,752
  • 4
  • 60
  • 91
0

If you want to call GetListOfStudents method without parameter you must set default value for parameter. such as GetListOfStudents(string Username=null, string Password=null) Otherwise you must call method with Parameters. GET http://localhost:8080/api/Products/GetListOfStudents/Username/Password

0

One issue could be the order of the route declarations in your WebApiConfig.cs file. Have a look here about the precedence of routes. If you have two routes with the same amount of parameters, you may need to reorder the routes, or -- depending on how specific the route is -- hardcode the controller or action name

Adam Hey
  • 1,512
  • 1
  • 20
  • 24
-5

When sending, encode the password with base64. Then when you about to use it decode it.

byte[] numArray = Convert.FromBase64String(password);
string Pass = Encoding.UTF8.GetString(numArray); 

List<Structures.StudentInfo> si = StudentFunctions.GetListOfStudents(Username, Pass);

Works fine for me.

dan
  • 23
  • 7
  • 1
    But every eavesdropper can get your password. See my comment above in the [accepted answer](http://stackoverflow.com/a/22916092/1016343) to see how to solve this. – Matt Dec 22 '15 at 09:29