Web API Methods: Return IHttpActionResult Or IQueryable - asp.net-web-api

I see most comments about Web API methods say they should return IHttpActionResult. When I add a Controller in Visual Studio and I select "Web API 2 Controller With Action For Entity Framework" it entered these two methods (along with POST, PUT, and DELETE). It uses IQueryable to return multiple records instead of IHttpActionResult. Which is correct and why?
// GET: api/Trades
public IQueryable<Trade> GetTrades()
{
return db.Trades;
}
// GET: api/Trades/5
[ResponseType(typeof(Trade))]
public IHttpActionResult GetTrade(int id)
{
Trade trade = db.Trades.Find(id);
if (trade == null)
{
return NotFound();
}
return Ok(trade);
}

Either is correct. The convenience of IQueryable is that you, well, can query it.
For example: You can use OData with WebAPI where some flexibility is required. You can then make your AJAX requests like mysite.com/odata/Orders?$filter=OrderID eq 1156.
Here is more on OData: http://www.odata.org/
It proved to be very handy for a dynamic search API with pagination support.

Related

Web API and View in same controller

I'm creating a website that will have 50+ optical calculations. I need them accessible via api, but I also need them available in a view. I have a .dll that does the calculations that are accessed in a controller labeled with the name of the "type" of calculations. In this case it's a PrismController that handles all the calculations related to Prism. I'm only showing the Apical Angle calculation.
Is the way I'm doing it optimal in regards to best practices? Before I create 50 of these, I'd like to know if I'm going down the wrong path.
Seems like a lot of code.
The code in the view is what I'm using for one calculation.
[Produces("application/json")]
[Route("api/v1/[controller]")] <----------api route at controller level
public class PrismController : Controller
{
[HttpGet("ApicalAngle/{DegreesDeviation}/{Index}")]
public ActionResult<PrismModel> ApicalAngle(PrismModel prism)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
prism.Result = Prism.ApicalAngle(prism.DegreesDeviation, prism.Index);
return prism;
}
[Route("/[Controller]/[Action]")] <----view route overriding api route
[HttpGet("ApicalAngle")]
public IActionResult ApicalAngleCalc()
{
return View();
}
[HttpPost("ApicalAngle")]<------- Post that performs calculation
public ActionResult<PrismModel> ApicalAngleCalc(PrismModel prism)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var dev = prism.DegreesDeviation;
prism.Result = Prism.ApicalAngle(prism.DegreesDeviation, prism.Index);
return View(prism);
}

ASP.net Core RC2 Web API POST - When to use Create, CreatedAtAction, vs. CreatedAtRoute?

What are the fundamental differences of those functions? All I know is all three result in a 201, which is appropriate for a successful POST request.
I only follow examples I see online, but they don't really explain why they're doing what they're doing.
We're supposed to provide a name for our GET (1 record by id):
[HttpGet("{id}", Name="MyStuff")]
public async Task<IActionResult> GetAsync(int id)
{
return new ObjectResult(new MyStuff(id));
}
What is the purpose of naming this get function, besides that it's "probably" required for the POST function below:
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return CreatedAtRoute("MyStuff", new { id = myStuff.Id }, myStuff);
}
I notice that CreatedAtRoute also has an overload that does not take in the route name.
There is also CreatedAtAction that takes in similar parameters. Why does this variant exist?
There is also Created which expects a URL and the object we want to return. Can I just use this variant and provide a bogus URL and return the object I want and get it done and over with?
I'm not sure why there are so many variants just to be able to return a 201 to the client. In most cases, all I want to do is to return the "app-assigned" (most likely from a database) unique id or a version of my entity that has minimal information.
I think that ultimately, a 201 response "should" create a location header which has the URL of the newly-created resource, which I believe all 3 and their overloads end up doing. Why should I always return a location header? My JavaScript clients, native mobile, and desktop apps never use it. If I issue an HTTP POST, for example, to create billing statements and send them out to users, what would such a location URL be? (My apologies for not digging deeper into the history of the Internet to find an answer for this.)
Why create names for actions and routes? What's the difference between action names and route names?
I'm confused about this, so I resorted to returning the Ok(), which returns 200, which is inappropriate for POST.
There's a few different questions here which should probably be split out, but I think this covers the bulk of your issues.
Why create names for actions and routes? What's the difference between action names and route names?
First of all, actions and routes are very different.
An Action lives on a controller. A route specifies a complete end point that consists of a Controller, and Action and potentially additional other route parameters.
You can give a name to a route, which allows you to reference it in your application. for example
routes.MapRoute(
name: "MyRouteName",
url: "SomePrefix/{action}/{id}",
defaults: new { controller = "Section", action = "Index" }
);
The reason for action names are covered in this question: Purpose of ActionName
It allows you to start your action with a number or include any character that .net does not allow in an identifier. - The most common reason is it allows you have two Actions with the same signature (see the GET/POST Delete actions of any scaffolded controller)
What are the fundamental differences of those functions?
These 3 functions all perform essentially the same function - returning a 201 Created response, with a Location header pointing to the url for the newly created response, and the object itself in the body. The url should be the url at which a GET request would return the object url. This would be considered the 'Correct' behaviour in a RESTful system.
For the example Post code in your question, you would actually want to use CreatedAtAction.
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return CreatedAtAction("MyStuff", new { id = myStuff.Id }, myStuff);
}
Assuming you have the default route configured, this will add a Location header pointing to the MyStuff action on the same controller.
If you wanted the location url to point to a specific route (as we defined earlier, you could use e.g.
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return CreatedAtRoute("MyRouteName", new { id = myStuff.Id }, myStuff);
}
Can I just use this variant and provide a bogus URL and return the object I want and get it done and over with?
If you really don't want to use a CreatedResult, you can use a simple StatusCodeResult, which will return a 201, without the Location Header or body.
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return StatusCode(201);
}
I believe there is an example for you here.
Remembering that I'm using .NET 6
[HttpPost]
public IActionResult CadastrarCerveja([FromBody] Cerveja cerveja)
{
using (var ctx = new CervejaContext())
{
ctx.Cervejas.Add(cerveja);
ctx.SaveChanges();
}
return CreatedAtAction(
nameof(LerCerveja),
new { IdCerveja = cerveja.Id },
cerveja);
}
[HttpGet("{IdCerveja}")]
public IActionResult LerCerveja(int IdCerveja)
{
var ctx = new CervejaContext();
var cerv = ctx.Cervejas.FirstOrDefault(c => c.Id == IdCerveja);
if (cerv == null)
return NotFound();
else
return Ok(cerv);
}

Additional GetXYZ methods in EntitySetController

I want to be able to have additional GetXYZ methods in my EntitySetController derived controller class. For example:
[HttpGet]
[Queryable]
public string GetAirportsWithinRadius(int airportId, int radius)
{
var resultAirports = GetAirportsWithinRadius2(airportId, radius);
return resultAirports;
}
This is what I have for config:
ActionConfiguration getAirportsWithinRadius = modelBuilder.Entity<Airport>().Collection.Action("GetAirportsWithinRadius");
getAirportsWithinRadius.Parameter<int>("airportId");
getAirportsWithinRadius.Parameter<int>("radius");
getAirportsWithinRadius.ReturnsCollectionFromEntitySet<Airport>("Airports");
I want this action to be composable just like the default Get Queryable action, but this would be an alternative that supports all odata parameters but additionally an airportId and radius. This would first filter airports by a radius search (this I know how to do - it's irrelevant to the question) and then return the Queryable so that it can be further filtered by odata params.
Everything I read says this would be an odata action and therefore must be a POST, but Get is also an action and that is a GET, so why not allow extended getters with additional parameters? Am I missing something? How do I accomplish what I want to get done?
I would call this from an ajax client as such:
GET /odata/Airports?$inlinecount=allpages&$top=25&airportId=2112&radius=50
as opposed to a regular odata GET:
GET /odata/Airports?$inlinecount=allpages&$top=25
Thanks
EDIT:
I understand now that this is an odata "function" and it is under consideration as a future feature. Let's forget for second the odata meaning of this. It is essentially a WebApi HttpGet that returns a Queryable, right? So, as long as I don't care about the metadata advertising of this "function", how can I make sure that it is a reachable HttpGet form a route perspective inside of an ODataController? The ODataController needs the MapODataRoute and can I additionally add non odata routes using additional MapHttpRoutes?
I ask this because it seems to me that I should be able to, but all my tries have failed (trying to hit the HttpGet through fiddler). I can find no examples on extending an ODataController with additional non-odata GETs. Can someone help me understand if and how this can be done with the example?:
[Queryable]
public IQueryable<Airport> Get()
{
return db.Airports;
}
[HttpGet]
[Queryable]
public string GetAirportsWithinRadius(int airportId, int radius)
{
var resultAirports = GetAirportsWithinRadius2(airportId, radius);
return resultAirports;
}
You are looking for OData Functions, which is not yet supported out of the box. We have an issue over here. You can up-vote it.
http://aspnetwebstack.codeplex.com/workitem/881

Controller not filtering data in Breeze query in DotNetNuke Module

I am trying to include the basic Breeze sample in a DotNetNuke module (it works fine in a standalone WebAPI project). To simplify things I remove the client and will just refer to the URL JSON calls I make in the Chrome browser.
I can see my metadata and a full list of items, eg:
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/metadata
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/todos
however, when I try to filter the list from the URL, it always returns the full list, e.g.
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/todos?=DareId%20eq%204
I think it is something to do with the way I have declared the MapHTTRoute. The problem is that DotNetNuke modules do not have a Global.ascx. I have copied the BreezeWebApiconfig.cs file into my App_Start folder and this does fire when I debug, however DotNetNuke uses mechanism for registering routes:
using DotNetNuke.Web.Api;
namespace SmartThinker.Modules.Framework
{
public class RouteMapper : IServiceRouteMapper
{
public void RegisterRoutes(IMapRoute mapRouteManager)
{
mapRouteManager.MapHttpRoute("framework", "BreezeApi", "breeze/{controller}/{action}", new[] { "SmartThinker.Modules.Framework.Controllers" });
}
}
}
I have read up on http://www.breezejs.com/documentation/web-api-controller#note01 and http://www.breezejs.com/documentation/web-api-routing but it seems that it's something to do with the way DNN registers the routes. Is there anyway to do this without using BreezeWebApiConfig.cs?
My controller code has the BreezeController attribute. (When I do connect the sample client to it I do get a list of items - it just does not filter, so I think it is something to with the OData Action filters. How can I debug where the problem is?
Update 1)
Here is the metadata:
http://www.ftter.com/desktopmodules/framework/api/dare/metadata
The GetUsers method:
http://www.ftter.com/desktopmodules/framework/api/dare/getusers
and the GetUsers method trying to filter by UserID (which doesn't work, which is the issue)
http://www.ftter.com/desktopmodules/framework/api/dare/getusers?=UserID%20eq%204
http://www.ftter.com/desktopmodules/framework/api/dare/GetUsersWithoutCors?=UserID%20eq%204 (this returns IQueryable)
Here is the controller:
[BreezeController]
public class DareController : DnnApiController
{
private readonly EFContextProvider<FrameworkContext> contextProvider = new EFContextProvider<FrameworkContext>();
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage Metadata()
{
var response = Request.CreateResponse(HttpStatusCode.OK, contextProvider.Metadata());
return GetResponseWithCorsHeader(response);
}
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage GetUsers()
{
var userInfoController = new UserInfoController();
var response = Request.CreateResponse(HttpStatusCode.OK, userInfoController.GetUsers());
return GetResponseWithCorsHeader(response);
}
[AllowAnonymous]
[HttpGet]
public IQueryable<User> GetUsersWithoutCors()
{
return contextProvider.Context.Users;
}
}
The routing is not really a Breeze issue. How your server routes requests to your controller is up to you. What we do out-of-the-box is just one way among innumerable many.
You have the [BreezeController] attribute on your controller yes? Can you put a sample endpoint up where we could hit it. Might get some clues from that. Also post the controller. A tiny example should do ... something returning metadata and one method returning IQueryable.
Update 25 Jun 2013
I think you've discovered a bug in the way our [BreezeController] discovers methods returning IQueryable<T>.
The [BreezeController] attribute scans your Web API controller methods and (in effect) applies the [BreezeQueryable] attribute to methods returning IQueryable<T>.
[BreezeQueryable] is an extension of the Web API's [Queryable] that adds support for $select, $expand, and nested $orderby ... all missing from the current [Queryable].
I see now that your GetUsers() method returns HttpResponseMessage rather than IQueryable<User>. Let's assume that the userInfoController.GetUsers() method inside your method returns IQueryable<User>. Otherwise, the OData query parameters will not apply and we'll have to take this in a different direction. Moving along ...
I checked with v.1.3.6 of the Breeze.WebApi.dll and it does not detect that the HttpResponseMessage is wrapping IQueryable<T>. Therefore, it does not apply the client's OData query criteria (or any other OData modifiers for that matter). This shortcoming (in my opinion) is a bug. The following should be equivalent implementations:
[HttpGet]
public IQueryable<TodoItem> Todos() {
return _repository.Todos;
}
[HttpGet]
public HttpResponseMessage TodosWrapped()
{
return Request.CreateResponse(HttpStatusCode.OK, _repository.Todos);
}
The second, "wrapped" method does not respect the OData query parameters.
Fortunately, there is a workaround until we get this fixed. Just add the [BreezeQueryable] attribute explicitly ... as in:
[HttpGet]
[BreezeQueryable]
public HttpResponseMessage TodosWrapped()
{
return Request.CreateResponse(HttpStatusCode.OK, _repository.Todos);
}
I confirmed that this approach does work.
Thanks for finding this.
Use OData query syntax
A colleague also noticed that your query URL does not use the OData query syntax. You wrote:
... /todos?=DareId%20eq%204
when it should be
... /todos/?$filter=DareId%20eq%204
Make sure you use ?$filter=

The "DELETE" type of Http request does not work in WebAPI?

I have GET, PUT, POST working in my WebAPI project.
The last one of Http requests I am doing is DELeTE, BUT it does not work.
I have read through many posts in here as well as other websites, none of them. e.g.
WebAPI Controller is not being reached on DELETE command
WebAPI Delete not working - 405 Method Not Allowed
ASP.Net WebAPI Delete verb not working
ASP.NET Web API - PUT & DELETE Verbs Not Allowed - IIS 8
http://social.msdn.microsoft.com/Forums/en-US/windowsazuredevelopment/thread/8906fd7e-a60b-484e-be63-9574b9fca44a/
etc...
Are there any workarounds?
Please help, thanks.
Update:
My back-end code:
[HttpDelete]
public HttpResponseMessage Delete(int divisionID)
{
if (divisionID != default(int))
{
var found = dc.MedicareLocalAccounts.SingleOrDefault(m => m.DivisionID == divisionID);
if (found == null)
{
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
dc.MedicareLocalAccounts.Remove(found);
dc.SaveChanges();
return new HttpResponseMessage(HttpStatusCode.OK);
}
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
Now, if I change the parameter type from int to any classes, let's say Division
Delete(Division d)
{
int divisionID = d.DivisionID;
//....the rest is same
}
In this way, it works.
But I just do not want to input the entire object as a parameter to make the DELETE method work as it is not necessary.
So do you have any other better solutions?
Web API handles simple parameter types (int) differently than complex types (classes). By default, a simple parameter is taken from the request URI, and a complex type is taken from the request body.
In your first example, the parameter name is 'divisionID' -- does this match your route variable? The default Web API route is "api/{controller}/{id}", so the parameter should be named 'id'.
A workaround would be using the AttributeRouting library. This is an extension to WebAPI and can be downloaded from nuget. With the AttributeRouting library you could e.g. implement a function with HttpGet that wil perform the delete
[GET("delete/{id}"]
function DeleteThis(int id)
{
...
}

Resources