I want to create a REStful web service.
This service returns "Meetings" for a certain
day, week or months.
I would create 3 Get methods which takes date values determined by the API user.
GetMeetingsByDay(currentDate)
GetMeetingsByWeek(startDate,endDate)
GetMeetingsByMonth(startDate,endDate)
Having 3 Get methods I could not access the API anymore just via http-method type 'GET' like:
// GET: api/meetings/date
because there will be multiple GET...
I could merge the ByWeek and ByMonth methods and do this instead:
GetMeetings(startDate,endDate);
How would you make this service RESTful?
I wouldn't think of it in terms of the method names - I'd think of it in terms of the URLs to start with:
/api/meetings?date=...
/api/meetings?startDate=...&endDate=...
Think of it as a collection of meetings, and the query parameters are just that - query parameters. I would expect any value after the meetings element in the URL to be a meeting ID - for example, your GetMeetings method might return a meeting with an ID of foobar which I'd then expect to be able to fetch later with
/api/meetings/foobar
That suggests to me that you shouldn't have date as part of the URL path at all.
In terms of implementation, I don't know enough about WebAPI routing to know whether you could implement that with two methods of:
[HttpGet]
[Route("api/meetings")]
public ... GetMeetings([FromUri] DateTime startDate, [FromUri] DateTime endDate)
[HttpGet]
[Route("api/meetings")]
public ... GetMeetings([FromUri] DateTime date)
... or whether you need to a single method with optional parameters:
[HttpGet]
[Route("api/meetings")]
public ... GetMeetings(
[FromUri] DateTime? date = null,
[FromUri] DateTime? startDate = null,
[FromUri] DateTime? endDate = null)
In the latter case you'd need to then validate that the set of arguments provided was valid.
(As noted in comments, you may not need the Route here at all.)
Web API 2 supports a new type of routing, called attribute routing.
This is the scenario where you need to use attribute routing.
eg:
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
One advantage of convention-based routing is that templates are
defined in a single place, and the routing rules are applied
consistently across all controllers. Unfortunately, convention-based
routing makes it hard to support certain URI patterns that are common
in RESTful APIs.
Read more here :
Attribute Routing in ASP.NET Web API 2
Create a REST API with Attribute Routing in ASP.NET Web API 2
From a previous good answer (source):
If you have a multiple Get actions that have a single primitive argument of the same type, ASP.NET Web API will look at the name of the argument to resolve which overloaded action to call.
For example, if you have two actions:
GetProductByName(string name)
GetProductByCategory(string category)
your http client can call as :
api/products?name=hammer
api/products?category=toys
and the routing engine will call the correct action.
The SO question, How does ASP.NET Web.api handle two methods with names starting with GET? can give you some more knowledge as it had been well answered.
EDIT :
I could merge the ByWeek and ByMonth methods and do this instead:
GetMeetings(startDate,endDate);
Definitely I would encourage this if you don't have any specific logic going into the GetMeetingsbyWeek and GetMeetingsbyMonth, like it merely fetches a list from database between the specified date range.
Look at Web API routing - http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Otherwise, you could use just a querystring - like api/meetings/date?currentDate=x&startDate=x&endDate=x
For creating a WebApi Controller with multiple GET requests, You need to specify the route as follows:
routeTemplate:api/{Controller}/{action}/{param}
This should solve your problem.
Related
I have a requirement to inspect routes in .Net Core middleware and perform different actions based on different route. For this I have to do route comparisons for below sample routes returned from HttpContext.Request.Path:
/api/employees/2d50f670c72911ec861760be36a6c36a/authorise
/api/employees/9d50f670c72911ec861760be36a6c36a/decline
Because of dynamic ID present in the path, I have to split and join the string using below code:
route = "/api/employees";
var pathSegments = request.Path.Value.Split('/');
if (pathSegments.Length > 3 && Guid.TryParse(pathSegments[3], out Guid employeeId))
{
route += #"/{employeeId}/"+string.Join("/", pathSegments.Skip(4));
}
While this works okay for URL comparison, I wanted to know if this can be done in a simpler way like UriTemplate class provides in WCF?
Instead of the first two lines you wrote which you tried to partition the url, You can use the property Segments of the HttpContext.Url object. It gives you a string array contains all segments.
BUT, I guess you're performing a wrong logic. Why don't you just do the desired action in an actual Action?
I mean the urls you mentioned to compare, surely call an Action(Controller Method), right? If so, why don't you just execute your desired actions in them?
For instance, if the /api/employees/2d50f670c72911ec861760be36a6c36a/authorise request must do a logic before the requested action (and this logic is done for every request), why should we do it in a middleware?
Because as far as I know, the middlewares are used for cross-cutting concerns.
I am building a webapi to getBalance of a customer from Db.It works well and i can retrieve the data.But the problem is in the parameter part.
For eg. In ASP.NET webservice when we request the service it gives us a page where according to the the service we get to enter the parameters in the textbox and upon
firing up the service we get the data.
I have my webapi up and running and the uri looks like this --- http://localhost/api/accounts/balance/cs-001
AccountsController
public class AccountsController : ApiController
{
[HttpGet]
[ActionName("balance")]
public string Getbalance(string accountNumber)
{
var data = BusinessLayer.Api.AccountHolderApi.GetBalance(accountNumber);
return data;
}
}
And my route
RouteTable.Routes.MapHttpRoute("OfficeApi", "api/{controller}/{action}/{accountNumber}");
So the accountNumber is the paramater here.Now one of my mobile app dev friend to is going to use this api suggests me to move account number in parameter rather than url.Like in my case we append the parameter in the url to retrieve the balance from the db.I want to know how to move the account number (the param) from the url to parameter in asp.net webapi.
I am new to webapi.Help needed.Thank You.
All you have to do is change the route since account number is already a parameter on the function:
RouteTable.Routes.MapHttpRoute("OfficeApi", "api/{controller}/{action}");
Then you'll be able to do:
http://localhost/api/accounts/balance?accountNumber=cs-001
#Avitus's answer is correct. Here is some belabor in case you are interested.
If someone requests this URL: http://localhost/api/accounts/balance?accountNumber=cs-001
ASP.NET routing will figure out the controller and action based on your routing configuration. Once the controller and action have been discovered, then it will try to bind the parameters in the action like this:
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.
Here is the full article.
I have an API action defined as the following:
[Route(Name="GetMembersTest"), HttpGet, ResponseType(typeof(MemberHeadersDto))]
public IHttpActionResult GetMembers[FromUri]MemberFilterDto filter, [FromUri]PagingOptionsDto paging)
This method works as expected, routing and all, requests are flowing through just fine. However, I'd like to supply a "NextUri" for paging so that the caller can just keep following NextUri until it is null to get all the results. I have to send back a uri to the same action, 1 page ahead, if that makes sense.
So I tried using UrlHelper.Route. This route is named "GetMembers" for the purpose of this example.
NextUri = Url.Route("GetMembers", new { filter, paging });
The problem is that instead of getting something like
/v1/members?filter.q=&filter.otherproperty=&paging.count=10&paging.startRow=11
I get
/v1/members?filter=WebApi.Models.MemberFilterDto&paging=WebApi.Models.PagingOptionsDto
It looks like UrlHelper.Route doesn't support complex types in the [FromUri] parameter of a GET Request. Is there anything I can do to get this functionality? My workaround right now is to take in all the Dto properties as individual parameters then build my Dtos from them on the server. This isn't ideal because if I ever add any more options I'd have to add more parameters to the action, which also makes the route value dictionary more fragile as well because it has to match with the method signature in UrlHelper.Route(routeName,routeValues).
Unfortunately, there is no way to pass in complex object to routing. Instead, you will need to pass in the simple properties individually.
I was not able to find a way to extend Url.Route, but that would be/have been your best option.
I am looking at the following tutorial from Microsoft. As per this tutorial,
In the first example, "products" matches the controller named
ProductsController. The request is a GET request, so the framework
looks for a method on ProductsController whose name starts with
"Get...". Furthermore, the URI does not contain the optional {id}
segment, so the framework looks for a method with no parameters. The
ProductsController::GetAllProducts method meets all of these
requirements.
What happens if there are two methods like GetAllProducts() and GetSoldProducts()? Both have no parameters.
Your First Web API Tutorial
There are two possible solutions to this specific problem:
Alter MapHttpRoute calls to require specifying the name of the action. (I'm using Self-hosting syntax):
config.Routes.MapHttpRoute(
"API Route 1",
"api/{controller}/{action}");
config.Routes.MapHttpRoute(
"API Route 2",
"api/{action}",
new { controller = "products" });
So your http client would call:
api/products/GetAllProducts OR api/GetAllProducts
api/products/GetSoldProducts OR api/GetSoldProducts
See:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Place each method in a separate controller (ProductsController, SoldProductsController). So you would call api/products and api/soldproducts to get your results.
Related topic... in the situation where you have a multiple Get actions that have a single primitive argument of the same type, ASP.NET Web API will look at the name of the argument to resolve which overloaded action to call.
For example, if you have two actions:
GetProductByName(string name)
GetProductByCategory(string category)
your http client can call
api/products?name=hammer
api/products?category=toys
and the routing engine will call the correct action.
Assuming you're using the default routes the short answer is : the method defined first (at the top) of your class will be called. the other method is inaccessible.
NOTE : the beta behaved as above for 'matching multiple methods' - the RC & Release version is a bit more OCD. It throws an error if there are multiple potential matches. This change removes the confusion of multiple ambiguous matches. At the same time, it reduces our ability to mix REST and RPC style interfaces in the same controller, relying on the order & overlapping routes.
Stealing liberally from another post I wrote on the topic:
WebAPI Matching Semantic
The matching semantic used by WebAPI is fairly simple.
It matches the name of the action with the verb (verb = get? look for method starting with "get")
if a parameter is passed, the api seeks an action with a parameter
So in your code sample a GET request without a parameter matches the Get*( ) function without an parameters. A Get containing and ID looks for a Get***(int id).
Examples
While the matching semantic is simple, it creates some confusion for MVC developers (well at least this developer). Lets look at some examples :
Odd Names - Your get method can be named anything, so long as it starts with "get". So in the case of a widget controller you can name your functions GetStrawberry() and it will still be matched. Think of the matching as something like : methodname.StartsWith("Get")
Multiple Matching Methods - What happens if you have two Get methods with no parameters? GetStrawberry() and GetOrange(). As best I can tell, the function defined first (top of the file) in your code wins ...strange. This has the side effect of making some methods in your controller unreachable (at least with the default routes)....stranger.
UPDATE
#WinFXGuy - This was a bit long to put in a comment, but ...
Don't jump to conclusions! I tried to answer the question you posed, but that's only half the story. There is plenty you can do to change the default behavior.
First, WebAPI supports much of the oData spec. If you bubble an IQueryable up to your controller, oData paramaters are automatically integrated with the query object. It takes parameters like $filter, $top, and $skip. So you in your case you can write one method and pass something like $filter=sale_date neq null.
Additionally, you can apply the [ResultLimit] attribute to prevent people asking for 15 billion records.
Second you can modify the routes. The default routes aim towards a RESTful api, where you generally have 1 controller per entity. You can change the routes and make it RPC style.
If you look at my linked post I explain how I kept the default route binding, added 'sub folders' and also allowed additional method calls for scenarios where i needed GetAllProducts() and GetSoldProducts().
Adding an answer to reflect that the latest version of Web API supports [Route] attribute natively
[Route("api/products")]
public IEnumerable<Product> GetAllProducts(){}
[Route("api/products/sold")]
public IEnumerable<Product> GetSoldProducts(){}
I have a fixedURL to which I'd like to post different types of xml message, deserialized using DataContracts. Depending on the type of the deserialized message, I'd like to route to:
overloaded methods, e.g.
void Process(ContractType1 request) {}
void Process(ContractType2 request) {}
So at some point I need to deserialize this message and hopefully allow the default routing rules to match the correct method. Which extensibility point should I use for this? Or even better, can I make this work out of the box?!
If it makes any difference, I'm using MVC 3.
ASP NET MVC does not respect the overload if they are not decorated for different HTTP methods - e.g. one for POST, other for GET.
You need to use [ActionName(Name = "Process2")] to change the route name. And you will have to use different routes to access (if the HTTP methods are the same)
Have a look here.
Apart from the technical workaround, passing different contracts to the same URL is against the REST principles. Data could be in different format (XML, JSON, ...) but it must be the same. The URI defines a unique intention. Now it is possible to have a common dumpster where documents are all dumped to the same URI but then ASP NET MVC default model binder would not be able to help you and you need to create your own model binder.
Contrary to the other answer I say this is possible
Asp.net MVC is a great platform that can be easily extended. And so basically I've written a special action method selector that makes it possible to write overloads that can serve the same HTTP method but defer in parameters. By default you'd get runtime error that action method can't be resolved. But when you use this action method selector you get rid of this error.
Basically if your parameter classes have distinct parameter names, you can actually select methods by that.
Action method selector is called RequiresRouteValuesAttribute and a typical usage scenario would be with default route where id is optional in:
{controller}/{action}/{id}
This means that you either have to write
public ActionResult Index(int? id)
{
if (id.HasValue)
{
// display details view
}
else
{
// display master view
}
}
but by using my action method selector you can easily write two action methods:
public ActionResult Index()
{
// display master view
}
[RequiresRouteValues("id")]
public ActionResult Index(int id)
{
// display details view
}
The same could be applied to your action methods as long as your custom types have distinct property names or methods use different parameter names. So in your case it could be something like:
[RequiresRouteValues("first.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType1.Distict.Property.Name")]
public ActionResult Process(ContractType1 first)
{
// do what's appropriate
}
[RequiresRouteValues("second.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType2.Distict.Property.Name")]
public ActionResult Process(ContractType2 second)
{
// do what's appropriate
}
Read all the details about this action method selector and get the code as well.