I have a project with Attribute routing like:
[Route("home")]
public class HomeController : Controller
{
[HttpPost]
public IActionResult Post(int id)
{
}
[HttpGet]
public IActionResult Get()
{
}
}
Now I want to catch all Get/Post/Put Requests which doesn't have a specified route. So I can return an error, redirect to the home page and such stuff. Is it possible with AttributeRouting or should I use Conventional Routing in the startup? And how would the "not existing" route look there?
By default, server returns 404 HTTP Status code as the response for requests that are not handled by any middleware (attribute/convention routing is part of MVC middleware).
In general, what you always can do is to add some middleware at the beginning of the pipeline to catch all responses with 404 status code and do custom logic or change response.
In practice, you can use the existing mechanism provided by ASP.NET Core called StatusCodePagesmiddleware. You can register it directly as raw middleware by
public void Configure(IApplicationBuilder app)
{
app.UseStatusCodePages(async context =>
{
context.HttpContext.Response.ContentType = "text/plain";
await context.HttpContext.Response.WriteAsync(
"Status code page, status code: " +
context.HttpContext.Response.StatusCode);
});
//note that order of middlewares is importante
//and above should be registered as one of the first middleware and before app.UseMVC()
The middleware supports several extension methods, like the following (the difference is well explained in this article):
app.UseStatusCodePages("/error/{0}");
app.UseStatusCodePagesWithRedirects("/error/{0}");
app.UseStatusCodePagesWithReExecute("/error/{0}");
where "/error/{0}" is a routing template that could be whatever you need and it's {0} parameter will represent the error code.
For example to handle 404 errors you may add the following action
[Route("error/404")]
public IActionResult Error404()
{
// do here what you need
// return custom API response / View;
}
or general action
[Route("error/{code:int}")]
public IActionResult Error(int code)
Related
EDIT: If I create an empty ASP.NET CORE WEB APP MVC, I can make it working. I am having problem when I am using MVC with Angular. There might be a problem with SPA proxy as well.
EDIT 2: I found a report https://github.com/dotnet/aspnetcore/issues/38354
I am still trying but no chance.
I can not access my public methods in controller classes. This is my controller:
[Route("authentication")]
public class AuthenticationController : Controller
{
[HttpGet("example")]
public IActionResult Example()
{
return Ok("This is the Welcome action method...");
}
}
And also I tried this attribute as well:
[Route("[controller]")]
public class AuthenticationController : Controller
when I try to navigate to localhost:PORT/authentication/example I am getting 404. I am not using API. I am trying to build a web application with .net core MVC and angular. So I will be just sending GET or POST requests to controllers.
This is my program.cs file
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
app.Run();
I strongly believe that something is wrong in my program.cs. But I couldn't figure it out.
FIX:
After trying out a few days, I finally found the answer. I had to add my new route into 'proxy' variable in proxy.conf.js file.
const PROXY_CONFIG = [
{
context: [
"/weatherforecast",
"/authentication"
],
target: target,
secure: false,
headers: {
Connection: 'Keep-Alive'
}}
]
you can try this for example, it will work for localhost:PORT/authentication/example
[Route("[controller]/[action]")]
public class AuthenticationController : Controller
{
public IActionResult Example()
{
return Ok("This is the Welcome action method...");
}
}
//or
public class AuthenticationController : Controller
{
[HttpGet("~/Authentication/Example")]
public IActionResult Example()
{
return Ok("This is the Welcome action method...");
}
}
but since you are using a Controller as a base class, not an ApiController for example, everything should be working even if you remove all attribute routing at all.
You need to decorate your controller with method / routing attributes
Try:
[Route("api/[controller]")]
public class AuthenticationController : Controller
{
[HttpGet("example")]
public IActionResult Example()
{
return Ok("This is the Welcome action method...");
}
}
This will create a get endpoint which can be called at api/authentication/example
Returning a 200 status with the text in the body.
The convention is that if Your memers start with an action verb, it can find out automatically, like
public string GetExample()
However you do not want to return raw string, you always want to return an action result, because you want wrapping with explicit HttpStatus response codes, so
public IActionResult<string> GetExample()
Now many of us a bias towards the works by magic because of prefix and like to be more explicit, not only because the attribute notation allows more control, but also for consistency. Because nearly almost always, at least one action method of the controller actually requires that fine grain.
[HttpGet("example")]
public IActionResult<string> Example()
Then often for instance there is an id and you can go
[HttpGet("example/id?")]
public IActionResult<string> Example([FromRoute] string id)
if you want to not have it go through all the places it might be getting your variables from for instance, there are many choices available
I'm building an ASP.NET Core 5.0 Web API application as I mentioned in the title I have an issue when trying to delete a record from the database; I'm getting an error 405 Method Not Allowed response from HttpDelete request.
PS: I have added services.AddCors() and app.UseCors() with default policy.
This is the delete method code
public bool deleteLivreById(int id)
{
Livre l = _db.Livres.Find(id);
_db.Livres.Remove(l);
_db.SaveChanges();
return true;
}
And this is the HttpDelete method inside the controller
[HttpDelete("{id}/delete")]
public bool deleteLivreById(int id)
{
return _objGererLivre.deleteLivreById(id);
}
Finally this is a picture from console when navigating to HttpDelete Url
Edit: This is full code of my controller
namespace GestionLivre.Controllers
{
[ApiController]
[Route("test")]
public class LivreController : Controller
{
private IGererLivre _objGererLivre;
public LivreController(IGererLivre gererLivre)
{
_objGererLivre = gererLivre;
}
[HttpGet]
public JsonResult getLivres()
{
return Json(_objGererLivre.getLivres());
}
[HttpDelete("{id}/delete")]
public bool deleteLivreById(int id)
{
return _objGererLivre.deleteLivreById(id);
}
}
}
I opened the screenshot and noticed that you have selected 'GET' as http verb and method type is 'Delete'. Could you please change that and try.
As I understand by default when you're trying to access URL in browser it uses GET method. So we should to pass in header appropriate method(POST,GET,DELETE,PATCH,PUT) If you want to test HTTP methods I'll recommend you to use Postman or Swagger. Postman much easier to use whether than Swagger which you should to add to service configuration and middleware.
Example of Postman:
And than configure body like that to return response.
Also recommend you to use REST Best Practices. And name resources properly. https://restfulapi.net/resource-naming/#:~:text=2.-,Best%20Practices,-2.1.%20Use%20nouns
I'm developing an ASP.net core MVC 2.2 app and at the moment I'm using the classic [Authorize] and [AllowAnonymous] attributes on my controller actions to decide whether a certain action can be accessed anonymously or requires authorization.
However, I have a requirement where certain actions should require authorization only if a certain header is missing from the http request. Usually I would implement this by simply having something like this:
[HttpGet]
[AllowAnonymous]
public IActionResult SomeAction()
{
if (!Request.Headers.ContainsKey("something"))
{
return RedirectToAction("SomeActionWithAuth");
}
...
}
[HttpGet]
[Authorize]
public IActionResult SomeActionWithAuth()
{
...
}
however, in this particular case I have a strict requirement to avoid redirects, so I cannot use this approach.
So my question is:
Is there a way to intercept the request before it reaches the controller and decide at runtime, on a per-request basis, if the request should require authentication or not?
If that is not possible, is there maybe a way to decide at runtime which controller/action to route the request to? (that way I could have a setup similar to the one above, with two actions with different auth requirements, but without causing an actual HTTP Redirect on the client side, which I cannot do)
Create your own authorisation policy to handle this.
public class HeaderRequirement : IAuthorizationRequirement
{
public HeaderRequirement(string header)
{
Header = header;
}
public string Header { get; }
}
public class HeaderRequirementHandler : AuthorizationHandler<HeaderRequirement>
{
protected override Task HeaderRequirementHandler (
AuthorizationHandlerContext context,
HeaderRequirement requirement)
{
var hasHeader = context.Request.Headers.ContainsKey(requirement.Header);
if (hasHeader) // if we have the header
{
context.Succeed(requirement); // authorization successful
}
return Task.CompletedTask;
}
}
Register the handler as a service
services.AddScoped<IAuthorizationHandler, HeaderRequirementHandler>();
Add the policy to the authorisation services
services.AddAuthorization(options =>
{
options.AddPolicy("SomeHeader", policy =>
policy.Requirements.Add(new HeaderRequirement("SomeHeader")));
});
Now you can use it like this: [Authorize(Policy = "SomeHeader")]
If you need it a little bit more dynamic, If you don't want to register every single header that could possible be authenticated but rather interpreted at run-time. You can write you own policy provider
I have an HTTP-GET method looks like below one
[Route("api/[controller]")]
[ApiController]
public class CityController : ControllerBase
{
public ActionResult Get(int id)
{
try
{
var city = new { CityName = "Gotham" };
return Ok(city);
}
catch(Exception ex)
{
return StatusCode(500);
}
}
}
For both type of the requests
Request:
GET http://localhost:49915/api/city
POST http://localhost:49915/api/city
Response:
status: 200 OK
-------------------
{
"cityName": "Gotham"
}
Now my questions around it are,
As it is a GET, should it supposed to accept a POST?
Shouldn't it return a 405 status code and Why it does not? (at least I'm expecting)
In such case, if I have to return 405 what to be done?
As it is a GET, should it supposed to accept a POST?
While you assume it is a get because of the action name and convention-based routing, you would be mistaken as the controller has been decorated for attribute routing.
[Route("api/[controller]")]
and thus ignores convention based routes if a match is made. Note that PUT and DELETE
PUT http://localhost:49915/api/city
DELETE http://localhost:49915/api/city
should also work on the same action.
Shouldn't it return a 405 status code and Why it does not? (at least I'm expecting)
The action matches both calls by design since no directive was given for the action.
[Route("api/[controller]")]
[ApiController]
public class CityController : ControllerBase {
// GET api/city?id=2 //Note id would be optional as a query variable.
[HttpGet]
public ActionResult Get(int id) {
try {
var city = new { CityName = "Gotham" };
return Ok(city);
} catch(Exception ex) {
return StatusCode(500);
}
}
}
Now with the HttpGet in place, if
POST http://localhost:49915/api/city
or another HTTP method is done, you will get the 405 error because the path matches but the method does not.
In such case, if I have to return 405 what to be done?
with the attribute route in place, the framework will do it for you so there is nothing more for you to do.
Reference Routing to controller actions in ASP.NET Core
Mixed routing: Attribute routing vs conventional routing
What distinguishes the two types of routing systems is the process applied after a URL matches a route template. In conventional routing, the route values from the match are used to choose the action and controller from a lookup table of all conventional routed actions. In attribute routing, each template is already associated with an action, and no further lookup is needed.
I would like to return only standardized error responses from my Web API (Asp.net Core 2.1), but I can't seem to figure out how to handle model binding errors.
The project is just created from the "ASP.NET Core Web Application" > "API" template. I've got a simple action defined as:
[Route("[controller]")]
[ApiController]
public class MyTestController : ControllerBase
{
[HttpGet("{id}")]
public ActionResult<TestModel> Get(Guid id)
{
return new TestModel() { Greeting = "Hello World!" };
}
}
public class TestModel
{
public string Greeting { get; set; }
}
If I make a request to this action with an invalid Guid (eg, https://localhost:44303/MyTest/asdf), I get back the following response:
{
"id": [
"The value 'asdf' is not valid."
]
}
I've got the following code in Startup.Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
JsonErrorMiddleware.CreateSingleton(env);
if (!env.IsDevelopment())
{
app.UseHsts();
}
app
.UseHttpsRedirection()
.UseStatusCodePages(async ctx => { await JsonErrorMiddleware.Instance.Invoke(ctx.HttpContext); })
.UseExceptionHandler(new ExceptionHandlerOptions() { ExceptionHandler = JsonErrorMiddleware.Instance.Invoke })
.UseMvc()
}
JsonErrorMiddleware is simply a class that converts errors to the correct shape I want to return and puts them into the response. It is not getting called at all for the model binding errors (no Exception is thrown and UseStatusCodePages is not called).
How do I hook into the model binding to provide a standardized error response across all actions in my project?
I've read a bunch of articles, but they all seem to either discuss global exception handling or validation errors.
It's worth mentioning that ASP.NET Core 2.1 added the [ApiController] attribute, which among other things, automatically handles model validation errors by returning a BadRequestObjectResult with ModelState passed in. In other words, if you decorate your controllers with that attribute, you no longer need to do the if (!ModelState.IsValid) check.
Additionally, the functionality is also extensible. In Startup, you can add:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new BadRequestObjectResult(actionContext.ModelState);
});
The above is just what already happens by default, but you can customize the lambda that InvalidModelStateResponseFactory is set to in order to return whatever you like.