WebApi Put, how pass the params? - asp.net-web-api

I have created a webApi that automatically created som methods for me.
That Put-Method got created like this:
// PUT: api/Actor/5
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutActor(int id, Actor actor)
{
//Code that updates the actor
}
But How can i call this method?
Im thinking that it must start with:
HttpResponseMessage response = client.PutAsJsonAsync("api/Actors/") <-How can i add the two params?
According to some posts you cant do it without workarounds:
WebAPI Multiple Put/Post parameters
But it seems strange considering that the method got created to me automatically. Should be a standard way to do it no?

You could pass the id in the route, whereas the Actor in the payload using the following overload which takes as second argument an object:
HttpResponseMessage response = client.PutAsJsonAsync("api/Actors/123", new
{
actor = new Actor
{
Name = "John Smith",
Age = 30,
}
});

Related

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

Web API 2 attribute routing returning 404

I'm having trouble getting the Web API 2 attribute routing to work.
I've been trying everything I could find this whole evening but I can't find the problem.
What I want to achieve is the following:
Make a POST request to http://localhost:xxxx/api/chat/joingroup/1234 to get to the following API call:
[Route("joingroup/{id}")]
[HttpPost]
public async Task<IHttpActionResult> JoinGroup(string id, string connectionID)
{
await hubContext.Groups.Add(connectionID, id);
return Ok(hubContext.Groups.ToString());
}
This keeps getting me a http 400 message.
{"message":"No HTTP resource was found that matches the request URI 'http://localhost:41021/api/chat/joingroup/123'.",
"messageDetail":"No action was found on the controller 'Chat' that matches the request."}
But sending a post to: http://localhost:41021/api/chat/sendmessage/pm/123123 and also to http://localhost:41021/api/chat/joingroup gives me a 200
The chatcontroller:
[RoutePrefix("api/chat")]
public class ChatController : ApiController
{
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
[...]
[Route("joingroup/{id}")]
[HttpPost]
public async Task<IHttpActionResult> JoinGroup(string id, string connectionID)
{
await hubContext.Groups.Add(connectionID, id);
return Ok(hubContext.Groups.ToString());
}
HTTP POSTS to http://localhost:xxxx/api/chat/sendmessage are working fine.
I cannot figure out why it isn't going to the correct method when I'm calling a POST on http://localhost:xxxx/api/chat/joingroup/1234.
SOLUTION:
The solution was to reference both values that are needed in the JoinGroup method, id and connectionID. Now the request will hit this method.
Using:
http://localhost:xxxx/api/chat/joingroup/john?connectionid=123 will work.
I noticed two things on the code you sent through:
the path you POST to is: localhost:xxxx/joingroup/1234 , this
should be localhost:xxxx/api/chat/joingroup/1234
because you have 2 parameters for the joingroup, you will need to pass both of them through, may be like this localhost:xxxx/api/chat/joingroup/1234?connectionID=value or you can pass it on the request body
if the connectionID is optional you can modify the method to use option al parameters like this
public string JoinGroup(string id, string connectionID = "")
please let me know if this helps.
Thanks
Ashraf
I assume the connectionID parameter references the POSTed data. The easiest thing to make it work is to decorate it with the [FromBody] attribute and put an = in front of the value being sent like this: =MyConnection1.
Web API expects an object with properties or an array otherwise. Alternatively, you can wrap the connection ID with a custom class and pass it serialized as JSON/XML.

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)
{
...
}

Generating url for a resource in asp.net web api outside of ApiController

Looking for a way to construct or generate a url for a specific resource in asp.net web api. It can be done in the controller since it inherits from ApiController hence you get the UrlHelper.
I am looking to construct resource url out of the context of the ApiController.
Here is what I did:
Requires HttpContext/Request, so might not work in Application_Start.
Only tested in WebApi 1
Only works for routes registered in GlobalConfiguration (but if you have some other one, just pass it in instead)
// given HttpContext context, e.g. HttpContext.Current
var request = new HttpRequestMessage(HttpMethod.Get, context.Request.Url) {
Properties = {
{ HttpPropertyKeys.HttpConfigurationKey, GlobalConfiguration.Configuration },
{ HttpPropertyKeys.HttpRouteDataKey, new HttpRouteData(new HttpRoute()) },
{ "MS_HttpContext", new HttpContextWrapper(context) }
}
};
var urlHelper = new UrlHelper(request);
What about the UrlHelper classes:
System.Web.Http.Routing.UrlHelper;
System.Web.Mvc.UrlHelper
The MVC one has some useful static methods accepting routing information or it can be used as an instance created by passing in a RequestContext (which is available in most MVC filters and various other places). The instance methods should be exactly what you need to generate urls.
The HTTP one accepts a ControllerContext (which is also available in most HTTP filters and various other places).
I'm not sure about the ApiController, as I haven't used it before. This may then be redundant for you, but then again, it may not be. Check out your Global.asax.cs file, specifically the RegisterRoutes function. Initially, you should see the following mapping:
routes.MapRoute ("Default", "{controller}/{action}/{id}", new { controller = "MyController", action = "Index", id = "" });
So by default your application is set up to handle routes in the following format:
{ControllerName}/{ActionName}/{ResourceId}
A controller class set up like the following should enable you to receive requests in that format.
class {ControllerName}Controller : ApiController
{
public ActionResult {ActionName} (string id)
{
// fetch your resource by its unique identifier
}
}

Resources