I have a GET request with URL which is http://foo.com/accounts/123/users/456. For the API Controller, the routing will be like:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/accounts/{accountid}/users/{userid}",
defaults: new { accountid = RouteParameter.Optional, userid = RouteParameter.Optional }
);
The controller will like:
public string Get(string accountid, string userid)
{
....
}
So, rather than receive two parameters, is that possible to make the controller to receive one object which contains these parameters like:
public string Get(Query query)
{
string accountid = query.AccountID;
string userid= query.UserID;
}
Thanks for the help!!
Absolutely. From the ASP.NET site:
By default, Web API uses the following rules to bind parameters:
If the parameter is a “simple” type, Web API tries to get the value from the URI. Simple types include the .NET primitive types
(int, bool, double, and so forth), plus TimeSpan, DateTime, Guid,
decimal, and string, plus any type with a type converter that can
convert from a string. (More about type converters later.)
For complex types, Web API tries to read the value from the message body, using a media-type formatter.
Related
For what reason should we apply these attributes in ASP.NET Core model binding?
What are the consequences of not using them?
Isn't the model binding engine able to search through the incoming request and map them to Controller action method parameters without these attributes:
[FromQuery] - Gets values from the query string.
[FromRoute] - Gets values from route data.
[FromForm] - Gets values from posted form fields.
[FromBody] - Gets values from the request body.
[FromHeader] - Gets values from HTTP headers.
See this Controller action method examples:
public ActionResult<Pet> Create([FromBody] Pet pet)
public ActionResult<List<Pet>> Search([FromRoute] string breed, [FromQuery] string color, [FromQuery] int age)
We can also apply the attributes to the model class:
public class Pet
{
public string Name { get; set; }
[FromQuery]
public string Breed { get; set; }
}
Source: Microsoft Docs
Controller action method examples without attributes:
public ActionResult<Pet> Create(Pet pet)
public ActionResult<List<Pet>> Search(string breed, string color, int age)
You could check the Sources description:
By default, model binding gets data in the form of key-value pairs from the following sources in an HTTP request:
Form fields
The request body (For controllers that have the [ApiController] attribute.)
Route data
Query string parameters
Uploaded files
For each target parameter or property, the sources are scanned in the order indicated in the preceding list. If the default source is not correct, we can use one of the following attributes to specify the source:
[FromQuery] - Gets values from the query string.
[FromRoute] - Gets values from route data.
[FromForm] - Gets values from posted form fields.
[FromBody] - Gets values from the request body.
[FromHeader] - Gets values from HTTP headers.
For example:
When using the following method, it will get the pet data from the form fields:
public ActionResult<Pet> Create(Pet pet)
If using the following method, it will get the parameter from the default source. We can pass the parameter via the Form or Query string.
public ActionResult<List<Pet>> Search(string breed, string color, int age)
If adding attribute to above method, like this:
public ActionResult<List<Pet>> Search([FromQuery]string breed, [FromQuery]string color, [FromQuery]int age)
You could only pass the parameter via the Query string. In this scenario, if you pass the parameters via Form, the parameters in the action method will be Null.
So, by using these attributes we could specify the model binding source, without to scan the default source list.
Very possible this is a duplicate, but I've looked and can't find an answer. The first answer here looked promising: Query string not working while using attribute routing But I tried that and it didn't work.
[HttpGet, Route("api/machine/byid/{id=id}/{pageNumber=pageNumber}/{pageSize=pageSize}/{fields=fields}")]
public string ById(int id, int pageNumber, int pageSize, string fields)
// code removed
}
This works:
https://localhost:44303/api/machine/byid/1/2/3/a,b,c
This does not:
https://localhost:44303/api/machine/byid?id=1&pageNumber=2&pageSize=3&fields=a,b,c
The second url returns:
{"type":"https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"|bf12950b-472923d3a24062d1.","errors":{"id":["The value 'id' is not valid."],"pageSize":["The value 'pageSize' is not valid."],"pageNumber":["The value 'pageNumber' is not valid."]}}
You would need two routes:
[HttpGet("api/machine/byid")]
public string ById(
[FromQuery("id")] int id,
[FromQuery("pageNumber")] int pageNumber,
[FromQuery("pageSize")] int pageSize,
[FromQuery("fields")] string fields)
{
}
Follow this link for more informations
The example you provided demonstrates route parameters. There is a distinct difference between route parameters and query parameters.
To accomplish query parameters, you can the [FromQuery] attribute to your method parameters. This will allow for the query parameter example that you provided,
Example : https://localhost:5000/api/persons?firstName=bob&lastName=smith
You can also provide default values for these from within your method parameters. You can string multiple query parameters together in one action.
For route parameters, the parameters are provided via the route itself.
Example : https://localhost:5000/api/persons/23
These parameters are defined from within the [HttpGet("{id}")] attribute on your controller action. You can also constrain the parameter to a certain type, such as an int. This is achieved by adding a colon and specifying the type. Example [HttpGet("{id:int}")]. No further attributes are required to be added within your method parameters for route parameters.
Of course you must also declare these parameters in your method parameters, for both types.
// "/api/persons/23"
[HttpGet("{id}")]
public async Task<IActionResult> GetPersonById(int id)
{
// Code ...
}
// "/api/persons?firstName=bob&lastName=smith"
[HttpGet]
public async Task<IActionResult> GetPersonByName([FromQuery] string firstName = null, [FromQuery] string lastName = null)
{
// Code here... both firstName and lastName can now be optional or only one provided
}
The answer by sturcotte06 was close, but was not 100% Core compliant. This works:
[HttpGet, Route("api/machine/byid/{id=id}/{pageNumber=pageNumber}/{pageSize=pageSize}/{fields=fields}")]
public string ById([FromQuery] int id, [FromQuery] int pageNumber, [FromQuery] int pageSize, [FromQuery] string fields)
{
// code removed
}
I would like to provide a ASP.Net Web API that can be called by the following formats:
http
://myApiServer/API/MyLookupMethod/GetForIVR/PhoneNumber/8005551212
or
http
://myApiServer/API/MyLookupMethod/GetForIVR?LookupType=PhoneNumber&LookUpValue=8005551212
Is it possible to set up a route that works with either call?
My current route is
config.Routes.MapHttpRoute(
name:"MyRoute",
routeTemplate:"api/{controller}/{action}/{Lookuptype}/{lookupvalue}"
);
Try to use attribute routing
[Route("api/user/YourMethodName/{id}/{param2}")]
[HttpGet]
public HttpResponseMessage YourMethodName(int id = 0, bool param2= true)
Hope it works!!
You can use Attribute based routing i.e. specific routing on controllers and methods. Further you should pass your 2 parameters as query-string like in this URL MyLookupMethod/GetForIVR?LookupType=PhoneNumber&LookUpValue=8005551212 and they will be automatically parsed as method parameters.
public class SomeController : ApiController
{
// GET MyLookupMethod/GetForIVR?LookupType=PhoneNumber&LookUpValue
[Route("MyLookupMethod/GetForIVR")]
public Book Get(int LookupType, int LookUpValue) { ... }
}
Disclaimer: Yes I know WCF isn't the Web API or MVC. However I'm trying to figure out a hack to do sort of what MVC does with routing so that I may be able to keep the same method names in my .svc contract behind the scenes when I wire up different requests with different sets of querystring params.
So I have this dumb WCF service and I want to have an overload of Get methods in here. But I can't, WCF bitches about it when I try to make a call to this service.
I have 2 methods named "Get" that I'm trying to essentially overload but differentiate by querystring. Notice the two below have different sets of querystrings in the uri so my intent is not having to have all these different Get, GetbySomething, GetBySomething2. It'd be nice to just stick with the name Get() on all mymethods and just overload them / differentiate them so that the request coming in can wire up to maybe the querystring differentiation sort of like MVC does:
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&orderStatus={orderStatus}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}&purchaseOrderNumber={purchaseOrderNumber}")]
public OrderResponse Get(string securityToken, string brokerId, string orderStatus, string pageNumber, string pageSize, string sortBy, string purchaseOrderNumber)
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&purchaseOrderNumber={purchaseOrderNumber}&startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}")]
public OrderResponse Get(string securityToken, string brokerId, int purchaseOrderNumber, string startDate, string endDate, string pageNumber, string pageSize, string sortBy)
Error in response coming from WCF: "Cannot have two operations in the same contract with the same name, methods Get and Get in type OrderService violate this rule. You can change the name of one of the operations by changing the method name or by using the Name property of OperationContractAttribute. "
ok whatever fine. So can I somehow use a route table in my global.asax to essentially do what MVC can do?
RouteTable.Routes.Add(new ServiceRoute("Inventory", new WebServiceHostFactory(), typeof(InventoryService)));
MVC allows me to map but takes into account the querystring as part of the matching:
Just an example, I could take into account querystrings ?someParam={someValue}
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}?someParam={someValue}",
defaults: new { id = RouteParameter.Optional }
);
yes I also know in MVC you can overload your methods to differentiate them via parameters in the controller and as you can see here my WCF contract methods for Get() do have different set of params so one would think WCF would let you differentiate the same method name by params but it doesn't.
You can overload the functions to accept different parameters
Because in WCF the way you specify the querystring match part is via attribute:
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&purchaseOrderNumber={purchaseOrderNumber}&startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}]
so is there a way to use my RouteTable.Routes.Add(new ServiceRoute("V1/Inventory", new WebServiceHostFactory(), typeof(InventoryService))); that I have in my global.ascx to somehow all me to make Get method names unique and overload them by differentiating them by querystring?
Because right now to fix this I'd have to make one of my Get method names something else which is IMO stupid and unclean. I'd have to do something like GetByDateRange. So I'd have to do something like:
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&orderStatus={orderStatus}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}&purchaseOrderNumber={purchaseOrderNumber}")]
public OrderResponse Get(string securityToken, string brokerId, string orderStatus, string pageNumber, string pageSize, string sortBy, string purchaseOrderNumber)
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&purchaseOrderNumber={purchaseOrderNumber}&startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}")]
public OrderResponse GetByDateRange(string securityToken, string brokerId, int purchaseOrderNumber, string startDate, string endDate, string pageNumber, string pageSize, string sortBy)
This puts me back into RPC style method names behind my web service handling and I hate that. We are using WCF restfully as well so I'd like to keep this clean by just having a bunch of overloaded Put() methods, Get(), etc.
Previously, I had two methods and I labelled one with [WebGet] and one with [WebInvoke(Method = "POST"]
when I did a GET or a POST to the URL that I specified, it would always call the correct method.
The URLs were:
POST: fish-length
GET: fish-length?start-date={startDate}&pondId={pondId}
Now that I'm using web api, I have to define my routes seperately, like this:
RouteTable.Routes.MapHttpRoute(
name: "AddFishLength",
routeTemplate: "fish-length",
defaults: new
{
controller = "FishApi",
action = "AddFishLength"
});
RouteTable.Routes.MapHttpRoute(
name: "GetFishLength",
routeTemplate: "fish-length?start-date={startDate}&pondId={pondId}",
defaults: new
{
controller = "FishApi",
action = "GetFishLength"
});
However the second route doesn't work, because you're not allowed a ? in the routeTemplate.
I can change the URL format to something like fish-length/{startDate}/{pondId} but it's really not a very nice way to expose the service.
Is there a better way to do this? Also because I was doing a POST and GET to the same url before, I'd need to ensure my routing method still allowed this. Assuming the above worked, I'm still not sure how it would route correctly.
No, you don't need to define separate routes. All you need is a single route:
RouteTable.Routes.MapHttpRoute(
name: "AddFishLength",
routeTemplate: "fish-length",
defaults: new
{
controller = "FishApi",
}
);
and then follow the RESTful naming conventions of your ApiController's actions:
public class FishApiController: ApiController
{
// will be called for GET /fish-length
public HttpResponseMessage Get()
{
// of course this action could take a view model
// and of course that this view model properties
// will automatically be bound from the query string parameters
}
// will be called for POST /fish-length
public HttpResponseMessage Post()
{
// of course this action could take a view model
// and of course that this view model properties
// will automatically be bound from the POST body payload
}
}
So assuming you have a view model:
public class FishViewModel
{
public int PondId { get; set; }
public DateTime StartDate { get; set; }
}
go ahead and modify your controller actions to take this parameter:
public class FishApiController: ApiController
{
// will be called for GET /fish-length
public HttpResponseMessage Get(FishViewModel model)
{
}
// will be called for POST /fish-length
public HttpResponseMessage Post(FishViewModel model)
{
}
}
You could obviously have different view models for the different actions.
You can't specify the query string parameters in the route template - but as long as you have a method that matches the names of the parameters WebApi should be clever enough to figure it out on its own.
public HttpResponseMessage Get(string id) would correspond to a request for {controller}?id=xxx
However, it's hard to tell without seeing the actual objects how you should go about solving your case. E.g. WebApi doesn't like complex types in a Get request, as well as it supports url-encoded content in post data in a particular way only.
As for differentiating between Get and Post that's quite simple - WebApi knows which method you used when sending the request and then it looks for a method name starting with Get/Post or decorated with the HttpGet/Post attribute.
I recommend taking a look at the following articles - they helped me understand how it works:
http://www.west-wind.com/weblog/posts/2012/Aug/16/Mapping-UrlEncoded-POST-Values-in-ASPNET-Web-API
http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx