I'm overriding ASP.NET MVC's Controller.HandleUnknownAction(string actionName) method. It's being called when an action is not found and also when an HTTP method is not allowed. How can I distinguish between the two? I'd like to return a 404 when and action is not found and 405 when a method is note allowed.
The simplest way I can think of is to create custom action filter. This will allow you to return http status code result if method is not allowed
public class HttpPostFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!(filterContext.RequestContext.HttpContext.Request.GetHttpMethodOverride().Equals("post", StringComparison.InvariantCultureIgnoreCase)))
{
filterContext.Result = new HttpStatusCodeResult(405);
}
}
}
Or better, create more generic version of it, much like AcceptVerbsAttribute
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class AllowMethodsAttribute : ActionFilterAttribute
{
public ICollection<string> Methods
{
get;
private set;
}
public AllowMethodsAttribute(params string[] methods)
{
this.Methods = new ReadOnlyCollection<string>(methods);
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string httpMethodOverride = filterContext.HttpContext.Request.GetHttpMethodOverride();
if (!this.Methods.Contains(httpMethodOverride, StringComparer.InvariantCultureIgnoreCase))
{
filterContext.Result = new HttpStatusCodeResult(405);
}
}
}
And use it like
[AllowMethods("GET")]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
Customizing attribute to take HttpVerbs as parameter is up to you.
Related
I've a custom AuthorizeAttribute in my website. It has some logic about the Result created for unathorized requests.
In some cases, I want to trigger its validation manually*. I don't know if its possible. As I haven't found how to do that, I thought that I could extract the logic to get the Result to a diferrent method, and call it when I want. But then I don't know how to execute the ActionResult (outside de controllers).
How can I do to manually execute authorize validation? If not possible, how can I do to execute an ActionResult outside a controller?
*I need to trigger it manually because some request may pass the validation (because the session is created) and then, when accessing my services, found that the session was closed by someone else. I wouldn't like to add a call to the services in OnAuthorization to reduce services calls.
I'm not sure if its the best, but I've found a way to get it working (still listening for better answers).
When I call the services and notice that the work session has expired, all I do is removing the active user in the web session.
My custom authorize attribute also implements IResultFilter and IExceptionFilter.
In both OnResultExecuted and OnException I validate the active user once more. If the session was removed, then apply the same ActionResult that I would apply in OnAuthorization.
Here is the final class:
public class CustomAuthorizeAttribute : AuthorizeAttribute, IResultFilter, IExceptionFilter
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public void OnException(ExceptionContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
{
filterContext.Result = result;
filterContext.ExceptionHandled = true;
}
}
public static ActionResult Validate(HttpContextBase httpContext)
{
if (UserActiveInSession)
return null;
// Different rules to build an ActionResult for this specific case.
}
}
I did not get Diego answer's, But Just simply answering the title, I got it to work like that, You can use it as attribute on controllers actions and also trigger it manually at any place in C# or in Razor views.
namespace SomeNameSpace
{
public class CustomAuthorizeAttributeMVC : AuthorizeAttribute
{
private readonly string[] rolesParams;
public CustomAuthorizeAttributeMVC(params string[] roles)
{
this.rolesParams = roles;
}
public bool IsAuthorized { get {
//Do your authorization logic here and return true if the current user has permission/role for the passed "rolesParams"
string[] allowedRoles = new string[] {"role 1", "role 2", "role 3"};
return allowedRoles.Intersect(rolesParams).Any(); //for the example
}
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return this.IsAuthorized;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//...
}
}
public class AuthorizeHelper
{
public static bool HasPermission(params string[] roles)
{
return new CustomAuthorizeAttributeMVC(roles).IsAuthorized;
}
}
}
Usage example:
[CustomAuthorizeAttributeMVC("role 2")]
public ActionResult SomeAction()
{
return Content("Authorized !");
}
public ActionResult SomeOtherAction()
{
if(AuthorizeHelper.HasPermission("role 2"))
{
return Content("Authorized !");
}
return Content("401 Not Authorized !");
}
And as said, it can be used in Razor views by calling it normally
#if(AuthorizeHelper.HasPermission("role 2")) {
//...
}
Thanks
I have a controller in my MVC app for which I'm trying to log details using a custom ActionFilterAttribute, by using the onResultExecuted method.
I read this tutorial to understand and write my own action filter. The question is how do I pass variables from the controller to the action filter?
I want to get the input variables with which a controller is called. Say, the username/user ID.
If (in some situations) an exception is thrown by any controller method, I would want to log the error too.
The controller -
[MyActionFilter]
public class myController : ApiController {
public string Get(string x, int y) { .. }
public string somemethod { .. }
}
The action filter -
public class MyActionFilterAttribute : ActionFilterAttribute {
public override void onActionExecuted(HttpActionExecutedContext actionExecutedContext) {
// HOW DO I ACCESS THE VARIABLES OF THE CONTROLLER HERE
// I NEED TO LOG THE EXCEPTIONS AND THE PARAMETERS PASSED TO THE CONTROLLER METHOD
}
}
I hope I have explained the problem here. Apologies if I'm missing out some basic objects here, I'm totally new to this.
Approach - 1
Action Filter
public class MyActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
}
}
Action Method
[MyActionFilter]
public ActionResult Index()
{
ViewBag.ControllerVariable = "12";
return View();
}
If you pay attention to the screenshot, you can see the ViewBag information
Approach - 2
Action Filter
public class MyActionFilter : ActionFilterAttribute
{
//Your Properties in Action Filter
public string Property1 { get; set; }
public string Property2 { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
}
Action Method
[MyActionFilter(Property1 = "Value1", Property2 = "Value2")]
public ActionResult Index()
{
return View();
}
I suggest another approach, and it is passing parameters to Action Filter as constractor.
[PermissionCheck(Permissions.NewUser)]
public ActionResult NewUser()
{
// some code
}
Then in the ActionFilter:
public class PermissionCheck : ActionFilterAttribute
{
public Permissions Permission { get; set; }
public PermissionCheck(Permissions permission)
{
Permission = permission;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (/*user doesn't have that permission*/)
{
filterContext.Result = new RedirectToRouteResult
(
new RouteValueDictionary
(
new {
controller = "User",
action = "AccessDeny",
error = "You don't have permission to do this action"
}
)
);
base.OnActionExecuting(filterContext);
}
}
}
Which Permissions is an ENUM like:
enum Permissions {NewUser, Edit, Delete, Update, ...}
i have ActionFilterAttribute like the following
class MyCustomRouteConstraint : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (something == 1) //i know there is no something variable
{
// do something
}
base.OnActionExecuting(filterContext);
}
}
as you can see in my code there is no variable named as something.
but i want to use the variable named as something in my action.
public ActionResult Index()
{
int something = 1;
return View();
}
public ActionResult About()
{
int something = 2;
return View();
}
public ActionResult Contact()
{
int something = 1;
return View();
}
I think what you need is OnActionExecuted. I have not tested.
class MyCustomRouteConstraint : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var something = Convert.ToInt32(filterContext.RouteData.Values["something"]);
if(something == 1)
{
//do something
}
base.OnActionExecuting(filterContext);
}
}
OnActionExecuting is called by the ASP.NET MVC framework BEFORE the action method executes. So it doesn't make sense to initialize your something variable in the action body. But still if you are going to override some method which is called AFTER the action method executes you can probably use ViewBag to init the variable in the controller and then get its value using filterContext.
You use ViewBag for this as well.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewBag.SommeVariable = "Test";
}
And in your action
public ActionResult Index()
{
var variable = ViewBag.SommeVariable;
return View();
}
I use structuremap dependency injection to do that. See here some snippets:
namespace something.Infrastructure.ActionFilters {
public class PlatformAuthorizeAttribute : AuthorizeAttribute
{
public IRepository<User> UserRepo { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (WebSecurity.Initialized && filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (filterContext.HttpContext.User.IsInRole("Banned"))
{
WebSecurity.Logout();
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{"Controller", "Home"},
{"Action", "Banned"},
{"Area", ""}
});
}
UserRepo.Dispose();
UserRepo = null;
}
}
}
}
And then initialize the following mapping in your IoC structuremap class:
public static class IoC
{
public static IContainer Initialize()
{
ObjectFactory.Initialize(x =>
{
x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
});
x.SetAllProperties(pset =>
{
pset.WithAnyTypeFromNamespace("something.Infrastructure.ActionFilters");
pset.OfType<IRepository<User>>();
});
});
return ObjectFactory.Container;
}
}
I think this is the cleanest way to provide your action filters with properties. Note that in my example I used a custom AuthorizeAttribute. You will need to change this to ActionFilterAttribute and use the OnActionExecuting method override to access the properties.
Use HttpContext.Items
HttpContext.Items["something"] = 1;
In ActionFilter, you can access it as:
var something = (int)filterContext.HttpContext.Items["something"];
However, your example of action filter is OnActionExecuting; this will execute before any of your Index/About actions executes. So you should initialize 'something' somewhere else as per your needs (for example, inside controller's OnActionExecuting method).
I'm trying to create a permission attribute to configure in each action of my controllers so this custom attribute should take the sessionId from the user.
My code is like that:
public class PermissionChecker: ActionFilterAttribute
{
private int _permissionId { get; set; }
private IUserSelectorService _userService { get; set; }
public PermissionChecker(int permissionId)
{
_permissionId = permissionId;
_userService = new UserSelectorService();
}
public PermissionChecker(int permissionId, IUserSelectorService userService)
{
_permissionId = permissionId;
_userService = userService;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (_userService.HasPermission(_permissionId, /* here I must pass the session["Id"]*/)){
base.OnActionExecuting(actionContext);
return;
}
throw new HttpException(401, "Unauthorized");
}
}
Use this
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(filterContext.HttpContext.Session != null)
{
var id = filterContext.HttpContext.Session["Id"];
}
}
EDIT
Given the fact that you're using MVC 4 and you don't have
public override void OnActionExecuting(ActionExecutingContext filterContext)
Try using
System.Web.HttpContext.Current.Session
if you are trying to access using ActionFilterAttribute then OnActionExecting event it wont give the accessibility of HttpContext with System.Web.Http.
Instead of that If you are trying to access using System.Web.Mvc it will provide you the current session with onActionExecting event with help of ActionExecutingContext class.
I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.
You could write a marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute
{
}
and then in your global action filter test for the presence of this marker on the action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
{
return;
}
// here do whatever you were intending to do
}
}
and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:
[SkipMyGlobalActionFilter]
public ActionResult Index()
{
return View();
}
Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.
You just need to add a boolean property to your attribute and check against it, just before your logic begins:
public class DataAccessAttribute: ActionFilterAttribute
{
public bool Disable { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Disable) return;
// Your original logic for your 95% actions goes here.
}
}
Then at your 5% actions just use it like this:
[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{
return View();
}
In AspNetCore, the accepted answer by #darin-dimitrov can be adapted to work as follows:
First, implement IFilterMetadata on the marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}
Then search the Filters property for this attribute on the ActionExecutingContext:
public class MyGlobalActionFilter : IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
{
return;
}
// etc
}
}
At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.
There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.
See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/
Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.
public class MyFilterProvider : IFilterProvider
{
private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
public void Add(Func<ControllerContext, object> mycondition)
{
filterconditions.Add(mycondition);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return from filtercondition in filterconditions
select filtercondition(controllerContext) into ctrlContext
where ctrlContext!= null
select new Filter(ctrlContext, FilterScope.Global);
}
}
=============================================================================
In Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
MyFilterProvider provider = new MyFilterProvider();
provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
FilterProviders.Providers.Add(provider);
}
protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}
Well, I think I got it working for ASP.NET Core.
Here's the code:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Prepare the audit
_parameters = context.ActionArguments;
await next();
if (IsExcluded(context))
{
return;
}
var routeData = context.RouteData;
var controllerName = (string)routeData.Values["controller"];
var actionName = (string)routeData.Values["action"];
// Log action data
var auditEntry = new AuditEntry
{
ActionName = actionName,
EntityType = controllerName,
EntityID = GetEntityId(),
PerformedAt = DateTime.Now,
PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
};
_auditHandler.DbContext.Audits.Add(auditEntry);
await _auditHandler.DbContext.SaveChangesAsync();
}
private bool IsExcluded(ActionContext context)
{
var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
}
The relevant code is in the 'IsExcluded' method.
You can change your filter code like this:
public class NHibernateActionFilter : ActionFilterAttribute
{
public IEnumerable<string> ActionsToSkip { get; set; }
public NHibernateActionFilter(params string[] actionsToSkip)
{
ActionsToSkip = actionsToSkip;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (null != ActionsToSkip && ActionsToSkip.Any(a =>
String.Compare(a, filterContext.ActionDescriptor.ActionName, true) == 0))
{
return;
}
//here you code
}
}
And use it:
[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]