Catch an invalid HTTP request method globally - c#

I would like to restrict my Web API endpoints to certain HTTP methods, such as GET and POST. I have searched the internet and I found out that you can add either [HttpGet] or [HttpPost] above your method as follows:
[HttpPost]
public ActionResult Login(string userName, string password) {
// do login stuff
return View();
}
Now I want to test if the example above with [HttpPost] really works so I use postman to send a HTTP request to my Web API. I fill in the URI and set the method to GET. The response I get is as follows:
{
"message": "The requested resource does not support http method 'POST'."
}
I'm able to verify that adding [HttpPost] prevents me from using HTTP GET requests.
What I would like to do now is log the event whenever an user tries to sent GET requests when the application is expecting POST, and vice versa. I could implement something for every single method but this would take a lot of time and it wouldn't be easy to make changes once it's been implemented. So I would like to filter it globally or something.
For example something like:
class MethodRequestFilter : SomeGlobalMethodFilter
{
public override void Filter(SomeRequestContext requestContext)
{
if (usedRequestMethod.isNotValid == true)
{
//implementation for logging
}
}
}
But ofcourse I haven't been able to find this yet within the libraries of .Net. How can I log the event globally whenever a user tries to make a request that isn't a supported method?
Greetings,
Damien.

One way is to using common base controller, to implement you need to add one base controller which would inherited from ApiController
public class BaseController : ApiController
{
public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
try
{
HttpResponseMessage response = await base.ExecuteAsync(controllerContext, cancellationToken);
if (!response.IsSuccessStatusCode) // or if(response.StatusCode == HttpStatusCode.BadRequest)
{
//log here
}
return response;
}
catch(Exception ex)
{
return await InternalServerError(ex).ExecuteAsync(cancellationToken);
}
}
}
Now, let's assume that you're having ValuesController and Login method and it supports only POST, here your all other controllers inherit from BaseController instead ApiController
public class ValuesController : BaseController
{
[HttpPost]
public void Login([FromBody]string value)
{
}
}
So, once you call your login method, it'll call BaseController method first and you will get response there.
Hope this helps!

Thanks to the user div I was able to solve my problem by using a base controller that implements logging. These are the steps that I had to take:
Create a new controller class called BaseController and inherit ApiController:
Override the ExecuteAsync method from ApiController:
Add an implementation for logging in the catch clause
Inherit the new BaseController in every controller class that you would like to have logging functionality.
The code that I used in my implementation:
public class BaseController : ApiController
{
public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
try
{
HttpResponseMessage response = await base.ExecuteAsync(controllerContext, cancellationToken);
return response;
}
catch (HttpResponseException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.MethodNotAllowed)
{
//Logging implementation
}
return Request.CreateErrorResponse(ex.Response.StatusCode, ex.Message);
}
}
}
If there is any way to make my code better, please let me know :)

Related

Using API Controller in Page Controller

I'm starting learning asp.net core, and I have problems with understanding some basic patterns. Can I use ApiController inside PageController?
For example
//Home controller
public async Task<IActionResult> Index()
{
var artists = await _artistController.GetAll();
var t = artists.GetValue();
var indexModel = new Models.Pages.IndexModel
{
Artists = artists.GetValue() //Extension method
};
return View(indexModel);
}
//ArtistRestController
[HttpGet]
public async Task<ActionResult<IEnumerable<Artist>>> GetAll()
{
try
{
return Ok(await _repository.GetAll());
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return BadRequest(ex.Message);
}
}
It works, but is is ok by design? Maybe I should directly call _repository.GetAll() and do not use other controller?
First, you should avoid using one Controller in another Controller. For Controller, it is used to handle http request from client. It is unreasonable to use it as a class even through it is a class.
Second, for combining ApiController and PageController will make your application unstatable and too coupling. If you change anything later in ArtistRestController.GetAll will make HomeController broken. And, for your current code, if there are some exception in PageController, your ApiController will be down.
You should inject repository to query the data.

Http get and post method without attribute name

I'm using .net Core 2.1 Web API. I add every method [HttpGet("....")] or [HttpPost("....")] like below. But, I don't want to write every method in every controller. I want to write only [HttpGet] or [HttpPost]. How can I achieve this?
Or, if this is not possible, can I do this like [HttpPost("[action]")] with taking automatically actionName?
I'm calling like this:
http://localhost:5000/api/University/GetUniversities
This working perfectly
namespace University.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UniversityController : ControllerBase
{
private readonly IUniversityService universityService;
public UniversityController(IUniversityService universityService)
{
this.universityService = universityService;
}
[HttpGet("GetUniversities")]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
[HttpGet("GetUniversityStatues")]
public async Task<ServiceResult> GetUniversityStatues()
{
return await universityService.GetUniversityStatues();
}
}
}
I tried this but is not working:
namespace University.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UniversityController : ControllerBase
{
private readonly IUniversityService universityService;
public UniversityController(IUniversityService universityService)
{
this.universityService = universityService;
}
[HttpGet]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
[HttpGet]
public async Task<ServiceResult> GetUniversityStatues()
{
return await universityService.GetUniversityStatues();
}
}
}
Web API like MVC has the convention over configuration, so if you didn't define the endpoint in the attribute, it will work but not as you expect. Generally Get method will be called like this api/[ControllerName] post method the same.
In your case, if you need to name your route the same name of your action you can write this attribute above your controller
[Route("[controller]/[action]")]
You can use just [HttpGet]
[HttpGet]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
And then send a get request to http://localhost:5000/api/University. It will work fine.
But your problem is you have more than one parameterless get methods. then it can't identify which get method you need to call.
So if you really want to use two parameterless get methods in same controller you have to decorate it like this,
[HttpGet("GetUniversities")]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
[HttpGet("GetUniversityStatues")]
public async Task<ServiceResult> GetUniversityStatues()
{
return await universityService.GetUniversityStatues();
}
Otherwise you have to move the second one to another controller
You can automate it a little by this
[HttpPost(nameof(GetUniversities))]
It's not required to pass the method name with Http Verb. You can use like this also:
[HttpGet]
public async Task<ServiceResult> GetUniversities()
{
return await universityService.GetUniversities();
}
But you want to change the method name or route. You can something like this:
[HttpGet("", Name="Students"]
public async Task<ServiceResult> GetStudents()
{
return await universityService.GetStudents();
}

Is it OK for multiple REST API methods to share same controller class?

Is it best practice to put each distinct Get method in its own controller class, or is it perfectly fine to have multiple (related and non-related) API methods in the same class, if the methods are very simple and uncomplicated.
E.g. these two API methods work fine in the same controller class, but would they be better off in their own class?
If so why?
public class TestController : ApiController
{
[HttpGet]
[Route("api/test/ping")]
public IHttpActionResult Ping()
{
try
{
return Ok("HELLO");
}
catch (Exception ex)
{
return Content(HttpStatusCode.InternalServerError, ex.Message);
}
}
[HttpGet]
[Route("api/test/echo/{message}")]
public IHttpActionResult Echo(string message)
{
try
{
return Ok(message);
}
catch (Exception ex)
{
return Content(HttpStatusCode.InternalServerError, ex.Message);
}
}
}
There is nothing stopping you from having multiple actions in a controller once their routes are distinct and do not cause route conflicts in the current or other controllers.
Take your provided example. You can take advantage of route prefixes for the controller to help with the organizing of similar routes
[RoutePrefix("api/test")]
public class TestController : ApiController {
//GET api/test/ping
[HttpGet] [Route("ping")]
public IHttpActionResult Ping() {
return Ok("HELLO");
}
//GET api/test/echo/hello%20world
[HttpGet] [Route("echo/{message}")]
public IHttpActionResult Echo(string message) {
if(message == null)
return BadRequest();
return Ok(message);
}
}
Personally I would put related API actions that work that do related work together in 1 single controller class.
In your given example it would be fine to put them together. Another example, say you have a Controller that handles all actions on a User model (Please note not entirely valid code, but hopefully you get the point):
[RoutePrefix("api/users")]
public class UserController: ApiController
{
[HttpGet]
public IHttpActionResult GetUsers()
{
// GET all users.
}
[HttpGet]
[Route("{id}")]
public IHttpActionResult GetUserById(int id)
{
// GET user by ID
}
[HttpPost]
public IHttpActionResult CreateUser()
{
// Create User
}
[HttpPut]
[Route("{id}")]
public IHttpActionResult UpdateUser()
{
// Update User
}
}
As you can see, all these actions work on the User model, so they fit together in a Controller class.

Web Api 2 bad request

Im a beginner with Web api and Im trying to setup a simple owin selfhosted service that Im trying out.
I've been searching stackoverflow and other places for a while now, but I cant seem to find anything obviously wrong.
The problem I have is that I get a bad request response everytime I try to call my service.
The controller looks like this:
[RoutePrefix("api/ndtdocument")]
public class NDTDocumentsController : ApiController, INDTDocumentsController
{
[HttpGet]
public IHttpActionResult Get()
{
var document = Program.NDTServerSession.GetNextNDTDocument(DateTime.Today);
if (document == null)
return null;
return Ok(document);
}
[Route("")]
public IHttpActionResult Post([FromBody] NDTDocument ndtDocument)
{
try
{
Program.NDTServerSession.AddNDTDocument(ndtDocument);
return Ok();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
}
And the client looks like this:
static void Main(string[] args)
{
AddNDTDocument(#"C:\Testing.txt");
}
private static void AddNDTDocument(string centralserverPath)
{
var client = GetServerClient();
NDTDocument document = new NDTDocument();
var response = client.PostAsJsonAsync("ndtdocument", document).Result;
}
static HttpClient GetServerClient()
{
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:9000/api/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
I can see when I debug it that the request uri is infact http://localhost:9000/api/ndtdocument
But the response is allways bad request and I have a breakpoint in the controller and it is never invoked.
Everytime I try to do something with web apis I Always run into some weird (but simple problem).
Any thoughts?
Thanks!
Web API will decide your route based on your method names. Since you have added [RoutePrefix("api/ndtdocument")] on class level this will be the route to your controller. When web api looks for an action it will match on method names, so in your case your actual route would be http://localhost:9000/api/ndtdocument/post.
When trying to decide what http method that a specific action requires web api will check your method names and methods starting with post will be http post, get will be http get etc.
So lets say we would instead call our method PostData, for starters we could remove the [HttpPost] attribute. Our route would now be http://localhost:9000/api/ndtdocument/postdata. Let's now say that we want our path to be just /data. We would then first rename our method to Data, but now web api does not know what http method we want to invoke this method with, thats why we add the [HttpPost] attribute.
Edit after reading your comment
[Route("{id:int}")]
public IHttpActionResult Get(int id)
[Route("")]
public IHttpActionResult Post([FromBody] NDTDocument ndtDocument)
Okey, after nearly going seriously insane. I found the problem.
I forgot to reference webapi.webhost and then system.web.
After this Everything worked like a charm.
You must use route tags and call this way http://localhost:9000/api/get or http://localhost:9000/api/post
[RoutePrefix("api/ndtdocument")]
public class NDTDocumentsController : ApiController, INDTDocumentsController
{
[HttpGet]
[Route("get")]
public IHttpActionResult Get()
{
var document = Program.NDTServerSession.GetNextNDTDocument(DateTime.Today);
if (document == null)
return null;
return Ok(document);
}
[HttpPost]
[Route("post")]
public IHttpActionResult Post([FromBody] NDTDocument ndtDocument)
{
try
{
Program.NDTServerSession.AddNDTDocument(ndtDocument);
return Ok();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
}
for more infromation pls check this link

ASP.NET override '405 method not allowed' http response

In ASP.NET WebAPI I have a controller/action which is accessible by using the GET verb. If I query the endpoint using POST verb I get a standard 405 method not allowed response.
Is is possible to intercept this behaviour and inject my own custom response instead of that one without adding code to the controllers? Or maybe somehow overwrite the original response. This behavior is expected to be present application wide, so I will somehow have to set this setting globally.
This behavior of a 405 is determined by the pipeline looking for a proper controller, then a proper method by either naming convention or attributes. I see two ways for you to achieve your desired result, a custom IHttpActionSelector or a base ApiController.
Example code for IHttpActionSelector:
public class CustomHttpActionSelector : IHttpActionSelector
{
public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
var isPostSupported = false;
//logic to determine if you support the method or not
if (!isPostSupported)
{
//set any StatusCode and message here
var response = controllerContext.Request.CreateErrorResponse(HttpStatusCode.ServiceUnavailable, "Overriding 405 here.");
throw new HttpResponseException(response);
}
}
...
}
//add it to your HttpConfiguration (WebApiConfig.cs)
config.Services.Add(typeof(IHttpActionSelector), new CustomHttpActionSelector());
Example code for a base ApiController:
public abstract class BaseApiController<T> : ApiController
{
public virtual IHttpActionResult Post(T model)
{
//custom logic here for "overriding" the 405 response
return this.BadRequest();
}
}
public class UsersController : BaseApiController<User>
{
public override IHttpActionResult(User model)
{
//do your real post here
return this.Ok();
}
}

Categories