Multiple parameters in a web api 2 get - asp.net-web-api2

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;
}

Related

Web API One action works while a nearly identical one doesn't?

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.

Route Attribute not working in Web API 2

I have two GET methods on my API controller. When I attempt to call the GetByCompanyId method, which I have decorated with the Route Attribute, the request instead is being routed to the GetById method. Below are the relevant code files.
global.ascx.cs
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
webApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.EnableCors();
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
route.config
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
CompanyFunctionsController.cs
public IHttpActionResult GetById(int id)
{
var companyFunction = this._service.GetByKey(new object[] { id });
if (companyFunction != null)
{
var companyFunctionDto = Mapper.Map<CompanyFunctionDto>(companyFunction);
return Ok(companyFunctionDto);
}
return NotFound();
}
[Route("CompanyFunctions/GetByCompanyId", Name = "GetByCompanyId")]
[HttpGet]
public IEnumerable<CompanyFunctionDto> GetByCompanyId(int id)
{
var collection = this._service.GetAll().ToList().Where(x => x.CompanyId == id);
IEnumerable<CompanyFunctionDto> collectCompanyFunctionDtos = Mapper.Map<IEnumerable<CompanyFunctionDto>>(collection);
return collectCompanyFunctionDtos;
}
My HTTP request:
http://localhost:1317/api/CompanyFunctions/GetByCompanyId?id=1
If you want to have a route that starts with api like http://localhost:1317/api/CompanyFunctions/GetByCompanyId?id=1 then you must use the string api in your route attribute that you want it to go to.
[Route("api/CompanyFunctions/GetByCompanyId", Name = "GetByCompanyId")]
Otherwise it will only match based on the http verb (Get in this case).
Alternatively you can decorate the web api controller with the [RoutePrefix("api/CompanyFunctions")] attribute as well and change your Route attribute to [Route("GetByCompanyId", Name = "GetByCompanyId")]
Web API 2 supports a new type of routing, called attribute routing. As the name implies, attribute routing uses attributes to define routes. Attribute routing gives you more control over the URIs in your web API
You might use Attribute Routing in Web Api to solve your problems. Your Controller Action should be like this,
[Route("CompanyFunctions/GetByCompanyId/{companyId}"]
public IEnumerable<CompanyFunctionDto> GetByCompanyId(int companyId)
{
var collection = this._service.GetAll().ToList().Where(x => x.CompanyId == companyId);
IEnumerable<CompanyFunctionDto> collectCompanyFunctionDtos = Mapper.Map<IEnumerable<CompanyFunctionDto>>(collection);
return collectCompanyFunctionDtos;
}
and your HTTP request is http://localhost:1317/CompanyFunctions/GetByCompanyId/1

Web api routing methods with different parameter names

This question could have been answered hundred times, but I couldnt find a proper resource. In a WebApi project (default project provided by VS) I have the ValuesController as below.
public string Get(int id)
{
return "value";
}
[HttpGet]
public string FindByName(string name)
{
return name;
}
[HttpGet]
public string FindById(int id)
{
return id.ToString();
}
In the WebApiConfig.cs, I have following route mapping.
config.Routes.MapHttpRoute(
name: "actionApiById",
routeTemplate: "api/{controller}/{action}/{Id}",
defaults: new { action = "FindById", Id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "actionApi",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: new { name = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Now only the FindById() action is working when i try in the browser. Why does the rest of api calls return "No HTTP resource was found that matches the request"
How can I get all three methods working? without using AttributeRouting. Am I lack of basic concepts of web api? ( i think yes)
AS We all know REST is resource based and this identify the resource with the URL, so that not more than one method with same parameter will be allowed in the REST service but there is work around in MVC 5 Web Api method level routing.
Here is the example you can do that:
[HttpGet]
[Route("api/search/FindByName/{name}")]
FindByName(string name)
{
}
[HttpGet]
[Route("api/search/FindById/{name}")]
FindById(int searchId)
Note:"search" is the controller name.
Please let know if need more clarification.
In general you don't want to have a route per action like your sample suggests. As your app grows this will get quickly out of hand.
Also consider building your url space in a way that will look just RESTfull
So methods will be GetById, GetByName, and then pass the parameters in the query string to match the right action (BTW not sure what the difference in your case is between GetById and FindById if they are not really different consider just keeping one of them around).
You can stick with the default route and your request will look like:
/api/controller/345 or /api/controller?name=UserName or /api/controller?SearchId=345 (assuming search was indeed a different behavior)
Then the method signatures:
Get(int id)
{
}
[HttpGet]
FindByName(string name)
{
}
[HttpGet]
FindById(int searchId)
{
}
Your actionApiById Route also matches the actionApi route, As your id is integer try using constraint like this.
config.Routes.MapHttpRoute(
name: "actionApiById",
routeTemplate: "api/{controller}/{action}/{Id}",
defaults: new { action = "FindById", Id = RouteParameter.Optional }
constraints: new {Id = #"\d+" }
);

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.

How to create ASP.NET Web API Url?

In ASP.NET MVC, we have #Url.Action for actions. Is there something similar like #Url.Api which would route to /api/controller?
The ApiController has a property called Url which is of type System.Web.Http.Routing.UrlHelper which allows you to construct urls for api controllers.
Example:
public class ValuesController : ApiController
{
// GET /api/values
public IEnumerable<string> Get()
{
// returns /api/values/123
string url = Url.Route("DefaultApi", new { controller = "values", id = "123" });
return new string[] { "value1", "value2" };
}
// GET /api/values/5
public string Get(int id)
{
return "value";
}
...
}
This UrlHelper doesn't exist neither in your views nor in the standard controllers.
UPDATE:
And in order to do routing outside of an ApiController you could do the following:
public class HomeController : Controller
{
public ActionResult Index()
{
string url = Url.RouteUrl(
"DefaultApi",
new { httproute = "", controller = "values", id = "123" }
);
return View();
}
}
or inside a view:
<script type="text/javascript">
var url = '#Url.RouteUrl("DefaultApi", new { httproute = "", controller = "values", id = "123" })';
$.ajax({
url: url,
type: 'GET',
success: function(result) {
// ...
}
});
</script>
Notice the httproute = "" route token which is important.
Obviously this assumes that your Api route is called DefaultApi in your RegisterRoutes method in Global.asax:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
It works with the simpler form of Url.Action thus you don't have to reference any Routing names:
Url.Action("ActionName", "ControllerName", new { httproute = "DefaultApi" })
You might want to add an area = "" if the URL is needed within an Area. (Api controllers are outside of Areas by default.) I'm using MVC 4.
Want to be able to generate links in a typesafe manner, without hardcoded strings (controller names)?
There's a nuget for that! (and it's written by Mark Seeman)
https://github.com/ploeh/Hyprlinkr
Works like this:
Routes, as usual:
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
Get an URL:
var linker = new RouteLinker(request);
var uri = linker.GetUri<FooController>(r => r.GetById(1337));
Result:
http://localhost/api/foo/1337
Here is the KISS method for answering the question:
If this is the code that you would use to create a MVC controller URL
#Url.Action("Edit", "MyController")
In order to get a URL for the API version of the controller (assuming you use the same controller name) you can use
#Url.Action("Edit", "api/MyController")
All the Url.Action method is doing is appending the root path of the application, with the controller name, followed by the action name (unless it is "Index" in which case it is not appended. if the route values object has an id property the value is also appended to the URL.