Web API One action works while a nearly identical one doesn't? - asp.net-web-api2

Error Message
{
"Message": "No HTTP resource was found that matches the request URI 'https://localhost:44390/api/UserRoutes?effectiveDate=3/29/2019'.",
"MessageDetail": "No type was found that matches the controller named 'UserRoutes'."
}
Working Action
public class AdvanceOrderApiController : BaseApiController
{
[HttpGet, Route("api/AdvanceOrders")]
public AdvanceOrdersResult GetAdvanceOrdersForRouteDate(string route, DateTime effectiveDate)
{
...
}
}
// JavaScript Usage: route="0100" and effectiveDate="03/29/2019".
API.SendRequest("/api/AdvanceOrders", "GET", { route: route, effectiveDate: effectiveDate }, success, failure);
Not Working Action
public class UserApiController : BaseApiController
{
[HttpGet, Route("api/UserRoutes")]
public IEnumerable<string> GetUserRoutes(DateTime effectiveDate)
{
...
}
}
// JavaScript Usage: effectiveDate="03/29/2019"
API.SendRequest("/api/UserRoutes", "GET", { effectiveDate: effectiveDate }, success, failure);
WebApiConfig
Not sure that it's relevant since I'm just declaring the route for each action, but...
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
...
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
API.SendRequest
This function is just a wrapper around jQuery's $.ajax function, nothing fancy. If the code is necessary I'll present it, but it works for all my other API calls so I can't imagine it would be the source of the problem.
These actions are nearly identical, why does one work and the other doesn't?

Passing the date in as Igor said in the comments presented an error message that revealed that I had an Api controller in my Permissions area that had a route also named api/UserRoutes.
Once I changed the name of the route the problem resolved.
I just wish it could have just told me this error message from the start.

Related

Attribute routing - every method, class or as needed?

Let's say you have an API controller. Some methods of this controller use the same routes:
[HttpPost] // /api/entities
public IHttpActionResult Add(Entity entity)
{
...
}
[HttpGet] // /api/entities
public IHttpActionResult FindAll()
{
...
}
[HttpGet] // /api/entities
public IHttpActionResult Find(String name)
{
...
}
[HttpGet] // /api/entities/id
public IHttpActionResult Find(Int32 id)
{
...
}
[HttpDelete] /api/entities/id
public IHttpActionResult Remove(Int32 id)
{
...
}
Do I apply RouteAttribute to all methods or only to two methods to cover for "api/entities" and "api/entities/id"? Or is it better to apply two RouteAttribute to the class itself?
If you have default routes specified in the configuration, that is:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
You don't have to apply routing attributes, don't forget that requests will be mapped to the actions not only by HTTP method, but by parameter type also, so there should be no problem.
Take a look at the "Action" section of the documentation.

Unable to call WebApi 2 method

I've added a webapi 2 controller to my project, inside api > LoginAPi as shown here:
Inside LoginApi I have the following:
[RoutePrefix("api/LoginApi")]
public class LoginApi : ApiController
{
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
}
Inside my global.asax file I have:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
Inside App_Start I have the following:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
I then put a break point inside the Get method within LoginAPI and run the project and type the following into the URL:
http://localhost:37495/api/LoginApi/4
But I get :
No HTTP resource was found that matches the request URI 'http://localhost:37495/api/LoginApi/4'.
So I thought OK let me specify the method name as so
http://localhost:37495/api/LoginApi/Get/4
This returns:
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
Now I've been looking at this for a while so maybe I've missed something obvious, but if someone can please tell me what I'm doing wrong I'd very much appreciate it.
The routeTemplate you have set up would work for convention-based routing except for the fact that Web API adds the string "Controller" when searching for the controller class (as per this article). You therefore need to rename your controller class LoginApiController in order for the convention-based routing to work.
For attribute-based routing, the addition of the RoutePrefix attribute should be combined with a Route attribute on your action. Try adding the following to your Get method in your controller:
[HttpGet]
[Route("{id}")]
And then navigate to http://localhost:37495/api/LoginApi/4.

Multiple parameters in a web api 2 get

I want to make a web api that is passed 4 parameters.
Here is my route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{email}/{firstname}/{lastname}/{source}"
);
Here is the method signature
public string GetId(string email, string firstname, string lastname, string source)
Here is the calling url
http://fakedomain.com/api/Contacts/GetId?email=user#domain.com&firstname=joe&lastname=shmoe&source=123
I get a 404 error.
If I set each parameter to optional in the route config, and set up each argument with a default value it gets called. However, each argument gets the default value and not the passed value.
I feel like I am close, what am I missing?
You don't need a special routing record to handle multiple parameters. The routing record you created would be looking for the following route
/api/controller/Dan#dan.com/Dan/FunnyLastName/TheCoffeeShop
but you are trying to pass in parameters, not specify a route.
with this routing record:
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional, action = "DefaultAction" });
the following GET endpoint:
public HttpResponseMessage Get(int requestId = 0, string userName = null, string departmentName = null, bool includeCompleted = false)
{
//code
}
could be hit as :
/api/controllername/?requestId=15&username=Dan
or
/api/controllername/?departmentName=SoftwareEngineering
or any other combination of the parameters (or no parameters since they have default values)
Since you have a "Named" action (GetId) instead of the default actions (GET,POST,PUT..), this complicates things a little bit and you would have to work out a custom route to handle the action name. The following is what I use for custom action names (id is required in this example)
config.Routes.MapHttpRoute("ActionRoute", "api/{controller}/{action}/{id}");
Your endpoint would have to explicitly accept one parameter with the name 'id'
public HttpResponseMessage LockRequest(int id, bool markCompleted)
{
//code
}
This endpoint would be hit at the following route:
/api/controllerName/LockRequest/id?markCompleted=true
Following the RESTful spec, it is better to stay away from custom action names when possible. Most of the time you can get away with the normal HTTP verbs and just use named actions to manipulate existing items (hence why ID is required in my example). For your code you could just have two GET endpoints, one that takes a specific ID to get the item, one that returns all items (including ids) based on "search parameters".
public HttpResponseMessage Get(int id)
public HttpResponseMessage Get(int requestId = 0, string userName = null, string departmentName = null, bool includeCompleted = false)
These would both be handled by the default routing record.
Ensure you have default api route setting in WebApiConfig.cs file.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ContactApi",
routeTemplate: "api/{controller}/{email}/{firstname}/{lastname}/{source}"
);
}
}
http://fakedomain.com/api/Contacts/GetId?email=user#domain.com&firstname=joe&lastname=shmoe&source=123
Note : I replaced fakedomain with localhost and it works... (localhost/api/Contacts/GetId?email=user#domain.com&firstname=joe&lastname=shmoe&source=123)
public class Parameters
{
public int Param1 { get; set; }
public string Param2 { get; set; }
}
and then in your controller method:
[ActionName("DoSomething")]
[HttpPost]
public IHttpActionResult DoSomething(Parameters myParameters)
{
var x = myParameters.Param1;
var y = myParameters.Param1;
//do something else..
}
And build a ajax call like this:
var request = {
Param1 : "1",
Param2 : "Mystring"
};
function callToMethodController(request) {
var deferred = $q.defer();
$http.post('api/object/DoSomething', request)
.success(function (data) {
deferred.resolve(data);
}).error(function (error) {
deferred.reject('There was an error.');
});
return deferred.promise;
}

ASP MVC default route is not applying for root site url

I'm having trouble getting ASP.net MVC to serve up the default controllers index view for the root site url http://mysite:8080/. All I get back is a 404 with The resource cannot be found. It works fine if I specify the full route path in the url : http://mysite:8080/Default/Index, however, I want the default route to apply even if the user doesn't specify the route path though. It seems that this should just work out of the gate. This is a fresh project from the VS2013 MVC 4 template.
I've tried both route mappings below and neither seems to work. How can this be achieved?
routes.MapRoute(
"Root",
"",
new { controller = "DefaultController", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "DefaultController", action = "Index", id = UrlParameter.Optional }
);
Here is a solution to this problem. It's disappointing that the default route's defaults do not apply for the root site url though.
routes.Add(new Route("", new SiteRootRouteHandler("~/Default/Index")));
public class SiteRootRouteHandler : IRouteHandler
{
private readonly string _redirectUrl;
public SiteRootRouteHandler(string redirectUrl)
{
_redirectUrl = redirectUrl;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new SiteRootHandler(_redirectUrl);
}
}
public class SiteRootHandler: IHttpHandler
{
private readonly string _redirectUrl;
public SiteRootHandler(string redirectUrl)
{
_redirectUrl = redirectUrl;
}
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
context.Response.RedirectPermanent(_redirectUrl);
}
}

How to make {controller}/{id}/{action} work in MVC4?

I've tried everything but looks like I'm just not getting it at all. My WebApiConfig.cs looks like this:
config.Routes.MapHttpRoute(
"Default",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
"AccountVerification",
"api/{controller}/{id}/{action}",
null,
new { controller = "Account" });
And my controller looks like this:
public class AccountController : ApiController {
public HttpResponseMessage GetByKey(Guid accountID) {
...
}
[HttpGet]
[ActionName("Verify")]
public HttpResponseMessage VerifyAccount(Guid accountID) {
...
}
}
These methods should get a hit with the following URLs:
GET /api/account - WORKS
GET /api/account/00000000-0000-0000-000000000001 - WORKS
GET /api/account/00000000-0000-0000-000000000001/verify - DOESNT WORK
I've tried a lot of things; I am definitely doing something wrong here...please help.
First, if you want to test with fake Guids, as well as having optional Guid parameters, they must be Nullable parameters (fake guids will be deserialized as null) :
public class AccountController : ApiController
{
public HttpResponseMessage GetByKey(Guid? accountID)
{
throw new Exception("GetByKey " + (accountID.HasValue ? accountID.ToString() : "NULL"));
}
[System.Web.Http.HttpGet]
[System.Web.Http.ActionName("Verify")]
public HttpResponseMessage VerifyAccount(Guid? accountID)
{
throw new Exception("VerifyAccount "+(accountID.HasValue?accountID.ToString():"NULL"));
}
}
then, your mapping should :
use the most specific route first
use the correct parameters names
use the correct defaults for actions
config.Routes.MapHttpRoute(
"AccountVerification",
"api/{controller}/{accountID}/{action}"
);
config.Routes.MapHttpRoute(
"Default",
"api/{controller}/{accountID}",
defaults: new { Controller="Account", action = "GetByKey", accountID = RouteParameter.Optional }
);
GET /api/account/00000000-0000-0000-000000000001/verify is matching the first route in your routing collection. Therefore, it never inspects the second route to see if it matches. Make "api/{controller}/{id}/{action}" the first route in your collection and it should work correctly.