I have an ASP.NET controller where every single method will have a shared parameter. With attribute routing, I can add this parameter in the controller's route.
However, I still need to add that parameter along with a validation attribute in every single method. Is there a way for me to do the validation in one place or avoid having to pass it in to every single method?
This is the current working code:
[ApiController]
[Route("[controller]/{name}")]
public class ExampleController : ControllerBase
{
[HttpGet]
public string Sample([StringLength(10)][FromRoute]string name)
{
}
[HttpGet]
[Route("defaults")]
public string GetDefaults([StringLength(10)][FromRoute]string name)
{
}
[HttpGet]
[Route("objects/{id}")]
public string Sample([StringLength(10)][FromRoute]string name, [FromRoute]string id)
{
}
}
Is it possible to get something close to this? (I know the validation parameter on the controller is invalid, but I'd like to just have to apply it once)
[ApiController]
[StringLength(10)]
[Route("[controller]/{name}")]
public class ExampleController : ControllerBase
{
[HttpGet]
public string Sample()
{
}
[HttpGet]
[Route("defaults")]
public string GetDefaults()
{
}
[HttpGet]
[Route("objects/{id}")]
public string Sample([FromRoute]string id)
{
}
}
You can do this using a custom action filter to validate the name parameter:
public class ValidateNameParameterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionParameters.ContainsKey(name))
{
// the trick is to get the parameter from filter context
string name = filterContext.ActionParameters[name] as string;
// validate name
if (/*name is not valid*/)
{
// you may want to redirect user to error page when input parameter is not valid
filterContext.Result = new RedirectResult(/*urlToRedirectForError*/);
}
base.OnActionExecuted(filterContext);
}
}
}
Now you can apply the filter to your controller, or specific actions:
[ApiController]
[Route("[controller]/{name}")]
[ValidateNameParameter] // <-- execute this for all actions in the controller
public class ExampleController : ControllerBase
{
[HttpGet]
public string Sample([StringLength(10)][FromRoute]string name)
{
}
[HttpGet]
[Route("defaults")]
public string GetDefaults([StringLength(10)][FromRoute]string name)
{
}
[HttpGet]
[Route("objects/{id}")]
// [ValidateNameParameter] // <-- execute for this specific action
public string Sample([StringLength(10)][FromRoute]string name, [FromRoute]string id)
{
}
}
See this tutorial for more information.
As Hooman Bahreini said , you could customize a action filter that inherits ActionFilterAttribute and use it as a attribute on the controller .In Asp.net core , ActionArguments replaces ActionParameters
public class ValidateNameParameterAttribute: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionArguments.ContainsKey("name"))
{
string name = filterContext.ActionArguments["name"] as string;
if(name!=null && name.Length>10)
{
filterContext.Result = new BadRequestObjectResult("The length of name must not exceed 10");
}
}
}
}
For more details on Filters in ASP.NET Core , you could refer to here .
Related
In the documentation of ODATA's WebAPI there is a page about Attribute Routing.
In this page, there is an example about using ODataRoutePrefixAttribute when all requests to a particular controller have the same prefix, and this prefix can include a parameter. In the sample, all action methods declare the same parameter. From their sample:
[ODataRoutePrefix("Customers({id})")]
public class MyController : ODataController
{
[ODataRoute("Address")]
public IHttpActionResult GetAddress(int id)
{
......
}
[ODataRoute("Address/City")]
public IHttpActionResult GetCity(int id)
{
......
}
[ODataRoute("/Order")]
public IHttpActionResult GetOrder(int id)
{
......
}
}
I would like to avoid repeating the parameter in each and every method and just have it be a property of the class, like this:
[ODataRoutePrefix("Customers({id})")]
public class MyController : ODataController
{
public int Id
{
get { ... }
}
[ODataRoute("Address")]
public IHttpActionResult GetAddress()
{
......
}
}
How to get the value of the id parameter from the URL when it is not passed as parameter to the action method?
I found out that I could implement the property getter by reading the value from the RequestContext.RouteData.Values:
public string Id => (string)this.RequestContext.RouteData.Values["id"];
One drawback of this solution is that route data values do not seem to be available during the controller's Initialize method, so one needs to be cautious not to depend on such properties in there.
Suppose I have a Controller with 100's of ActionResult's Like Below
public class BasicController : Controller
{
public ActionResult Apple1()
{
}
public ActionResult Apple2()
{
}
.
.
.
public ActionResult Apple100()
{
}
public ActionResult Mango1()
{
}
public ActionResult Mango2()
{
}
.
.
.
public ActionResult Mango100()
{
}
}
Now How can I set [Authorize(Role="AppleAdmin")] to only Apple named methods and [Authorize(Role="MangoAdmin")] Mango named methods?
I know we can do it by decorating each ActionResult individually. But What I want to know is, Is there a way to set the Authorize to a Group of ActionResults once. So that makes me use the Authorize attribute only twice in my above scenario.
Assuming cleaning up the controller isn't an option, the only answer I can think of is a custom AuthorizeAttribute on the controller.
If you override the OnAuthorization method you can inspect the AuthorizationContext argument for the action name (filterContext.ActionDescriptor.ActionName) and set filterContext.Result = new HttpUnauthorizedResult if it fails your logic.
Something like
public class AppleMangoAuthorizeAttribute : AuthorizeAttibute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.ActionDescriptor.ActionName.Contains("Apple") /*&& some other failing logic*/)
{
filterContext.Result = new HttpUnauthorizedResult();
}
else if (/*same for mango*/)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
Then
[AppleMangoAttribute]
public class BasicController : Controller
{
public ActionResult Apple1()
{
}
}
I would separate the controller in 2 classes and just modify the routing to access the 2 controllers via the same route.
Note: Personally I haven't tested if this solution will crash the routing but it's the best I could come up with:
[Authorize(Role="AppleAdmin")]
[Route("BasicController")]
public class BasicControllerApple : Controller
{
public ActionResult Apple1()
{
}
}
[Authorize(Role="MangoAdmin")]
[Route("BasicController")]
public partial class BasicControllerMango : Controller
{
public ActionResult Mango1()
{
}
}
I've got a simple question about the behavior of ActionResult in ASP.NET MVC.
I need to have two action result with the same name but different parameters.
In particular I want this:
public class CarController : Controller
{
[AuthorizeUser(AccessLevel = "New")]
public ActionResult New()
{
return View("New");
}
[AuthorizeUser(AccessLevel = "New?type=2")]
public ActionResult New(string type)
{
return View("NewCarType2");
}
}
I know that I can rename the second action result with a different name, but if I want to keep it to "new"?
I've tried with route.maps without success.
I've think to do this:
public class CarController : Controller
{
public ActionResult New (string type) {
if(!string.IsNullOrEmpty(type) && type.Equals("2")) {
return NewCarType2()
else
return New()
}
}
[AuthorizeUser(AccessLevel = "New?type=2")]
private ActionResult NewCarType2()
{
return View("NewCarType2");
}
[AuthorizeUser(AccessLevel = "New")]
private ActionResult New()
{
return View("New");
}
}
but the attribute for the user's authorization is ignored.. also with the method's signature public and the attribute [NoAction].
The only way that I find to do what I want is with a class inheritance:
public class CarController : Controller
{
public virtual ActionResult New(string type)
{
if (!string.IsNullOrEmpty(type) && type.Equals("2"))
return RedirectToAction("NewCarType2", "CarOverride");
else
return RedirectToAction("New", "CarOverride");
}
}
public class CarOverrideController : CarController
{
[AuthorizeUser(AccessLevel = "New?type=2")]
public ActionResult NewCarType2(string type)
{
return View("NewCarType2");
}
[AuthorizeUser(AccessLevel = "New")]
public override ActionResult New(string type)
{
return View("New");
}
}
But is this the correct way to manage this situation (a part write the name of the ActionResults all different)?
There are 2 possibilities to do what you want.
As Puneet replied in the comment, you can overload the method in the controller as mentioned in this post.
[ActionName("MyOverloadedName")]
or
You can define a Route attribute on the controller method and route the action from a different URL to this method. For further explanation go to the ASP.NET Tutorial.
[Route("URL")]
public ActionResult New(string type) { ... }
I would question why you need two routes with the same name which are doing two different things?
If all your AuthorizeUser attribute is doing is checking the user's role, why not do something similar to:
public ActionResult New(string type)
{
if (!string.IsNullOrWhiteSpace(type) && User.IsInRole("YOUR-ROLE-HERE"))
{
// Display NewCarType2 view if type requested and user is in correct role
return View("NewCarType2");
}
// Default to New view if no type requested or user not in role
return View("New");
}
This will catch both /new and /new?type=foo requests.
If I have this action:
public ActionResult MyAction(long? personId)
{
// ...
}
I can call that action with this URL:
localhost/MyAction/?personId=x
I want to be able to send another extra parameter (token), which is going to be in all of my ajax calls from the client:
localhost/MyAction/?personId=x&token=asdf
Without having to declare this new parameter in all of my action signatures. That it: I want to avoid this:
public ActionResult MyAction(long? personId, string token)
{
// ...
}
public ActionResult MyAction2(long? animalId, string token)
{
// ...
}
etc.
But I also want to be able to have access to the token parameter from inside the method. That is:
public ActionResult MyAction(long? personId)
{
// I can check the token's value or manipulate it, eg:
if (token.Equals(...)) { .. }
}
Question:
Is there a way to declare this parameter implicitly in all (or some) of my actions? Maybe using attributes?
You could derive all your controllers from a common base controller.
Then, override OnActionExecuting in that and access the Querystring. For example:
public class BaseController : Controller
{
protected string Token { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
string token = Request.QueryString["token"] as string;
Token = token ?? string.Empty;
}
}
I find that a cleaner solution than a static class. Also, the BaseController is a useful pattern in MVC to share code that is used in many controllers.
public class Token
{
public static string Value
{
get
{
return System.Web.HttpContext.Current.Request["token"];
}
}
}
public ActionResult MyAction(long? personId)
{
// I can check the token's value or manipulate it, eg:
if (Token.Value.Equals(...)) { .. }
}
Do not forget to account for Token.Value being null.
[AuthenticateUser]
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
[AllowAnonymous]
public ActionResult List()
{
return View();
}
}
How to remove authentication for action named as List? Please advise....
My Custom Filter coding as follow.. i have inherited the FilterAttribute call as well.
Please advise regarding
public class AuthenticateUserAttribute: FilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext context)
{
if (this.IsAnonymousAction(context))
{
}
if (user == "user")
{
// do nothing
}
else
{
context.Result = new HttpUnauthorizedResult(); // mark unauthorized
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext context)
{
if (context.Result == null || context.Result is HttpUnauthorizedResult)
{
context.Result = new RedirectToRouteResult("Default",
new System.Web.Routing.RouteValueDictionary{
{"controller", "Home"},
{"action", "List"},
{"returnUrl", context.HttpContext.Request.RawUrl}
});
}
}
}
The below code generate the error message : Error 1 The best overloaded method match for 'MVC5Features.Filters.AuthenticateUserAttribute.IsAnonymousAction(System.Web.Mvc.AuthorizationContext)' has some invalid arguments c:\users\kirupananthan.g\documents\visual studio 2013\Projects\MVC5Features\MVC5Features\Filters\AuthenticateUserAttribute.cs 16 17 MVC5Features
Error 2 Argument 1: cannot convert from 'System.Web.Mvc.Filters.AuthenticationContext' to 'System.Web.Mvc.AuthorizationContext' c:\users\kirupananthan.g\documents\visual studio 2013\Projects\MVC5Features\MVC5Features\Filters\AuthenticateUserAttribute.cs 16 40 MVC5Features
if (this.IsAnonymousAction(context))
Since it is your custom filter, you can extend it to handle AllowAnonymous (if you don't want to use AllowAnonymous, yoy can create own f.e. NoAuthentication):
public class AuthenticateUser : IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
if (this.IsAnonymousAction(filterContext))
{
return;
}
// some code
}
private bool IsAnonymousAction(AuthenticationContext filterContext)
{
return filterContext.ActionDescriptor
.GetCustomAttributes(inherit: true)
.OfType<AllowAnonymousAttribute>()
//or any attr. you want
.Any();
}
}
Try the
[AllowAnonymous]
attribute
Maybe if you specify a specific User Group for that action and in your custom authentication filter allow this group for everything.
In MVC 5 and I quote from http://www.dotnetcurry.com/showarticle.aspx?ID=975
The class CustomOverrideAuthorizationAttribute is inherited from the FilterAttribute class and implements IOverrideFilter. This interface is used to define the filters applied on the controller. The property FiltersToOverride returns the IAuthorizationFilter type. This means that Authorize filter applied on the parent (controller or Global application class) will be overridden
I believe you should remove the attribute from the controller and put it on each action method except List.
So, reading the article that #Bilal posted (Oct 30 '14 at 12:24), it seems there's an elegant way to override filters by class (or interface). You'd have to write a custom attribute for each filter that you want to override, but that may not be a huge problem, if you consider that you probably don't want to override many filters, right?
So, in your question you want to override the AutherizationUser attribute, so you'd implement this class:
public class CustomOverrideAuthenticateUserAttribute :
FilterAttribute, IOverrideFilter
{
public Type FiltersToOverride
{
get
{
return typeof(AuthenticateUserAttribute);
}
}
}
And rewrite your controller as:
[AuthenticateUser]
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
[CustomOverrideAuthenticateUser]
public ActionResult List()
{
return View();
}
}