TL;DR Summary: Can I configure MVC Web API routing for HTTP GET, PUT & DELETE?
I've been looking into replacing our old Data Access Layer (a DLL based on DataSets and TableAdapters) with a private API, with a view to creating a public API if it's successful. I've done some work with MVC 4 to refresh our frontend, and loved working with it, so it seems sensible to explore the "Web API" project type before diving into WS- or WCF-based libraries.
An initial demo allows me to return XML/JSON nicely, for example:
//service.url/api/Users
... returns a list of users, while a specific user's details can be accessed via:
//service.url/api/Users/99
So far, so RESTful. However, in order to truly map URIs to resources I want to do an HTTP PUT (new user) or HTTP DELETE (remove user) to the the URI listed above. In all of the examples I've seen for these projects, along with the Scaffolds provided in Visual Studio, this convention is followed:
//service.url/api/Users/Create
//service.url/api/Users/Delete/99
//service.url/api/Users/Update/99
... and so on. This feels like side-stepping the issue to me, which is a shame when what's there has been put together so nicely!
Any thoughts on how best to approach this?
What you want is the default in MVC Web API. I'm not sure what you are looking at but here is a great example of routing the Get/Post/Put/Delete to actions.
For example you may want:
public class UsersController : ApiController
{
// GET http://service.url/api/Users/1
[HttpGet]
public User GetUser(int id);
// POST http://service.url/api/Users/?name=richard...
[HttpPost]
public User AddUser(User model);
// PUT http://service.url/api/Users/?id=1&name=Richard...
[HttpPut]
public User UpdateUser(User model);
// DELETE http://service.url/api/Users/1
[HttpDelete]
public User DeleteUser(int id);
}
I've explicitly set these, but the GetUser and DeleteUser don't need the prefix because they start with the matching HTTP method.
The link provided by Erik is a good start, but I see how it can confuse the situation when looking for a simple RESTful API that makes use of the HTTP verbs to perform these CRUD actions. If you're looking to use the HTTP verbs of GET, PUT, POST, and DELETE (and possibly PATCH, but I'm not covering that here) and you're ok with using convention, then the following would work:
public class UsersController : ApiController
{
// GET http://service.url/api/Users
public User GetAllUsers(){ ... }
// GET http://service.url/api/Users/1
public User GetUser(int id){ ... }
// POST http://service.url/api/Users/
// User model is passed in body of HTTP Request
public User PostUser([FromBody]User model){ ... }
// PUT http://service.url/api/Users/1
// User model is passed in body of HTTP Request
public User PutUser(int id, [FromBody]User model){ ... }
// DELETE http://service.url/api/Users/1
public User DeleteUser(int id){ ... }
}
Note that the attributes on the method are not needed when using the HTTP verb action convention in Web API. Also, note that I use the [FromBody] attribute on the User parameter for POST and PUT to denote that the body contains the data I wish to send. This may not be most convenient for POST if you're trying to append to a resource, and I have not tried creating/modifying data through query parameters using Web API. It certainly makes the call feel very clean to place your data in the body. "POST/PUT this content in the body at this resource."
Also, the way I read PUT in the spec, and I could very well be wrong, is that it acts as a replace. That also makes sense given the last line above. I'm PUTting this resource in this location, replacing what was already there. The spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) states: "If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server." The term they use is "modified" so I guess that leaves enough room for interpretation for the end user. That's where PATCH comes in (https://www.rfc-editor.org/rfc/rfc5789), but I don't have enough information to comment on that at this time.
Related
I am creating a web service for user authentication. (I am new in c# using entity framework core)
[HttpPost, Route("login")]
public async Task<ActionResult<Usuario>> Login([FromBody] User user)
{
}
my model User has the next fields:
{
name,
password,
phone,
email
}
I don't know how to specify so that from the client side, my users can see the fields that my web service needs to receive and when I use some plugin to document my API it can be clearly seen that I ONLY need the name to be sent and password only.
I think of something like that, I hope to make myself understand:
public async Task<ActionResult<Usuario>> Login([FromBody] string email, [FromBody] string password)
so in this way when the API is documented, I would like it to be understood that you need to send email and password only
and so from the client side
{"password": "212346", "email": "myemail#hotmail.com" }
is sent
Your view model should contain ONLY the fields each API method requires. If there are fields in your request that are not required, they should not be in the method body. If you use something like Swagger to document your API, then it will show just the fields required for each method.
Generally, I hear questions like this when the developer tries to use a DTO or even a database entity as a view model (both of which are incorrect uses).
I make sure each API method has a different view model (even if the contents are identical), because most of the time, eventually they will be different, but not always at the start.
I have a working API with a bunch of controllers, with a single database specified in config file.
Now I want to make the the API multi database and make the target database a part of the url.
I use attributes on controllers now and default routing.
Startup.cs:
app.UseMVC();
FolderController.cs:
[Route("api/[controller]")]
[ApiController]
public class FoldersController : ControllerBase { ...
and action on controller:
[HttpGet("{Parent:Guid}", Name = "Get")]
public IActionResult Get(Guid Parent) {...
So what that gives me is the standard overall template that looks like this:
https://api.example.com/api/{controller}/{action}
What I'd want is to make the database a part of the url, the intuitive place being in front of the controller. I can also skip the second api bit as I'm not running anything else on that base address.
https://api.example.com/{database}/{controller}/{action}
I've been able to extract the database name by changing the controller attribute to:
[Route("{database}/[controller]")]
But then I'd have to insert code in every action method to check for route etc, with the risk of not implementing it consitently (beside the extra typing).
Ideally I'd like to add this to the default route in startup.cs, and add a service to the middleware that would check the privileges for the authenticated user on the requested database and continue as appropriate. That way I'd have my security in one place and no way to forget it in a controller.
I havent been able to figure out how to mix that with the attributes, they seem to conflict with each other.
Can this be done? Does anyone have some pointers for me get out of this?
By understand I know we can do it. You need to implement IHttpHandler.
You can refer to the following example https://www.c-sharpcorner.com/article/dynamic-and-friendly-url-using-mvc/
I have two simple routing methods:
main routing directive:
[RoutePrefix("api/accounts")]
first one
[Route("user/{id:guid}", Name = "GetUserById")]
public async Task<IHttpActionResult> GetUser(string Id)
second one
[Route("user/del/{id:guid}")]
public async Task<IHttpActionResult> DeleteUser(string id)
I wonder why if I test the first method with direct ulr ( GET ) it works:
http://localhost/ReportApp_WebApi/api/accounts/user/1533882b-e1fd-4b52-aeed-4799edbbcda6
And if I try the second link that is just a little different:
http://localhost/ReportApp_WebApi/api/accounts/user/del/1533882b-e1fd-4b52-aeed-4799edbbcda6
I get: The requested resource does not support http method 'GET'.
Can you help me?
The second link is not just a little different. In fact it is pointing to the route that you have defined for your delete method. This method expects DELETE Http verb.
When you write the url directly on the browser it performs a GET request. If you want your delete method to work with a GET verb you have to put the attribte
[HttpGet]
before or just after your Route atttribute. Although this I would not recommend to do it. You can have a HTTP client like Fiddler or Postman to test this
Web Api uses conventions when naming methods in your controllers. Because you named your method DeleteUser, Web Api expects Delete verb.
EDIT>>>Please follow recommendations listed in comments. I have also make bold the part where I do not recommend this
I have next API methods:
Restore database (base on file name).
Copy something from folder A to folder B
So this are API method which does not Post anything and does not return(Get) anything.
Should I use Post,Put or Get in case like this?
Currently I am using Get, is this OK?
It make sense to use Get when we return some data, but I don't need to return any data.
At the same time I don't need to post any object, I only need identifier.
Restore from folder on server base on 'id' and 'file name':
[HttpGet]//**put?
[Route("api/[controller]/[action]/{restoreFileName}")]
public async Task<IActionResult> RestoreDB(string restoreFileName)
{
//Restore database from folder A on server base on restoreFileName
return new OkResult();
}
Copy from folder A to folder B:
[HttpGet]//**put?
[Route("api/[controller]/{id}/[action]/{filename}")]
public async Task<IActionResult> CopyFromAtoB(int id, string fileName)
{
//Copy from folder A to folder B base on 'id' and 'file name'.
return new OkResult();
}
The problem is your API doesn't follow REST principles. Endpoints should represent resources on which you can perform different actions (GET, POST, PUT, PATCH, DELETE). Your endpoint:
[Route("api/[controller]/[action]/{restoreFileName}")]
Doesn't represent any resource. It's just an URI path. Neither controller, action or restoreFileName is a resource.
So, you have two options.
Just ignore any REST conventions and just create your own custom paths. If you choose this option, you don't need to do anything. You can use any verb you wish, any naming you wish. However, even in such case POST verb is the most natural one (it does make some change to the system).
Refactor your API to follow REST.
If you choose the second option, then your routes should like like this:
[HttpPost]
[Route("api/fileManager/files/{filename}")]
And use a command which you will send in the POST body to distinguish between operations on the file, so e.g.: {action: "restore"}
Since a few days I'm trying to create my own web api controller. Duo to the rest conventions I need to use a post request to create an object. To get concrete, Im having this controller with this action:
public class ReservationController : ApiController
{
[HttpPost]
public void Create(int roomId, DateTime arrivalDate)
{
//do something with both parameters
}
}
This code is not working when I fire a post request at it, I'm receiving a 404 exception something like this:
No action was found on the controller 'Some' that matches the request.
The reason for it is that simple types are read from the query string, complex types from the body, according to this aricle. The web api uses the parameters to match the action to a request and can't therefore map my action to the request.
I do know that I can use the [frombody] tag, but you can only apply that to one parameter and I have 2. I also know that I can create a wrapper object which have both the parameters, but I'm not willing to use wrappers for all my calls.
So I do know that I can work around this by these methods. I also think that this is caused by the fact that the body of the post request can only be read once. But my actual question is:
Why is the source of a parameter determined by it's type and not by it's availability, especially when the conventions state that you should make for example a post request for creation? In MVC this is the case, why isn't it in the web api?
Best regards,
BHD
FINAL UPDATE
Since I'm getting some upvotes, problably more people are facing the same question. In the end it comes to this: Web-Api != MVC. It's simply not the same thing and the web api team made different design decisions than the mvc team I guess.
It seems that you have a fundamental misunderstanding of how Web API actually works.
Web API routing is driven off of verbiage, not the method names. "SomeMethod" actually translates to zero useful information for Web API. As a result, if I post
api/some/some?id=1
OR
api/some/somemethod?id=1
OR EVEN
api/some/?id=1
and the SomeMethod endpoint is the ONLY available POST, it will hit that endpoint.
As such, first of all, make sure you have only one POST on that api controller. If you do, POSTing to it from any test client using either of the query strings above will work just fine.
You can use the [FromBody] attribute on the parameter to force it to read from the body of the HTTP POST instead of the Uri. This is opposed to the [FromUri] attribute which does the opposite.
[HttpPost]
public void SomeAction([FromBody] int id)
{
//do something with id
}
Are you sure you're actually putting the id in the body? It could also be a routing issue. If this still doesn't work then maybe you should use Fiddler and copy the RAW output of your HTTP message here.
If you're packing multiple values into the body such as with JSON then you should use a model which should automatically be deserialized to:
public class PostModel
{
public int ID { get; set; }
public int SomeOtherID { get; set; }
}
[HttpPost]
public void SomeAction(PostModel postModel)
{
//do something with postModel.ID and postModel.SomeOtherID
}
You can actually do this straight out of the box in WebAPI, at least in 2.2 (.Net version 4.5.2). Your controller is correct. Using your controller, if you call it with a HTTP POST like this (tested through Fiddler):
http://localhost:58397/api/Reservation?roomId=123&arrivalDate=2015-12-17
You'll get the correct values of roomId = 123 and arrivalDate = 17.12.2015.
I suspect there's something wrong in your call to the WebAPI. Maybe post that call if you're still not getting it to work.