I have the following Web API ActionFilterAttribute
namespace namespace.Filters {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
public class LogApiRequestActionFilterAttribute : ActionFilterAttribute {
public LogApiRequestActionFilterAttribute() {
}
private void logData(HttpActionContext actionContext) {
var controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
var actionName = actionContext.ActionDescriptor.ActionName;
var parameters = "";
foreach (var item in actionContext.ActionArguments) {
parameters += string.Format("{0} = {1}, ", item.Key, item.Value);
}
if (parameters.Any()) {
parameters = parameters.Remove(parameters.Count() - 2);
}
var message = string.Format("{0}.{1}({2})", controllerName, actionName, parameters);
// Do the logging
}
public override void OnActionExecuting(HttpActionContext actionContext) {
logData(actionContext);
base.OnActionExecuting(actionContext);
}
}
}
When I add it globally over the WebApiConfig.cs like this:
config.Filters.Add(new LogApiRequestActionFilterAttribute());
But the logData method never gets called. Does anyone know why?
Did you decorate your controller (or any action method), with that filter ?
For e.g.
After creating a filter called LogApiRequestActionFilterAttribute, you need to register it (as you have done).
After registration, if the filter has to be applied at the controller level, then decorate your controller with that filter.
If the filter has to be applied only on certain action method, put the filter on that particular action method(s)
[LogApiRequestActionFilter]
public class YourController : ApiController
{ //--->controller level filter
[LogApiRequestActionFilter] //-->action level filter
public IHttpActionResult DoAction()
{
}
}
If neither of them works out, when the action method executes, put a breakpoint and analyse the http request. Check If your filter is registered or not
I just found my problem, thanks a lot to now he who must not be named.
My filter was properly registered and also perfectly loaded, just the method I was testing AND debugging with, wasn't called with a request. It was called from an razor helper and there the Request-property is null of course.
So the fix is to use an action which is called with an http request and not just as normal method call.
Related
I am required to record user action and I don't want to have code in every method is every controller so would it make sense to somehow do this in the basecontroller ? OR is there a better way?
public class BaseController : Controller
{
protected ILogger logger;
public BaseController(ILogger<BaseController> logger)
{
this.logger = logger;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
//How do I get the current controller?
//How do I get the current method being called?
//How can I pass in additional parameters?
//How can I get the user?
logger.LogWarning("Loaded BaseController");
base.OnActionExecuting(context);
}
}
There are many ways to do that.
First: You could create your own base controller and implement OnActionExecution as you did. See the sample bellow to get information from ActionExecutingContext.
If you go this way, every controller that inhirits from this base controller will get the implementation of the logger because you are overriding OnActionExecuting (that applies to all actions of your controller).
public override void OnActionExecuting(ActionExecutingContext context)
{
//How do I get the current controller?
string controllerName = context.ActionDescriptor.ControllerDescriptor.ControllerName
//How do I get the current method being called?
string actionName = context.ActionDescriptor.ActionName;
//How can I pass in additional parameters?
foreach (var parameter in context.ActionParameters)
{
var parameterKey = parameter.Key;
var parameterValue = parameter.Value;
}
//How can I get the user?
var user = this.User; // IPrinciple instance, explore this object
logger.LogWarning("Loaded BaseController");
base.OnActionExecuting(context);
}
Second: On the other hand, you can use ActionFilters which is a class that inhirits from ActionFilter class and do the same implementation on this classe overriding the OnActionExecuting. Then you can decorate your controllers with this attribute to make the logger. Given it is an attribute, you have to define the name fo the class with a sufix Attribute and use without it. For sample:
public class LoggerAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
// same code above
}
}
[Logger]
public class CustomerController : Controller
{
// actions code...
}
Third: Use the same action filter class and instead of applying on all classes you want, you define it as a global action filter and it will be applied to all controllers. You have to define it on GlobalFilter and if you are using the default template of asp.net mvc, you can define it on the FilterConfig.cs, for sample:
filters.Add(new LoggerAttribute());
For getting controller & action names you can use ActionDescriptor of ActionExecutingContext
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var descriptor = filterContext.ActionDescriptor;
var actionName = descriptor.ActionName;
var controllerName = descriptor.ControllerDescriptor.ControllerName;
......
base.OnActionExecuting(filterContext);
}
Regarding User information: controller initialisation will occur before authorisation takes place. So all of yours controllers will be created before any OnAuthorization takes place.
Approach to deal with these situations is to use Action Filters. The Authorize Attribute is fired early than controller initialisation occur.
Have a look this articles:
How to get controller and action name in OnActionExecuting?
How do I get the action name from a base controller?
Getting User Identity on my base Controller constructor
I commonly need to authorize a particular parameter to be evaluated in a service call within an action in MVC5. For instance, let's say that my action is public ActionResult Edit(string partnerName).
Today, I handle this by always evaluating if (!User.CanAccessPartnerModule(THIS_MODULE_ID, partnerName)) throw new UnauthorizedException();
However, I would like to be able to do something like this:
[Authorize(Roles = THIS_MODULE_ID)]
public ActionResult Edit([AuthorizePartnerModule(THIS_MODULE_ID)] string partnerName)
{
...
}
To be clear, 1) I don't think the AuthorizeAttribute would be necessary if this were implemented as I envision, and 2) the thing that doesn't exist is the AuthorizePartnerModuleAttribute.
Is there a ready-made attribute or tutorial that explains how this may be accomplished? And if not, is this not advisable to do?
You could extend authorization with a custom authorization filter by creating a subclass of AuthorizeAttribute
using System.Web;
using System.Web.Mvc;
namespace Filters
{
public class AuthorizePartnerModule : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// getting the parameter from the request
string partnerName = httpContext.Request.Params["groupId"].ToString();
// custom validation
return User.CanAccessPartnerModule(THIS_MODULE_ID, partnerName);
}
}
}
And then, you could validate your action method with:
[AuthorizePartnerModule(Roles = THIS_MODULE_ID)]
public ActionResult Edit(string partnerName)
{
...
}
Another option would be to create a custom ActionFilter (an implementation of IActionFilter). An ActionFilter implements two methods:
OnActionExecuting is executed right before the action method
OnActionExecuted is executed right after the action method execution.
So, you could make the necessary validation with something like:
using System.Web.Mvc;
namespace Filters {
public class AuthorizePartnerModule : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
// getting the parameter from the request
string partnerName = filterContext.ActionParameters["partnerName"].ToString();
// custom validation
if (!User.CanAccessPartnerModule(THIS_MODULE_ID, partnerName))
{
filterContext.Result = new HttpNotFoundResult();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// do nothing
}
}
}
In this case, however ,you would have to validate like that:
[Authorize(Roles = THIS_MODULE_ID)]
[AuthorizePartnerModule]
public ActionResult Edit(string partnerName)
{
...
}
I have an action filter which (among other things), adds stuff to the RouteData. The value, however, is not picked up by the parameter in my action method. Any ideas why?
Action Filter:
public class SomeFilter : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var someData = new SomeClass();
//do stuff
filterContext.RouteData.Values["someData"] = someData;
}
}
Action Method:
[SomeFilter]
public ViewResult SomeActionMethod(SomeClass someData)
{
//someData is null here
}
Please note that the following line inside my action method does return something the data saved into it in action filter:
SomeClass isNotNull = RouteData.Values["someData"] as SomeClass;
Anyone knows why?
The filter is attached to the action (method). Hence by the time the filter is run, the values for the parameters have already been chosen. Imagine the situation if what you asked worked:
[SomeFilter]
public ViewResult SomeActionMethod()
{
// ....
}
public ViewResult SomeActionMethod(SomeClass someData)
{
// .....
}
You reference http://mysite.com/mycontroller/SomeActionMethod with no query parameter. So then it should call the first action. But if your filter were to do what you wanted, after it ran, it should call the second action. But that one DOESN'T have the filter, so it should call the first. And round & round.
Here's an article that covers how to modify the Parameter values from an Action Filter:
http://haacked.com/archive/2010/02/21/manipulating-action-method-parameters.aspx/
I want to redirect the user to a different view if they are using a mobile browser. I've decided I'd like to do this using MVC filters by applying it to actions which I want to have a mobile view.
I believe this redirect needs to happen in OnActionExecuted, however the filterContext does not contain information on the view - it does, however in OnResultExecuted, but by this time I believe it is too late to change the view.
How can I intercept the view name and change the ViewResult?
This is what I have in the result executed and what I'd like to have work in Action Executed.
public class MobilePageFilter : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.Result is ViewResult)
{
if (isMobileSite(filterContext.HttpContext.Session[SetMobile.SESSION_USE_MOBILE]))
{
ViewResult viewResult = (ViewResult)filterContext.Result;
string viewName = viewResult.ViewName;
filterContext.Result = new ViewResult
{
ViewName = "Mobile/" + viewName,
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
}
}
base.OnResultExecuted(filterContext);
}
}
I would recommend you the following blog post which explains a better alternative to achieve what you are asking for rather than using action filters.
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!
I have a number of Controllers in my project that all inherit from a controller I've named BaseController. I wrote a custom attribute that I applied to the entire BaseController class, so that each time an action runs in any of my controllers, that attribute will run first.
The problem is that I have a couple of controller actions that I'd like to ignore that attribute, but I don't know how to do it.
Can anyone help? I'm using MVC 1.
Thanks.
In your custom attribute, you can add this ShouldRun() check like this:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (ShouldRun(filterContext))
{
// proceed with your code
}
}
private bool ShouldRun(ActionExecutingContext filterContext)
{
var ignoreAttributes = filterContext.ActionDescriptor.GetCustomAttributes(typeof(IgnoreMyCustomAttribute), false);
if (ignoreAttributes.Length > 0)
return false;
return true;
}
ShouldRun() simply checks whether there's a "IgnoreMyCustomAttribute" on your action. If it's there, then your custom attribute won't do anything.
You'll now want to create a simple IgnoreMyCustomAttribute, which doesn't do anything:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class IgnoreMyCustomAttribute: ActionFilterAttribute
{
}
Whenever you decorate your controller action with [IgnoreMyCustom], then MyCustomAttribute won't do anything. e.g.:
[IgnoreMyCustom]
public ViewResult MyAction() {
}
I had a similar need for something like this and found that by creating an authorization filter (implementing/deriving from FilterAttribute, IAuthorizationFilter) rather than a regular action filter (deriving from ActionFilterAttribute), and setting Inherited=true and AllowMultiple=false on the attribute, that it would only run once at the appropriate spot.
This means I am able to "cascade" my filter down from a base controller (the site-wide default), to a derived controller (for example the AdminController or whatever), or even further down to an individual action method.
For example,
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, Inherited=true, AllowMultiple=false)]
public class MyCustomAttribute : FilterAttribute, IAuthorizationFilter
{
private MyCustomMode _Mode;
public MyCustomAttribute(MyCustomMode mode)
{
_Mode = mode;
}
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
// run my own logic here.
// set the filterContext.Result to anything non-null (such as
// a RedirectResult?) to skip the action method's execution.
//
//
}
}
public enum MyCustomMode
{
Enforce,
Ignore
}
And then to use it, I can apply it to my super-controller,
[MyCustomAttribute(Ignore)]
public class BaseController : Controller
{
}
And I can change/override it for specific controllers, or even for specific actions!
[MyCustomAttribute(Enforce)]
public class AdministrationController : BaseController
{
public ActionResult Index()
{
}
[MyCustomAttribute(Ignore)]
public ActionResult SomeBasicPageSuchAsAHelpDocument()
{
}
}
This allowed me to "turn off" the filter for specific cases, while still being able to apply it as a default on either the whole controller or whole application.
Good luck!
I'm not sure there is an easy way to remove attributes in this situation. But I have done something similar for a project and what I did, as it was only in a few instances I didn't want my attribute to run, was to create two attributes.
My first attribute was applied to my base controller as you've done but it was aware of the existance of a second attribute and by implementing that second attribute I could disable the attribute on the base class from running.
Not sure if it was the best solution but it worked for me.
This was applied to the base controller:
/// <summary>
/// This is used to force the schema to HTTP is it is HTTPS.
/// RequireHttpsAttribute or OptionalHttpsAttribute takes precedence if used.
/// </summary>
public class RequireHttpAttribute : FilterAttribute, IAuthorizationFilter
{
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (filterContext.HttpContext.Request.IsSecureConnection)
{
object[] attributes = filterContext.ActionDescriptor.GetCustomAttributes(true);
if (!attributes.Any(a => a is RequireHttpsAttribute || a is OptionalHttpsAttribute))
{
HandleHttpsRequest(filterContext);
}
}
}
protected virtual void HandleHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET method, otherwise browser may not propogate the verb and request body correctly
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException(MvcResources.RequireHttpAttribute_MustNotUseSsl);
// redirect to HTTP version
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
Like so:
[RequireHttp]
public abstract class Controller : System.Web.Mvc.Controller
{
}
I could then use what is effectively a dummy attribute to disable it.
/// <summary>
/// This attribute allows the action to be server on HTTP and HTTPS but neither is enforce.
/// RequireHttpsAttribute takes precedence if used.
/// </summary>
public class OptionalHttpsAttribute : FilterAttribute
{
// This is deliberately empty, the attribute is used by RequireHttpAttribute to stop it changing schema to HTTP
}
Like so:
[OptionalHttps]
public ActionResult OptionalHttps()
{
return View();
}