Many action filters can be specified for the whole Controller in ASP.NET MVC, resulting in it being applied to all of the actions in the controller. For example:
[Authorize]
public class MyController : Controller
{
// ....
}
means that the [Authorize] applies to all of the actions in the controller, which is very convenient.
But when I try to put [HttpGet], [HttpPost] or [AcceptVerbs(...)] on the controller, the compiler complains that the attribute is only usable for methods (which is right, since they are defined with [AttributeUsage] pointing only toward methods).
What if I want to limit all actions in a controller to POST verb only?
My question is:
Is there any way in the framework to achieve this, without writing my own attribute?
Why are these attributes implemented this way, not allowing them on controllers?
HttpGet and HttpPost inherits from ActionMethodSelectorAttribute Class, and are valids only for methods. I think that you need create your own attribute.
Custom attribute to solve this:
public sealed class AllowedMethodsAttribute : ActionFilterAttribute
{
private readonly string[] methods;
public AllowedMethodsAttribute(params string[] methods) => this.methods = methods.Select(r => r.Trim().ToUpperInvariant()).ToArray();
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
if (!methods.Contains(actionContext.HttpContext.Request.Method))
{
actionContext.Result = new StatusCodeResult(405);
}
}
}
Usage:
[AllowedMethods("GET", "PATCH")]
Related
So what I have is a base controller that the following [Route] definition
[Route("{application}/api/[controller]")]
public class BaseController
{
}
All of my current controllers inherit from BaseController.
What I am trying to achieve is that two different application can call my controllers and my code to be aware of what 'application' is calling it.
Application 1 should be able to call /Application1/Api/MyController
Application 2 should be able to call /Application2/Api/MyController
and both requests should go to the same controller but my code should be aware of which application called it.
I thought about having some sort of Middleware and then work out the application from the Request.Path, and then store it in something like HttpContext.Current.Items but that doesn't seem like the correct way to do it.
My personal preference here would be to pass the value as an HTTP header rather than a route parameter, especially if you want it everywhere. It means you don't need a Route attribute and a different URL per application. Using a custom ActionFilterAttribute, there's a bunch of ways you can pass this detail into your action. For example:
public class ApplicationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.Headers.TryGetValue("Application", out var values))
{
// Method 1: This allows you to specify a parameter on your action
context.ActionArguments.Add("application", values.First());
// Method 2: This adds the value into the route data
context.RouteData.Values.Add("Application", values.First());
// Method 3: This will set a property on your controller
if (context.Controller is BaseApplicationController baseController)
{
baseController.Application = values.First();
}
}
base.OnActionExecuting(context);
}
}
And apply it to action methods or your controller:
[Application]
public class FooController : Controller
{
}
Method 1 Usage:
public IActionResult Index(string application)
{
// do something with the parameter passed in
}
Method 2 Usage:
public IActionResult Index(string application)
{
var application = (string)RouteData.Values["Application"];
}
Method 3 Usage:
First, create a base controller that contains the property:
public abstract class BaseApplicationController : Controller
{
public string Application { get; set; }
}
Then make sure your controller inherits from it:
[Application]
public class FooController : BaseApplicationController
{
}
Now you can access the property on your controller:
public IActionResult Index(string application)
{
var application = this.Application;
}
Bonus Method 4:
As an aside, you could use this method to use the URL route value, using the base controller from method 3, modify the attribute to look like this:
public class ApplicationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Controller is BaseApplicationController baseController)
{
baseController.Application = (string)context.RouteData.Values["application"];
}
base.OnActionExecuting(context);
}
}
Add a route attribute to your controller:
[Route("{application}/api/[controller]/[action]")]
And now you should have the property value on the controller set.
You could move the route template into action and then each action would technically be aware of its caller context by your proposed convention:
[Route("api/[controller]")]
public class YourController : BaseController
{
[HttpGet("{application}")]
public IActionResult Get(string application)
{
if (application == "Application1")
{
...Application1 called
}
if (application == "Application2")
{
...Application2 called
}
...
}
}
Of course, this is your proposed convention and it is not enforced through some custom application authentication in any way so you will have to trust that your callers will correctly identify themselves through this convention.
Another approach, could be to have a base class variable and set that after inspecting the route.
[Route("{application}/api/[controller]")
public class BaseController: Controller
{
protected string CallingApp { get; set; }
public override void OnActionExecuting(ActionExecutingContext ctx)
{
CallingApp = ctx.RouteData.Values["application"];
base.OnActionExecuting(ctx);
}
}
I have worked in MVC on .Net 4 and I had my action filters overridden in the controller class. That way I can have multiple controllers, each with a unique OnActionExecuting method.
Now I'm working on Dotnet Core 2.1 and things are a little different so I can't get the same methodology to work.
I have a controller for an API that inherits ControllerBase and then another controller for serving pages, that one inherits Controller.
I created my action filter class that derives from ActionFilterAttribute.
I then registered my action filter by using this line:
services.AddMvc(config => { config.Filters.Add(typeof(MyActionFilter)); })
The problem is that the filter now applies to the API controller as well as the pages controller. Is there a way the action filter can be applied to specific controllers only?
I used this page as a reference for my initial action filter implementation.
I know that inside the OnActionExecuting method I can use context.Controller.GetType() to compare it to the types of my individual controllers, but I would prefer to avoid that, and instead specify which controllers should use which action filters.
Thanks in advance.
services.AddMvc(config => { config.Filters.Add(typeof(MyActionFilter)); })
The above source code is used when you want to apply the global filter that mean every API will have the affected.
So to specific particular Controller you just simply add [MyActionFilter] atribute to you controller
Example:
[MyActionFilter]
public class HomeController : Controller {
public ViewResult Index(){..}
}
First of All Register Your filter in your startup.cs
services.AddScoped<"---FilterNameHere----">();
Put Your Filter in controller like
[ServiceFilter(typeof("----FilterNameHere---"))]
public class HomeController : ControllerBase
{
public IActionResult Index()
{//do your code here}
}
Was looking for an answer too. Both above confused me. Went to the page of the documentation...
Two ways of doing this in a clean way.
An action filter for all actions of all controllers.
public class YourActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
throw new System.NotImplementedException();
}
public void OnActionExecuting(ActionExecutingContext context)
{
throw new System.NotImplementedException();
}
}
And in startup do
services.AddControllers(c => { c.Filters.Add(new YourActionFilter()); })
An Attribute that you want to use only on specific actions which implements TypeFilterAttribute and has an private class that implemements an action filter.
public class YourActionFilterAttribute : TypeFilterAttribute
{
public YourActionFilterAttribute() : base(typeof(TheActionFilter)) { }
private class TheActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
throw new System.NotImplementedException();
}
public void OnActionExecuting(ActionExecutingContext context)
{
throw new System.NotImplementedException();
}
}
}
Now you only need to add it to a whole controller or to a specific action
[YourActionFilter]
public class HomeController : ControllerBase
{
[YourActionFilter]
public IActionResult Index()
{//do your code here}
}
I always used this approach in my MVC applications before
[Route("admin")]
public class AdminController : Controller
{
}
[Route("products")]
public class ProductsAdminController :AdminController
{
[Route("list")]
public IActionResult Index()
{
...
}
}
Url.RouteUrl() for Index action returned /admin/products/list/
Now in .NET Core it ignores base class route attribute and result is just /products/list/
Is this new to .NET Core? Is there any setup so system can combine action + controller + base-controller routes?
I can't find a way to combine action + controller + base-controller automatically, but it is possible to achieve what you're looking for like this:
[Route("admin")]
public class AdminController : Controller { }
public class ProductsAdminController : AdminController
{
[Route("products/list")]
public IActionResult Index()
{
...
}
}
This approach ends up generating a URL of /admin/products/list, with the obvious downside that products is something that needs to be repeated for each action. This might be an acceptable compromise; that's up to you to decide. You could make it a bit better with a constant, like this:
private const string RoutePrefix = "products";
[Route(RoutePrefix + "/list")]
public IActionResult Index()
{
...
}
It's not pretty as it's just a workaround, but worth considering if you don't want to go with Chris Pratt's Areas suggestion.
As far as I'm aware, that never would have worked. You could use areas, and by applying the [Area] attribute to the base controller, you'd get the result you describe:
[Area("admin")]
public class AdminController : Controller
[Route("products")]
public class ProductsAdminController : AdminController
The same would work in Core, as well.
Anyone still looking for a way to define "baseURL/ControllerName" in .Net core 3.1.
You would need to define RouteAttribute on your base controller like this:
[Route("admin/[controller]")]
Note [controller] would allow the routing to automatically take the controller name in child class.
I'm pretty new to setting up routes and routing in MVC. At my last job we used attribute routing for our WebAPI stuff, so I'm pretty familiar with that (RoutePrefix, Route, and HttpGet/HttpPost attributes, etc). And I can get my current project to work just fine with attributes.
So now what I want to do is "prefix" all of the webApi routes with "api". So instead of going to mysite/test/hello, I want to go to mysite/api/test/hello.
This is what I have, using only attribute routing, and it works just fine:
[RoutePrefix("Test")]
public class TestController : ApiController
{ ....
[HttpPost]
[Route("{message}")]
public HttpResponse EchoBack(string message)
{
// return message ... in this case, "hello"
}
}
Now, I know I can change the RoutePrefix to "api/Test" (which works 100%), but I don't want to change all my controllers, I would rather be able to perform this by changing the values passed in to config.Routes.MapHttpRoute in WebApiConfig.
Is this possible?
What you describe can be done in attribute routing by using what is referred to as a global route prefix.
Referencing this article Global route prefixes with attribute routing in ASP.NET Web API
Create a DirectRouteProvider
public class CentralizedPrefixProvider : DefaultDirectRouteProvider {
private readonly string _centralizedPrefix;
public CentralizedPrefixProvider(string centralizedPrefix) {
_centralizedPrefix = centralizedPrefix;
}
protected override string GetRoutePrefix(HttpControllerDescriptor controllerDescriptor) {
var existingPrefix = base.GetRoutePrefix(controllerDescriptor);
if (existingPrefix == null) return _centralizedPrefix;
return string.Format("{0}/{1}", _centralizedPrefix, existingPrefix);
}
}
To quote article.
The CentralizedPrefixProvider shown above, takes a prefix that is
globally prepended to every route. If a particular controller has it’s
own route prefix (obtained via the base.GetRoutePrefix method
invocation), then the centralized prefix is simply prepended to that
one too.
Configure it in the WebAPiConfig like this
config.MapHttpAttributeRoutes(new CentralizedPrefixProvider("api"));
So now for example if you have a controller like this
[RoutePrefix("Test")]
public class TestController : ApiController {
[HttpPost]
[Route("{message}")]
public IHttpActionResult EchoBack(string message) { ... }
}
The above action will be accessed via
<<host>>/api/Test/{message}
where api will be prepended to the controller actions route.
I have:
[RoutePrefix("api/Order")]
public class OrderController : ApiController
{
[Route("~/api/Order/{id}")]
[Route("~/api/ManualOrder/{id}")]
[HttpGet]
public Task<HttpResponseMessage> Get(Guid id)
{
//Implementation
}
[Route("ExampleOtherNormalMethod")]
[HttpGet]
public Task<HttpResponseMessage> ExampleOtherNormalMethod()
{
//Implementation
}
}
And:
[RoutePrefix("api/ManualOrder")]
public class ManualOrderController : ApiController
{
//Other methods
}
The strategy used by route rewriting is to get the "Get" method called either "/api/Order/1" or "/api/ManualOrder/1" both pointing to "OrderController", this works.
The problem is when I request any other method in the "ManualOrder" I think it gets lost and can not resolve and returns the exception:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
Project.ProxyAPI.Controllers.ManualOrderController
Projects.ProxyAPI.Controllers.OrderController
Does anyone know how to solve this without duplicating the "Get" method on both controllers?
P.s: This is a hypothetical example.
I'm sure your example is much more complex than the one you present here. But based on the information in your example you could let ManualOrderController inherit from OrderController. I think it makes more sense when you assign the routes. The Route-attributes will not be inherited, so it shouldn't be any problem.
Does anyone know how to solve this without duplicating the "Get"
method on both controllers?
Yes, it will be a duplicate, but it won't contain any logic it will just be a fall-through...
[RoutePrefix("api/Order")]
public class OrderController : ApiController
{
[Route("~/api/Order/{id}")]
[HttpGet]
public virtual Task<HttpResponseMessage> Get(Guid id)
{
//Implementation
}
[Route("ExampleOtherNormalMethod")]
[HttpGet]
public Task<HttpResponseMessage> ExampleOtherNormalMethod()
{
//Implementation
}
}
[RoutePrefix("api/ManualOrder")]
public class ManualOrderController : OrderController
{
[Route("~/api/ManualOrder/{id}")]
[HttpGet]
public override Task<HttpResponseMessage> Get(Guid id)
{
return base.Get(id);
}
//Other methods
}
There is a downside to this approach - ManualOrderController will expose methods from OrderController via your default routing table. Depending on what your routing table looks like api/ManualOrder/ExampleOtherNormalMethod may call ExampleOtherNormalMethod on OrderController. This may, or may not, be a desired behavior.