ASP.NET Core Web API: Routing by method name? - asp.net

I remember from ASP.NET Web API that it's sufficient to prefix Web API REST method names with HTTP commands (e.g. GetList() => HTTP GET, Delete() => HTTP DELETE) to have incoming calls appropriately routed.
I also remember that in ASP.NET Web API parameter matching takes place so that even Get(int id) and Get(int id, string name) get automatically and appropriately routed without requiring any attributes.
public class MyController
{
public ActionResult Get(int id) => ...
public ActionResult Get(int id, string name) => ...
public ActionResult DeleteItem(int id) => ...
}
Isn't this all available in ASP.NET Web API Core?

You just need to add the Route to the top of your controller.
Specify the route with api, controller and action:
[Route("api/[controller]/[action]")]
[ApiController]
public class AvailableRoomsController : ControllerBase
{
...
}

Neither could we do action overloads nor prefix action name as Http verb.The way routing works in ASP.NET Core is different than how it did in ASP.NET Web Api.
However, you can simply combine these actions and then branch inside, since all params are optional if you send as querystring
[HttpGet]
public ActionResult<string> Get(int id, string name)
{
if(name == null){..}
else{...}
}
Or you need to use attribute routing to specify each api if you send in route data:
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
[HttpGet("{id}/{name}")]
public ActionResult<string> Get(int id, string name)
{
return name;
}
Refer to Attribute Routing,Web Api Core 2 distinguishing GETs

The aspnetcore webapi controllers do not natively support inference of http verbs by naming convention, but allow you to create your own convention and achieve this behavior.
Create your convention
public class MyConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach(var controller in application.Controllers)
{
foreach(var action in controller.Actions)
{
if (action.ActionName.StartsWith("Post"))
{
action.Selectors.First().ActionConstraints.Add(new HttpMethodActionConstraint(new[]{ "POST" }));
}
}
}
}
}
Then register it in Program/Startup:
builder.Services.AddControllers(configure => configure.Conventions.Insert(0, new MyConvention()));

This is available for Core 2 yes, but the way that I know how to do it is something like this
[Route("api/[controller]")]
[ApiController]
public class AvailableRoomsController : ControllerBase
{
private readonly ApplicationContext _context;
public AvailableRoomsController(ApplicationContext context)
{
_context = context;
}
// GET: api/AvailableRooms
[HttpGet]
public async Task<ActionResult<IEnumerable<AvailableRoom>>> GetAvailableRooms()
{
return await _context.AvailableRooms.ToListAsync();
}
// POST: api/AvailableRooms
[HttpPost]
public async Task<ActionResult<AvailableRoom>> PostAvailableRoom(AvailableRoom availableRoom)
{
_context.AvailableRooms.Add(availableRoom);
await _context.SaveChangesAsync();
return CreatedAtAction("GetAvailableRoom", new { id = availableRoom.Id }, availableRoom);
}
[HttpPut] .... etc
}
Now depending on what kind of REST action you specify and what type of model you send to "api/AvailableRooms" if the proper Action exists it will be chosen.
Visual Studio 2019 and I think 2017 can create a controller such as this automatically if you right click your Controllers folder and click Add->Controller and then choose "API Controller with actions, using Entity Framework" and choose one of your Model classes.

Related

Return correct 201 when using controller actions attribute routing

Net core 3 web api. It uses attribute routing for controllers actions. Few separate controllers with post methods (e.g. CreatedContract) should return 201 where routeName related to : GetOperationsById. Example is very simplified comparing to actual app, so please lets not put to much attention to it style
How to set route name to CreatedAtRoute correctly:
[ApiController]
public class OperationsController : ControllerBase
{
[HttpGet]
[Route("contractsOperation/{id}")]
public async Task<IActionResult> GetOperationsById(int id)
{
var operation = _service.GetOperation(id);
return Ok(operation);
}
}
[ApiController]
public class ContractsController : ControllerBase
{
[HttpPost]
[Route("saveContract")]
public async Task<IActionResult> CreatedContract(string jsonData)
{
var newlyCreatedContract = _service.Create(jsonData);
//var route = this value should point out to OperationsController -> GetOperationsById
return CreatedAtRoute(route, new { newlyCreatedContract.Id }, newlyCreatedContract);
}
}
Thank you
According to your description, I suggest you could try to add a route name to the GetOperationsById route attribute and then use this attribute name as the parameter to the CreatedAtRoute method.
More details, you could refer to below example:
Other controller method:
[HttpGet]
[Route("contractsOperation/{id}", Name = nameof(GetOperationsById))]
public async Task<IActionResult> GetOperationsById(int id)
{
return Ok("aa");
}
Call it:
[HttpGet]
public IActionResult Index()
{
return CreatedAtRoute(nameof(FooController.GetOperationsById), new { id = 1 }, new { id=1});
}
Result:

ASP.NET Core Route inside controller

I am trying to route url with and without parameter to two different methods but for some reason it always start first one.
Here is controller code:
public class ProductController : Controller
{
[Route("")]
[Route("Product")] //If i delete this one then it works how it is intended
public IActionResult Index()
{
//It always start this one
....
}
[Route("Product/{GroupID?}")]
public IActionResult Index(int GroupID)
{
....
}
}

Can you have multiple get action methods in asp net web api controller according to RESTful API constraints?

I have the following interface in my business layer
public interface IUserService
{
void CreateUser(User user);
List<User> FindUsersByName(string searchedString);
User GetUserById(int userId);
User GetUserByCredentials(string login, string password);
void UpdateUser(User user);
void UpdateUserPassword(int userId, string oldPassword, string newPassword);
}
Now I want to provide web api for this interface. As you can see this interface has multiple get methods that return one item GetUserById and GetUserByCredentials, it also has multiple update methods UpdateUser and UpdateUserPassword, in future I might want to add aditional get method that returns a collection, like, GetAllUsers for instance.
The obvious solution was to encapsulate this functionality in one controller.
So what I did first, in WebApiConfig I changed routes configuration to
config.Routes.MapHttpRoute(
name: "DefaultApi",
//as you can see I added {action} to the path so that, it will be possible to differentiate between different get/put requests
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then I created a UsersController that looks like this
public class UsersController : ApiController
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
// POST api/users/createuser
[HttpPost]
public IHttpActionResult CreateUser(User user)
{
//some code
}
// GET api/users/getuserbyid?id=1
[HttpGet]
public IHttpActionResult GetUserById(int id)
{
//some code
}
// GET api/users/getuserbycredentials?login=log&password=pass
[HttpGet]
public IHttpActionResult GetUserByCredentials(string login, string password)
{
//some code
}
// GET api/users/findusersbyname?searchedString=jack
[HttpGet]
public IHttpActionResult FindUsersByName(string searchedString)
{
//some code
}
// PUT api/users/updateuser
[HttpPut]
public IHttpActionResult UpdateUser(UserBase user)
{
//some code
}
// PUT api/users/updateuserpassword?userId=1&oldPassword=123&newPassword=1234
[HttpPut]
public IHttpActionResult UpdateUserPassword(int userId, string oldPassword, string newPassword)
{
//some code
}
}
As you can see from the code above I have different URIs for each action method, e.g., for GetUserById - api/users/getuserbyid?id=1, for GetUserByCredentials - api/users/getuserbycredentials?login=log&password=pass and so on. This solution works fine so far, but the problem is, as far as I know you cannot have multiple gets according to REST, so does this solution still comply with the constraints for a RESTful service? And if not how can I make it truly RESTful? The idea of splitting this interface into different controllers seems a little odd to me, because in the future I may want to add some new methods to my interface, like, GetUsersByGender, GetUsersByDateOfBirthday and so on (if I'm going to create a new controller each time, that doesn't sound right to me)

ASP.NET Web API Contract Versioning

We would like to achieve version based API using content negotiation in accept header.
We are able to achieve for controller & API methods with some inheritance and extending the default HTTP selector.
Controller inheritance is achieved using following sample code,
public abstract class AbstractBaseController : ApiController
{
// common methods for all api
}
public abstract class AbstractStudentController : AbstractBaseController
{
// common methods for Student related API'sample
public abstract Post(Student student);
public abstract Patch(Student student);
}
public class StudentV1Controller : AbstractStudentController
{
public override Post([FromBody]Student student) // student should be instance of StudentV1 from JSON
{
// To Do: Insert V1 Student
}
public override Patch([FromBody]Student student) // student should be instance of StudentV1 from JSON
{
// To Do: Patch V1 Student
}
}
public class StudentV2Controller : AbstractStudentController
{
//
public override Post([FromBody]Student student) // student should be instance of StudentV2 from JSON
{
// To Do: Insert V2 Student
}
}
public abstract class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class StudentV1 : Student
{
}
public class StudentV2 : Student
{
public string Email { get; set; }
}
We have created above architecture to do less code with change in version, say if version 1 has 10 API methods and there is a change in one API method than it should be available in version 2 code without modifying other 9(they are inherited from version 1).
Now, the main problem we are facing is in contract versioning as we cannot instantiate an instance of an abstract student. When someone is posting JSON to API version 1 instance of StudentV1 should be passed in methods and same in version 2.
Is there any way to achieve this?
Thanks in advance!!
ASP.NET API Versioning is capable of achieving your goals. First, you'll want to add a reference to the ASP.NET Web API API Versioning NuGet package.
You would then configure your application something like:
public class WebApiConfig
{
public static void Configure(HttpConfiguration config)
{
config.AddApiVersioning(
options => options.ApiVersionReader = new MediaTypeApiVersionReader());
}
}
Your controllers might look something like:
namespace MyApp.Controllers
{
namespace V1
{
[ApiVersion("1.0")]
[RoutePrefix("student")]
public class StudentController : ApiController
{
[Route("{id}", Name = "GetStudent")]
public IHttpActionResult Get(int id) =>
Ok(new Student() { Id = id });
[Route]
public IHttpActionResult Post([FromBody] Student student)
{
student.Id = 42;
var location = Link("GetStudent", new { id = student.Id });
return Created(location, student);
}
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] Student student) =>
Ok(student);
}
}
namespace V2
{
[ApiVersion("2.0")]
[RoutePrefix("student")]
public class StudentController : ApiController
{
[Route("{id}", Name = "GetStudentV2")]
public IHttpActionResult Get(int id) =>
Ok(new Student() { Id = id });
[Route]
public IHttpActionResult Post([FromBody] StudentV2 student)
{
student.Id = 42;
var location = Link("GetStudentV2", new { id = student.Id });
return Created(location, student);
}
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] StudentV2 student) =>
Ok(student);
}
}
}
I strongly advise against inheritance. It's possible, but is the wrong approach to the problem IMO. Neither APIs nor HTTP support inheritance. That is an implementation detail of the backing language, which is also somewhat of an impedance mismatch. A key problem is that you cannot uninherit a method and, hence, nor an API.
If you really insist on inheritance. Choose one of the following options:
Base class with only protected members
Move business logic out of the controllers
Use extension methods or other collaborators to fulfill shared operations
For example, you might do something like this:
namespace MyApp.Controllers
{
public abstract class StudentController<T> : ApiController
where T: Student
{
protected virtual IHttpActionResult Get(int id)
{
// common implementation
}
protected virtual IHttpActionResult Post([FromBody] T student)
{
// common implementation
}
protected virtual IHttpActionResult Patch(int id, [FromBody] Student student)
{
// common implementation
}
}
namespace V1
{
[ApiVersion("1.0")]
[RoutePrefix("student")]
public class StudentController : StudentController<Student>
{
[Route("{id}", Name = "GetStudentV1")]
public IHttpActionResult Get(int id) => base.Get(id);
[Route]
public IHttpActionResult Post([FromBody] Student student) =>
base.Post(student);
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] Student student) =>
base.Patch(student);
}
}
namespace V2
{
[ApiVersion("2.0")]
[RoutePrefix("student")]
public class StudentController : StudentController<StudentV2>
{
[Route("{id}", Name = "GetStudentV2")]
public IHttpActionResult Get(int id) => base.Get(id);
[Route]
public IHttpActionResult Post([FromBody] StudentV2 student) =>
base.Post(student);
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] StudentV2 student) =>
base.Patch(student);
}
}
}
There are other ways, but that is one example. If you define a sensible versioning policy (ex: N-2 versions), then the amount of duplication is minimal. Inheritance will likely cause more problems than it solves.
When you version by media type, the default behavior uses the v media type parameter to indicate the API version. You can change name if you wish. Other forms of versioning by media type are possible (ex: application/json+student.v1, you'd need a custom IApiVersionReader as there is no standard format. In addition, you'll have to update the ASP.NET MediaTypeFormatter mappings in the configuration. The built-in media type mapping does not consider media type parameters (e.g. the v parameter has no impact).
The following table shows the mapping:
Method
Header
Example
GET
Accept
application/json;v=1.0
PUT
Content-Type
application/json;v=1.0
POST
Content-Type
application/json;v=1.0
PATCH
Content-Type
application/json;v=1.0
DELETE
Accept or Content-Type
application/json;v=1.0
DELETE is an outlier case as it doesn't require a media type in or out. Content-Type will always take precedence over Accept because it is required for the body. A DELETE API can be made API version-neutral, meaning will take any API version, including none at all. This may be useful if you want to allow DELETE without requiring a media type. Another alternative could be to use media type and query string versioning methods. This would allow specifying the API version in the query string for DELETE APIs.
Over the wire, it will look like:
Request
POST /student HTTP/2
Host: localhost
Content-Type: application/json;v=2.0
Content-Length: 37
{"firstName":"John","lastName":"Doe"}
Response
HTTP/2 201 Created
Content-Type: application/json;v=2.0
Content-Length: 45
Location: http://localhost/student/42
{"id":42,"firstName":"John","lastName":"Doe"}
Based on your pasted code you could make AbstractStudentController generic.
Because those APIs that you declare abstract must be implemented in every API version, and you can define type with the generic. I hope I'm not missing something from your description, because Patch is missing from your implementation in StudentV2Controller, but is declared abstract. Do you wanted to derive StudentV2Controller from StudentV1Controller?
public abstract class AbstractBaseController : ApiController
{
// common methods for all api
}
public abstract class AbstractStudentController<StudentType> : AbstractBaseController
{
// common methods for Student related API'sample
public abstract Post(StudentType student);
public abstract Patch(StudentType student);
}
public class StudentV1Controller : AbstractStudentController<StudentV1>
{
public override Post([FromBody]StudentV1 student) // student should be instance of StudentV1 from JSON
{
// To Do: Insert V1 Student
}
public override Patch([FromBody]StudentV1 student) // student should be instance of StudentV1 from JSON
{
// To Do: Patch V1 Student
}
}
public class StudentV2Controller : AbstractStudentController<StudentV2>
{
//
public override Post([FromBody]StudentV2 student) // student should be instance of StudentV2 from JSON
{
// To Do: Insert V2 Student
}
}

Authorization has been denied for this request error when running webapi in MVC project

I have created an ASP.Net MVC project with WebApi option. Then modified the values controller with the code below:
public class ValuesController : ApiController
{
static List<string> data = initList();
private static List<string> initList()
{
var ret = new List<string>();
ret.Add("value1");
ret.Add( "value2" );
return ret;
}
// GET api/values
public IEnumerable<string> Get()
{
return data ;
}
// GET api/values/5
public string Get(int id)
{
return data[id];
}
// POST api/values
public void Post([FromBody]string value)
{
data.Add(value);
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
data[id] = value;
}
// DELETE api/values/5
public void Delete(int id)
{
data.RemoveAt(id);
}
}
When I am running the project and navigating to API/values URL, the following image is showing error.
.
The error description in text is:
<Error>
Authorization has been denied for this request.
</Error>
Have a look at the following article about
Authentication and Authorization in ASP.NET Web API
It will explain the different ways of how to use the [Authorize] and [AllowAnonymous] attribute on your controller/actions and any configurations you would need to do.
The following was taken from the linked article above:
Using the [Authorize] Attribute
Web API provides a built-in authorization filter,
AuthorizeAttribute. This filter checks whether the user is
authenticated. If not, it returns HTTP status code 401 (Unauthorized),
without invoking the action.
You can apply the filter globally, at the controller level, or at the
level of inidivual actions.
Globally: To restrict access for every Web API controller, add the
AuthorizeAttribute filter to the global filter list:
public static void Register(HttpConfiguration config){
config.Filters.Add(new AuthorizeAttribute());
}
Controller: To restrict access for a specific controller, add the
filter as an attribute to the controller:
// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
public HttpResponseMessage Get(int id) { ... }
public HttpResponseMessage Post() { ... }
}
Action: To restrict access for specific actions, add the attribute to
the action method:
public class ValuesController : ApiController
{
public HttpResponseMessage Get() { ... }
// Require authorization for a specific action.
[Authorize]
public HttpResponseMessage Post() { ... }
}
Alternatively, you can restrict the controller and then allow
anonymous access to specific actions, by using the [AllowAnonymous]
attribute. In the following example, the Post method is restricted,
but the Get method allows anonymous access.
[Authorize]
public class ValuesController : ApiController {
[AllowAnonymous]
public HttpResponseMessage Get() { ... }
public HttpResponseMessage Post() { ... }
}
In the previous examples, the filter allows any authenticated user to
access the restricted methods; only anonymous users are kept out. You
can also limit access to specific users or to users in specific roles:
// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}
So, I've been dealing with this error for awhile.
I didn't understand it at first, so I just removed and lived with it.
I finally got sick of it, because it's rather stupid. Microsoft wants a user to be authorized before they have signed in.
My error was looking for GET method which asks for HomeTown. In my case, I had changed it to CityCode.
Since the user is not logged in, there is no CityCode to GET. So, you get either a 402 or a 500 Resource Not Found.
I still don't understand it so, I gave CityCode some default data. So, from MeController I put the following code:
Public Function [Get]() As GetViewModel
Dim userInfo As ApplicationUser = UserManager.FindById(User.Identity.GetUserId())
Return New GetViewModel() With {.CityCode = "94110"}
End Function
App loads completely error free now.
This is a quick fix, not a certified solution.

Resources