.NET CORE Web API Routing - c#

I am new to .NET Core Web API and i'm trying to create Web API with 3 POST methods.
AddUser
UpdateUser
DeleteUser
I was able to create a .NET core web api project with AddUser POST method and its working fine but they way I want it be uri is
https://localhost:1234/api/Project/AddUser
https://localhost:1234/api/Project/UpdateUser
https://localhost:1234/api/Project/DeleteUser
When I run the application in default swagger uri shows POST /api/Project i.e. https://localhost:1234/api/Project
I am using .NET core web api 5.0
Here code from my controller
namespace ProjectAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKeyAuth]
public class ProjectController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<Response>> AddUser([FromBody] Request request)
{
var _message = await DoSomething(request);
Response response = new Response
{
Message = _message
};
return response;
}
private async Task<string> DoSomething(Request request)
{
string msg = string.Format("Add user {0} to {2} is successful", request.User, request.FromRole, request.ToRole);
return msg;
}
}
}

#joshykautz is right, you can add routing to each action
Another way is just to change controller routing and not touching actions:
[Route("api/[controller]/[action]")]
public class ProjectController : ControllerBase
....
but after this, if you need, you can still can assign a very special route for some action, for example
[Route("~/api/Project/AddNewUser")]
public async Task<ActionResult<Response>> AddUser( Request request)
Don't miss "~/". It will work for url
https://localhost:1234/api/Project/AddNewUser

Adding the [action] token to your controller route will yield your desired route format:
[Route("api/[controller]/[action]")]
However, I would discourage using verbs in your route naming. The HTTP method already sufficiently describes the action taken when calling a given endpoint.
POST /api/user creates a new user.
GET /api/user gets users.
PUT /api/user/{id} updates an existing user.
DELETE /api/user/{id} deletes a user.
In a RESTful approach, the route describes the resource that you're interacting with on the server, and the HTTP method used describes the action. Mixing actions/verbs into your routes goes against this mindset.
What I would do in your situation is create a new UserController, which will contain the endpoints for your user resource. Having them in a ProjectController, which to me sounds like something that should handle projects, mixes responsibilities and makes your code difficult to understand.

I kindly recommend not to use ../adduser ../updateuser ../deleteuser in your routing. It also causes security weakness.
You can build your API as /user and add;
yourrouter.route('/user/:id?')
.get(user.get)
.post(user.post)
.put(user.put)
.delete(user.delete);
for the same /user route.
It means, the client calls the same ./user with a specific request (GET, POST, PUT etc.) and with ID and also other parameters if required.
You can test your API and routing via POSTMAN, by selecting the method when you call the API (e.g. https://yourdomain/api/user/{parameters})

Give a routing attribute to each action.
[HttpPost]
[Route("api/[controller]/AddUser")]
public async Task<ActionResult<Response>> AddUser([FromBody] Request request)
{
var _message = await DoSomething(request);
Response response = new Response
{
Message = _message
};
return response;
}
And remember to remove the routing attribute that you've defined for the class.
You could also use [Route("api/[controller]/[action]")] since your method is already named AddUser.
[HttpPost]
[Route("api/[controller]/[action]")]
public async Task<ActionResult<Response>> AddUser([FromBody] Request request)
{
var _message = await DoSomething(request);
Response response = new Response
{
Message = _message
};
return response;
}
You can read more here on the Microsoft Docs.

Related

Getting tokenized path matched by route template (ASP.NET Core 3.1)

I'm trying to figure out the best approach to getting the fragment of the original request URI that matches a route template i.e. https://apim.example.com/api/v1/controller/[a/{id}/b] with the [..] being the fragment matched by the route template; the path is available from this.Request.Path but is the fragment available sepeartely?
I've read (all) the docs, checked all properties of the HttpRequest instance and didn't find the actual tokenized fragment (only the meta data describing the controller/action params like naming etc.). I need the fragment as part of a proxy API (with pattern specific handlers).
Proxy API
I'm writing a proxy service in ASP.NET Core 3.1 (sits behind an Azure API Management instance) that maps a subset of a remote API and applies a security scheme on top. The proxy recognizes a set of known route signatures in order to apply the security scheme according to requested resource(s).
Request sent to Azure API Management (e.g.):
https://apim.example.com/api/v1/organizations/{id}/{*path}
Request to proxy as forwarded by Azure API Management:
https://proxy.example.com/api/v1/proxy/v1/organizations/{id}/{*path}
It's worth mentioning that both the proxy and the remote API provide an invididualized versioning scheme (hence the fully qualified proxy path from Azure API Management). The proxy is implemented with ASP.NET Core 3.1 routing as can be seen from the following snippet:
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class ProxyController : ControllerBase
{
.. constructors and other members omitted for brevity
[HttpGet]
[HttpPost]
[HttpPut]
[HttpDelete]
[Route("v1/organizations/{id:int}/{*path}")]
public async Task OrganizationsAsync(int organizationID, string path, CancellationToken cancellation = default)
{
// would like to get the tokenized "v1/organizations/{id:int}/{*path}" fragment here
var message = await this.service.HandleAsync(this.Request, path, cancellation);
await this.handler.CopyAsync(this.HttpContext, message);
}
}
In OrganizationsAsync I'm looking for a dynamic approach to getting the v1/organizations/{id:int}/{*path} fragment instead of having to predefine the various base URIs of the controller (as can be seen from the following example).
Using a predefined controller URI
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class ProxyController : ControllerBase
{
.. constructors and other members omitted for brevity
private readonly string baseUri = "api/v1/proxy";
[HttpGet]
[HttpPost]
[HttpPut]
[HttpDelete]
[Route("v1/organizations/{id:int}/{*path}")]
public async Task OrganizationsAsync(int organizationID, string path, CancellationToken cancellation = default)
{
var localpath = this.Request.Path.Value.Replace(this.baseUri, "");
var message = await this.service.HandleAsync(this.Request, localpath, cancellation);
await this.handler.CopyAsync(this.HttpContext, message);
}
}
According to the ASP.NET Core team, there's currently no API that provides the string fragment (or equivalent structure) matching just the route template signature.
I decided to create the fragment from the route values as can be seen from the following snippet:
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{apiVersion:apiVersion}/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class ProxyController : ControllerBase
{
.. constructors and other members omitted for brevity
[HttpGet]
[HttpPost]
[HttpPut]
[HttpDelete]
[Authorize]
[Route("v{version:int}/organizations/{organizationID:int}/{*path}")]
public async Task OrganizationsAsync(int version, int organizationID, string path, CancellationToken cancellation = default)
{
await this.HandleAsync($"v{version}/organizations/{organizationID}/{path}", cancellation);
}
}

Model binding not working in aspnet core web api

I am working on ASP.NET Core 2.1 Web API project. I am trying to follow this article: https://www.c-sharpcorner.com/article/jwt-json-web-token-authentication-in-asp-net-core/ but I am stuck at Action. My model class just won't bind to the input.
[AllowAnonymous]
[HttpPost]
public IActionResult Login([FromBody] LoginVM loginVM)
{
IActionResult response = Unauthorized(); // cant reach this point, my breakpoint is here
var user = AuthenticateUser(new UserModel { });
if (user != null)
{
var tokenString = GenerateJSONWebToken(user);
response = Ok(new { token = tokenString });
}
return response;
}
public class LoginVM
{
public string Username { get; set; }
public string Password { get; set; }
}
You're posting as x-www-form-urlencoded, but you have the [FromBody] attribute applied to the action param. These two things are fundamentally incompatible. To accept x-www-form-urlencoded (or multipart/form-data) you must apply the [FromForm] attribute to the param. If you have [FromBody], as you do now, then you can only accept something like application/json or application/xml (if you also enable the XML serializers).
If the issue is that you want to be able to accept both application/json and x-www-form-urlencoded request bodies, that is not possible. You'll need a separate action for each request body encoding, though you can factor out the actual meat of the action into a private method on the controller that both actions can utilize.
Choose "raw" in Body and "Content-Type" as "application/json" in postman and then try.

Have a WebAPI Controller send an http request to another controller within the same service

I have a WebAPI service, and I want it to send an http request to itself. I'd like to confirm what the most appropriate way of doing this looks like. (Normally, I would just instantiate another instance of the target controller or refactor the code behind an interface and then make request that way, but for various reasons I do not want to use that method.)
Is the code below the most appropriate way to make the http request to a different controller within the same service?
using (HttpClient client = new HttpClient())
{
var httpRequest = new HttpRequestMessage("GET", "https://localhost/SomeOtherController");
return await client.SendAsync(httpRequest);
}
If you need to call another controller, that means there is some shared code there.
The best approach is to abstract this piece of code into another class and use it in both controllers.
ex:
public class SomeService
{
public void DoSomeLogic()
{
}
}
public class FirstController : ApiController
{
public IHttpActionResult SomeAction()
{
new SomeService().DoSomeLogic();
}
}
public class SecondController : ApiController
{
public IHttpActionResult SomeOtherAction()
{
new SomeService().DoSomeLogic();
}
}
Yes, this would be an appropriate way to call it.
If you need to do an actual HTTP request then it does not matter that the request goes out to a controller in the same service - it is just a generic HTTP request.
On a side note - you'd probably want to do something about that URI string, so that it is not all hardcoded.

How do i send a "forbidden" response in my Web API 2 solution?

I am making a web api to work with a legacy system. This web api should work in the same way as the old one. The security is to send a security token along with each call. This means that i need to check the token before serving data. I have a method like this:
public List<User> Get(string id, string securityToken)
{
//ValidateToken(securityToken);
return userRepository.LoadAll();
}
And in my method i would like the validateToken() method to return a "Forbidden" httpresponse if i cant validate it. How do i go around doing this?
You can use an HttpResponseMessage like so:
public HttpResponseMessage Get(string id, string securityToken)
{
var forbidden = true;
if (forbidden)
{
return this.Request.CreateResponse(HttpStatusCode.Forbidden);
}
return Ok(userRepository.LoadAll());
}
Using HttpResponseMessage allows you to return OK (an HTTP 200) with content, or an error.
IHttpActionResult:
return StatusCode(HttpStatusCode.Forbidden);
Or:
return Content(HttpStatusCode.Forbidden, "message");
HttpResponseMessage:
return this.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "message");
See this example if you would like a custom controller to have Forbidden() implemented just like BadRequest() or any other response.
https://stackoverflow.com/a/28361376/3850405
Typically you'd do the ValidateToken type call in an ActionFilterAttribute, returning the forbidden at that time, long before the Get method was called on the controller. You'd then apply that attribute to the controller or action method (or register it as a global action filter attribute if you need to authorize ALL calls).

Web API 2 Attribute Routing Controller Selection

I use Web API 2 Attribute Routing in my project to provide JSON interface over my data. I am facing weird behaviour of controller selection, not decided yet whether it's a bug or a feature :)
Let me describe my approach.
I would like to simulate OData syntax with help of attribute routing (direct OData usage has been refused due to design principles). For example, to get entity with id=5 I use HTTP GET request to URI http://mydomain.com/api/Entity(5) . I expect to use the same URI with HTTP PUT verb to update the entity. This is where the journey begins...
I would like to have separate controller for getting entities (FirstController in the example provided below) and another one for modifying entities (SecondController). Both controllers handles the same URI (e.g. http://mydomain.com/api/Entity(5)) the only difference is HTTP verb used with the URI - GET should be handled by FirstController, PUT should be handled by SecondController. But the URI is handled by none of them; instead HTTP 404 error is returned.
When I "merge" GET and PUT actions to only one controller (commented out in FirstController), both verbs are handled correctly.
I am using IIS Express and all conventional routes are disabled, only attribute routing is in charge.
It looks like the controller selection process does not work with HTTP verb. In another words, HttpGet and HttpPut attributes just limit action usage but they do not serve as criteria during controller selection. I am not so familiar with MVC / Web API fundamentals, so let me ask you my big question:
Is the behaviour, described herein before, a feature intentionally implemented by MVC / Web API 2 or a bug to be fixed?
If it is considered as a feature, it prevents me to follow design principles. I can live with "merged" controllers but still considering it as a bad practice...
Or am I missing something in my train of thought?
My environment setup:
Windows 7 (virtual machine using Oracle VirtualBox)
Visual Studio 2013
.NET 4.5.1
Web API 2
The following is implementation of FirstController class:
public class FirstController : ApiController
{
[HttpGet]
[Route("api/Entity({id:int})")]
public Output GetEntity(int id)
{
Output output = new Output() { Id = id, Name = "foo" };
return output;
}
//[HttpPut]
//[Route("api/Entity({id:int})")]
//public Output UpdateEntity(int id, UpdateEntity command)
//{
// Output output = new Output() { Id = id, Name = command.Name };
// return output;
//}
}
The following is implementation of SecondController class:
public class SecondController : ApiController
{
[HttpPut]
[Route("api/Entity({id:int})")]
public Output UpdateEntity(int id, UpdateEntity command)
{
Output output = new Output() { Id = id, Name = command.Name };
return output;
}
}
The following is implementation of a console application to test the described behaviour:
class Program
{
static void Main(string[] args)
{
// HTTP client initialization
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("http://localhost:1567");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP GET - FirstController.GetEntity
HttpResponseMessage getEntityResponse = httpClient.GetAsync("/api/Entity(5)").Result;
Output getOutput = getEntityResponse.Content.ReadAsAsync<Output>().Result;
// HTTP PUT - SecondController.UpdateEntity
UpdateEntity updateCommand = new UpdateEntity() { Name = "newEntityname" };
HttpResponseMessage updateEntityResponse = httpClient.PutAsJsonAsync("/api/Entity(10)", updateCommand).Result;
Output updateOutput = updateEntityResponse.Content.ReadAsAsync<Output>().Result;
}
}
For completion, the following are used DTOs:
public class UpdateEntity
{
public string Name { get; set; }
}
public class Output
{
public int Id { get; set; }
public string Name { get; set; }
}
Thanks in advance for your responses,
Jan Kacina
This design was intentional as we thought it to be an error case where a user would be having same route template on different controllers which can cause ambiguity in the selection process.
Also if we keep aside attribute routing, how would this work with regular routing? Let's imagine we have 2 regular routes where first one is targeted for FirstController and the second to SecondController. Now if a request url is like api/Entity(5), then Web API would always match the 1st route in the route table which would always hit the FirstController and would never reach SecondController. Remember that once Web API matches a route it tries to go till the action selection process and if the action selection process doesn't result in an action being selected, then an error response is sent to the client. You probably are assuming that if an action is not selected in one controller then Web API would route it to the next one in the route configuration. This is incorrect.
Route probing occurs only once and if it results in a match, then the next steps take place...that is controller and action selection. Hope this helps.

Categories

Resources