ASP.NET API routing - c#

I'm working on an ASP.NET 5 API and understand that, in order to make the API as "restful" as possible, we use Http verbs as method names.
My question is what happens if I have multiple methods that do different things and all have to be HttpPost?
Say, I have a method that I may call to update a user's first name and may have another method that I use to update the user's city. In both cases, the input parameters will be user ID (GUID) and value which is a string.
[HttpPost("id")]
public void Post([FromRoute]id, [FromBody]firstName)
{
// Change user's first name
}
[HttpPost("id")]
public void Post([FromRoute]id, [FromBody]city)
{
// Change user's city
}
How do I name my methods in this case?

To have 2 post methods that do different things, use the "ActionName" attribute.
[HttpPost("id")]
[ActionName("FirstNamePost")]
public void FirstNamePost([FromRoute]id, [FromBody]firstName)
{
// Change user's first name
}
[HttpPost("id")]
[ActionName("CityPost")]
public void CityPost([FromRoute]id, [FromBody]city)
{
// Change user's city
}
So you would call "www.mysite.com/api/citypost/1" or "www.mysite.com/api/FirstNamePost/1"
Another option would be to only have one post method and add a 3rd parameter to distinguish between a name update or city update.

You can give the controller a route prefix
[RoutePrefix("Note")]
public class NoteController
then give a specific route to whatever action
[Route("", Name = "Note")]
[HttpGet]
public async Task<IHttpActionResult> Get (string tenantName, [FromBody] TagTdo entity)
[Route("", Name = "CreateNote")]
[HttpPost]
public async Task<IHttpActionResult> Post (string tenantName, [FromBody] NoteDto entity)
[Route("Update\{id}", Name = "UpdateNote")]
[HttpPut]
public async Task<IHttpActionResult> Put(string tenantName, [FromBody] NoteDto entity)
Then the route will be:
\Note [GET]
\Note [POST]
\Note\Update\4 [PUT]
Also as a naming convention DON'T use long name in the route, break into several words with slash and http verb
Don't use GetUserContacts, use User\Contacts [GET].

Related

How to apply optional parameter in the middle of a route in c#?

I have a controller as
[ApiController]
[Route("api/getData/")]
and the GET method with route
[HttpGet("{name}/{message}/{num}")]
{
public async GetAsync(string name, string message,int num) {some code}
}
Now I also want to send 'message' as optional without changing the route and set it to default value of empty string in the method. So I am using both these functions now, independently they both work fine but not together.
[HttpGet("{name}/{num}/{message?}")]
{
public async GetAsyncOptional(string name, int num , string message="") {some code}
}
But I keep getting the error in StartUp.cs in
app.UseEndpoints(endpoint => {endpoint.MapController();
System.ArgumentException:'An optional parameter cannot have default value.(Parameter 'route template')
your two routes are overlapped, you have to give action name to separate them
[HttpGet("/~api/getDataOptional/{name}/{num}/{message?}")]
public async Task<ActionReslt> GetAsyncOptional(string name, int num , string message="")
{
...
}
or I don't see why dont' use one route for two, only if a query string parameters order is very important for you.
I can advise you to make class for request like, because you approach in answer seems like bad:
1)
class RequestData
{
string Name {get;set;}
int Age {get;set;}
string Message {get;set;}
}
Then make your controller:
[ApiController]
[Route("api/data")]
public class DataController {}
And method is:
3)
[HttpGet]
public async Task<ActionResult> GetAsyncOptional([FromBody] RequestData data)
{
//
}
That's more complex because if you extend your format for 4 and 5 argument, you will change your route one more time, It is not necessary. Optional params can be in request body.

asp.net core Api - insert new row in database

I'm trying to create an address object in a database through an asp.net core api. I'm using Postman to invoke the method.
Class:
namespace LC.Tools.API.Controllers {
[Route("api/[controller]")]
public class MailerController : Controller
{
Data.LCToolsDbContext _context;
public MailerController(Data.LCToolsDbContext context)
{
_context = context;
}
[HttpPost]
public async Task<IActionResult> CreateAddress(int client, string name, string email)
{
Address adr = new Address() { ClientId = client, Name = name, Email = email };
_context.MailerAddresses.Add(adr);
await _context.SaveChangesAsync();
return Ok(adr);
}
} }
URL (using POST):
http://localhost:50444/api/mailer/createAddress?client=1&name=test&email=mail#mail.no
I also have a break point in the method, but it never hits. I don't get any error message, it just doesn't do anything.
You can see #Rick van den Bosch's comment but still you would like to specify the route with action. use this
[Route("api/[controller]/[action]")]
See #Ayvaras' comment. Since you're building a Web API controller, you don't have to specify the action name in your URL. The HttpPost points all POST actions to the mailer controller towards the CreateAddress method.
Your POST URL should be:
http://localhost:50444/api/mailer?client=1&name=test&email=mail#mail.no
Problem solved! Thanks Ayvaras,
[Route("api/[controller]/[action]")]
did the trick. Now that the method is found I can look into how to pass an object instead of using querystring
You are not using POST correctly.
If you use POST you should not be sending parameters through the query string, you should use the request body. Even though, if you still have to send them via query string you should use the FromQuery attribute in your action parameters.
[HttpPost]
public async Task<IActionResult> CreateAddress([FromQuery] int client, [FromQuery] string name, [FromQuery] string email)
{
Address adr = new Address() { ClientId = client, Name = name, Email = email };
_context.MailerAddresses.Add(adr);
await _context.SaveChangesAsync();
return Ok(adr);
}
Edit: Use [Route("api/[controller]/[action]")] to append the action name in your action route.
Instead of changing the controllers routing scheme, it's also possible to specify the endpoint name by doing the following
[HttpPost("createAddress")]
public async Task<IActionResult> CreateAddress(int client, string name, string email)
{
[...]
}

How to fix - The requested resource does not support http method 'POST'

Below is WebAPI action. On googling about the below error:-
The requested resource does not support http method 'POST'
I got number of links & updated my api accordingly but still I am getting the same error.
Web api not supporting POST method
ASP.NET Web Api: The requested resource does not support http method 'GET'
[AcceptVerbs("POST")]
[HttpPost]
[Route("rename/{userId}/{type}/{title}/")]
public IHttpActionResult Rename([FromBody] int userId, [FromBody] string type, [FromBody] string title)
{
//my api stuff
}
But still when calling the above via post man throws the error.
How do I get rid of this error??
Also is it possible to fix this without using [FromBody] attribute in the method parameters list?
Any help/suggestion highly appreciated.
Thanks.
You have declared route which requires url parameters
[Route("rename/{userId}/{type}/{title}/")]
So when you send request to api/customer/rename it does not match this method. You should remove parameters which you are passing in request body from route parameters
[Route("rename")]
Make sure that you have appropriate RoutePrefix("api/customer") attribute on your controller.
Second problem is multiple [FromBody] parameters. You will get can't bind multiple parameters error. There is limitation - you can mark only one parameter as FromBody. See Sending Simple Types notes:
Web API reads the request body at most once, so only one parameter of
an action can come from the request body. If you need to get multiple
values from the request body, define a complex type.
You should create complex type which will hold all parameters
public class RenameModel
{
public int UserId { get; set; }
public string Type { get; set; }
public string Title { get; set; }
}
And change method signature to
[HttpPost]
[Route("rename")]
public IHttpActionResult Rename(RenameModel model)
And send request data as application/x-www-form-urlencoded
[Route("rename/{userId}/{type}/{title}/")]
public IHttpActionResult Rename([FromBody] int userId, [FromBody] string type, [FromBody] string title)
The last answer is correct, you're asking for these parameters in the route, but saying that you expect them in the post body. Also, usually the route would begin with a noun rather than a verb. What is it you're renaming? (i.e. [Route("users/rename/{userId}/{type}/{title}")]
Based on your initial post, try this instead:
[HttpPost]
[Route("rename/{userId}/{type}/{title}" Name = "RenameUser"]
public IHttpActionResult Rename(int userId, string type, string title)
{
_myServiceMethod.Rename(userId, type, title);
return new StatusCodeResult(HttpStatusCode.Created, this);
}
Or, if you wanted to do a post with the info in the body:
Declare your data contract:
public class User
{
public string Type { get; set; }
public string Title { get; set; }
}
Then on the endpoint:
[HttpPost]
[Route("rename/{userId}", Name = "RenameUserPost")]
public IHttpActionResult RenameUserPost(int userId, [FromBody] User userData)
{
return new StatusCodeResult(HttpStatusCode.Created, this);
}
Note that in both returns 'this' refers to your controller class that inherits from ApiController. Verified both of these in swagger, and they accept POSTs and return status codes.
Hope this helps.
I had this error for wrong string in Route string on top of my action.
[Route("api/TestReaderPercentStudyHomework/AddOrUpdate")]

Web API 2 / MVC 5 : Attribute Routing passing parameters as querystring to target different actions on same controller

I've been playing with the new Web API 2 (which looks very promising btw) but I'm having a bit of a headache to get some routes working.
All works fine when I have GetAllUsers / GetUser(int id), but then when I add GetUserByName(string name) and/or GetUserByUsername(string username) things start to be creepy. I know that the int will be the first one and that I can re-order the routes but let's imagine the following scenario:
A user can have a valid username=1234 or name=1234 (I know it's unlikely but we need to prevent any possible situation) and we might have a valid 1234 ID in the database and all the routes will be mixed up.
Maybe this is something that we will need to work with on the new WebAPI 2 so I thought I could come with an "workaround" passing filters as querystrings to target different action in the same controller, such as api/users/?username=1234 (GetUserByUsername) or api/users/?name=1234 (GetUserByName)
But I cannot make querystrings to come through ... actually any querystring option above is getting caught by the GetAllUsers.
Does anyone have any suggestion/fix for that scenario?
Thanks a lot
You need to define the method access name like
[HttpGet("User")]
public async Task<UserViewModel> GetByName(string name)
[HttpGet("User")]
public async Task<UserViewModel> GetByUserName(string name)
//You can access like
//- api/Users/User?name=someneme
//- api/Users/User?username=someneme
OR
[HttpGet("User")]
public async Task<UserViewModel> GetByAnyName(string name="", string username="")
//- api/Users/User?name=someneme
//- api/Users/User?username=someneme
//- api/Users/User?username=someneme&name=someone
UPDATED
Above both will work nicely with other configurations of route prefix.
OR
[HttpGet("")]
public async Task<UserViewModel> GetAll()
[HttpGet("")]
public async Task<UserViewModel> Get(int id)
[HttpGet("")]
public async Task<UserViewModel> GetByName(string name)
[HttpGet("")]
public async Task<UserViewModel> GetByUserName(string name)
//You can access like
//- api/Users/
//- api/Users/?id=123
//- api/Users/?name=someneme
//- api/Users/?username=someneme

Can you overload controller methods in ASP.NET MVC?

I'm curious to see if you can overload controller methods in ASP.NET MVC. Whenever I try, I get the error below. The two methods accept different arguments. Is this something that cannot be done?
The current request for action 'MyMethod' on controller type 'MyController' is ambiguous between the following action methods:
You can use the attribute if you want your code to do overloading.
[ActionName("MyOverloadedName")]
But, you'll have to use a different action name for the same http method (as others have said). So it's just semantics at that point. Would you rather have the name in your code or your attribute?
Phil has an article related to this: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx
Yes. I've been able to do this by setting the HttpGet/HttpPost (or equivalent AcceptVerbs attribute) for each controller method to something distinct, i.e., HttpGet or HttpPost, but not both. That way it can tell based on the type of request which method to use.
[HttpGet]
public ActionResult Show()
{
...
}
[HttpPost]
public ActionResult Show( string userName )
{
...
}
One suggestion I have is that, for a case like this, would be to have a private implementation that both of your public Action methods rely on to avoid duplicating code.
Here's something else you could do... you want a method that is able to have a parameter and not.
Why not try this...
public ActionResult Show( string username = null )
{
...
}
This has worked for me... and in this one method, you can actually test to see if you have the incoming parameter.
Updated to remove the invalid nullable syntax on string and use a default parameter value.
No,No and No. Go and try the controller code below where we have the "LoadCustomer" overloaded.
public class CustomerController : Controller
{
//
// GET: /Customer/
public ActionResult LoadCustomer()
{
return Content("LoadCustomer");
}
public ActionResult LoadCustomer(string str)
{
return Content("LoadCustomer with a string");
}
}
If you try to invoke the "LoadCustomer" action you will get error as shown in the below figure.
Polymorphism is a part of C# programming while HTTP is a protocol. HTTP does not understand polymorphism. HTTP works on the concept's or URL and URL can only have unique name's. So HTTP does not implement polymorphism.
In order to fix the same we need to use "ActionName" attribute.
public class CustomerController : Controller
{
//
// GET: /Customer/
public ActionResult LoadCustomer()
{
return Content("LoadCustomer");
}
[ActionName("LoadCustomerbyName")]
public ActionResult LoadCustomer(string str)
{
return Content("LoadCustomer with a string");
}
}
So now if you make a call to URL "Customer/LoadCustomer" the "LoadCustomer" action will be invoked and with URL structure "Customer/LoadCustomerByName" the "LoadCustomer(string str)" will be invoked.
The above answer i have taken from this codeproject article --> MVC Action overloading
To overcome this problem you can write an ActionMethodSelectorAttribute that examines the MethodInfo for each action and compares it to the posted Form values and then rejects any method for which the form values don't match (excluding the button name, of course).
Here's an example:- http://blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/
BUT, this isn't a good idea.
As far as I know you can only have the same method when using different http methods.
i.e.
[AcceptVerbs("GET")]
public ActionResult MyAction()
{
}
[AcceptVerbs("POST")]
public ActionResult MyAction(FormResult fm)
{
}
I have achieved this with the help of Attribute Routing in MVC5. Admittedly I am new to MVC coming from a decade of web development using WebForms, but the following has worked for me. Unlike the accepted answer this allows all the overloaded actions to be rendered by the same view file.
First enable Attribute Routing in App_Start/RouteConfig.cs.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Optionally decorate your controller class with a default route prefix.
[RoutePrefix("Returns")]
public class ReturnsController : BaseController
{
//.......
Then decorate your controller actions that overload each other with a common route and parameters to suit. Using type constrained parameters you can use the same URI format with IDs of different types.
[HttpGet]
// Returns
public ActionResult Index()
{
//.....
}
[HttpGet]
[Route("View")]
// Returns/View
public ActionResult View()
{
// I wouldn't really do this but it proves the concept.
int id = 7026;
return View(id);
}
[HttpGet]
[Route("View/{id:int}")]
// Returns/View/7003
public ActionResult View(int id)
{
//.....
}
[HttpGet]
[Route("View/{id:Guid}")]
// Returns/View/99300046-0ba4-47db-81bf-ba6e3ac3cf01
public ActionResult View(Guid id)
{
//.....
}
Hope this helps and is not leading somebody down the wrong path. :-)
You could use a single ActionResult to deal with both Post and Get:
public ActionResult Example() {
if (Request.HttpMethod.ToUpperInvariant() == "GET") {
// GET
}
else if (Request.HttpMethod.ToUpperInvariant() == "POST") {
// Post
}
}
Useful if your Get and Post methods have matching signatures.
I've just come across this question and, even though it's quite old now, it's still very relevant. Ironically, the one correct comment in this thread was posted by a self-confessed beginner in MVC when he wrote the post. Even the ASP.NET docs are not entirely correct. I have a large project and I successfully overload action methods.
If one understands routing, beyond the simple {controller}/{action}/{id} default route pattern, it might be obvious that controller actions can be mapped using any unique pattern. Someone here talked about polymorphism and said: "HTTP does not understand polymorphism", but routing has nothing to do with HTTP. It is, simply put, a mechanism for string pattern matching.
The best way to make this work is to use the routing attributes, for example:
[RoutePrefix("cars/{country:length(3)}")]
public class CarHireController
{
[Route("{location}/{page:int=1}", Name = "CarHireLocation")]
public ActionResult Index(string country, string location, int page)
{
return Index(country, location, null, page);
}
[Route("{location}/{subLocation}/{page:int=1}", Name = "CarHireSubLocation")]
public ActionResult Index(string country, string location, string subLocation, int page)
{
//The main work goes here
}
}
These actions will take care of urls like /cars/usa/new-york and /cars/usa/texas/dallas, which will map to the first and second Index actions respectively.
Examining this example controller it's evident that it goes beyond the default route pattern mentioned above. The default works well if your url structure exactly matches your code naming conventions, but this is not always the case. Code should be descriptive of the domain, but urls often need to go further because their content should be based on other criteria, such as SEO requirements.
The benefit of the default routing pattern is that it automatically creates unique routes. This is enforced by the compiler since urls will match unique controller types and members. Rolling your own route patterns will require careful thought to ensure uniqueness and that they work.
Important note The one drawback is that using routing to generate urls for overloaded actions does not work when based on an action name, e.g., when using UrlHelper.Action. But it does work if one uses named routes, e.g., UrlHelper.RouteUrl. And using named routes is, according to well respected sources, the way to go anyhow (http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/).
Good luck!
You can use [ActionName("NewActionName")] to use the same method with a different name:
public class HomeController : Controller
{
public ActionResult GetEmpName()
{
return Content("This is the test Message");
}
[ActionName("GetEmpWithCode")]
public ActionResult GetEmpName(string EmpCode)
{
return Content("This is the test Messagewith Overloaded");
}
}
I needed an overload for:
public ActionResult Index(string i);
public ActionResult Index(int groupId, int itemId);
There were few enough arguments where I ended up doing this:
public ActionResult Index(string i, int? groupId, int? itemId)
{
if (!string.IsNullOrWhitespace(i))
{
// parse i for the id
}
else if (groupId.HasValue && itemId.HasValue)
{
// use groupId and itemId for the id
}
}
It's not a perfect solution, especially if you have a lot of arguments, but it works well for me.
I have faced same issue in my application too. Without Modifiyig any Method information, I have provided [ActionName("SomeMeaningfulName")] on Action head. issue resolved
[ActionName("_EmployeeDetailsByModel")]
public PartialViewResult _EmployeeDetails(Employee model)
{
// Some Operation
return PartialView(model);
}
}
[ActionName("_EmployeeDetailsByModelWithPagination")]
public PartialViewResult _EmployeeDetails(Employee model,int Page,int PageSize)
{
// Some Operation
return PartialView(model);
}
Create the base method as virtual
public virtual ActionResult Index()
Create the overridden method as override
public override ActionResult Index()
Edit: This obviously applies only if the override method is in a derived class which appears not to have been the OP's intention.
I like this answer posted in another thread
This is mainly used if you inherit from another controller and want to override an acction from the base controller
ASP.NET MVC - Overriding an action with differing parameters
There is only one public signature allowed for each controller method. If you try to overload it, it will compile, but you're getting the run-time error you've experienced.
If you're not willing to use different verbs (like the [HttpGet] and [HttpPost] attributes) to differentiate overloaded methods (which will work), or change the routing, then what remains is that you can either provide another method with a different name, or you can dispatch inside of the existing method. Here's how I did it:
I once came into a situation where I had to maintain backwards compatibility. The original method expected two parameters, but the new one had only one. Overloading the way I expected did not work because MVC didn't find the entry point any more.
To solve that, I did the following:
Changed the 2 overloaded action methods from public to private
Created one new public method which contained "just" 2 string parameters. That one acted as a dispatcher, i.e.:
public ActionResult DoSomething(string param1, string param2)
{
if (string.IsNullOrEmpty(param2))
{
return DoSomething(ProductName: param1);
}
else
{
int oldId = int.Parse(param1);
return DoSomething(OldParam: param1, OldId: oldId);
}
}
private ActionResult DoSomething(string OldParam, int OldId)
{
// some code here
return Json(result);
}
private ActionResult DoSomething(string ProductName)
{
// some code here
return Json(result);
}
Of course, this is a hack and should be refactored later. But for the time being, it worked for me.
You can also create a dispatcher like:
public ActionResult DoSomething(string action, string param1, string param2)
{
switch (action)
{
case "update":
return UpdateAction(param1, param2);
case "remove":
return DeleteAction(param1);
}
}
You can see, that UpdateAction needs 2 parameters, while DeleteAction just needs one.
Sorry for the delay. I was with the same problem and I found a link with good answers, could that will help new guys
All credits for BinaryIntellect web site and the authors
Basically, there are four situations: using differents verbs, using routing, overload marking with [NoAction] attribute and change the action attribute name with [ActionName]
So, depends that's your requiriments and your situation.
Howsoever, follow the link:
Link:
http://www.binaryintellect.net/articles/8f9d9a8f-7abf-4df6-be8a-9895882ab562.aspx
This answer for those who struggling with the same issue. You can
implement your own custom filter based on
ActionMethodSelectorAttribute. Here I found the best solution
for solving your question. Works fine on .net 5 project.
If you try to implement the same logic as was in web api controllers then use Microsoft.AspNetCore.Mvc.WebApiCompatShim. This nuget package provides compatibility in ASP.NET Core MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations. Please check this answer but consider that
starting with ASP.NET Core 3.0, the Microsoft.AspNetCore.Mvc.WebApiCompatShim package is no longer available.
If this is an attempt to use one GET action for several views that POST to several actions with different models, then try add a GET action for each POST action that redirects to the first GET to prevent 404 on refresh.
Long shot but common scenario.

Categories

Resources