How to get the ActionDescription UniqueId from within OnResultExecuted - asp.net-mvc

In ASP.NET MVC, does anyone know a trick to access the ActionDescriptor.UniqueId from within OnResultExecuted? I need to pass information from OnActionExecuting to OnResultExecuted in a way that will work if multiple actions are executed during the one HttpRequest.
For example:
private Dictionary<string,Foo> _foos
{
get { return HttpContext.Current.Items["foos"] as Dictionary<string,Foo>; }
set { HttpContext.Current.Items["foos"] = value; }
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var foos = _foos;
foos[context.ActionDescriptor.UniqueId] = new Foo();
_foos = foos;
}
public override void OnResultExecuted(ResultExecutedContext context)
{
var actionUniqueId = ????
var foo = _foos[actionUniqueId]
}

You can custom the FilterAttributeFilterProvider and ActionFilterAttribute to implement it.
First you can create a filter that inherit the ActionFilterAttribute and contains ActionDescriptor property:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public ActionDescriptor ActionDescriptor { get; set; }
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var actionUniqueId = ActionDescriptor.UniqueId;
//code..
}
}
Then you need create a filter provider inherit the FilterAttributeFilterProvider and override the GetFilters method:
public class MyFilterProvider : FilterAttributeFilterProvider
{
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (controllerContext.Controller != null)
{
foreach (FilterAttribute attr in GetControllerAttributes(controllerContext, actionDescriptor))
{
var myAttr = attr as MyActionFilterAttribute;
if (myAttr != null)
{
myAttr.ActionDescriptor = actionDescriptor;
}
yield return new Filter(attr, FilterScope.Controller, order: null);
}
foreach (FilterAttribute attr in GetActionAttributes(controllerContext, actionDescriptor))
{
var myAttr = attr as MyActionFilterAttribute;
if (myAttr != null)
{
myAttr.ActionDescriptor = actionDescriptor;
}
yield return new Filter(attr, FilterScope.Action, order: null);
}
}
}
}
You can see at the GetFilters method, We set the ActionDescriptor property if the filter type is MyActionFilterAttribute.
Finally, At Global.asax, you need use MyFilterProvider instance to replace the FilterAttributeFilterProvider instance in Providers collection:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
//replace the FilterAttributeFilterProvider in providers collection
for (int i = 0; i < FilterProviders.Providers.Count; i++)
{
if (FilterProviders.Providers[i] is FilterAttributeFilterProvider)
{
FilterProviders.Providers[i] = new MyFilterProvider();
break;
}
}
//other global init code...
}
}

Related

Pass value from ActionFilterAttribute to controller

I have the following base controller with a string variable
public abstract class BaseController:Controller
{
string encryptedSessionGuid;
}
All other controller derives from base controller and ActionMethod has a custom ActionFilterAttribute CheckQueryString-
public class SampleController : BaseController
{
[CheckQueryString(new string[] {"sid"})]
public ActionResult SampleMethod()
{
return View();
}
}
Here is my custom attribute. It sends query string value to view. But I would like to send it base controller variable encryptedSessionGuid also.
public class CheckQueryString : ActionFilterAttribute
{
string[] keys;
public CheckQueryString(string[] Keys) { keys = Keys; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
foreach (var key in keys)
{
if (ctx.Request.QueryString[key] == null)
{
filterContext.Result = new RedirectResult(BulkSmsApplication.GlobalConfig.BaseUrl);
return;
}
else
{
string value = ctx.Request.QueryString[key];
if (string.IsNullOrEmpty(value))
{
filterContext.Result = new RedirectResult(BulkSmsApplication.GlobalConfig.BaseUrl);
return;
}
else
{
var viewBag = filterContext.Controller.ViewData;
viewBag[key] = value;
}
}
}
base.OnActionExecuting(filterContext);
}
}
How can it be done?

SimpleInjector Web API Attribute Filter Won't Return Response

I have followed this tutorial in order to inject dependencies into a custom filter via an method attribute.
https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=98
Everything looks like its working, it hits the filter and calls the OnActionExecuting() method.
The problem I'm having is that when the user isn't authenticated, I want to return a custom response via context.Response, but it just carries on into the action result as-if the filter wasn't there.
Below are the various pieces of code taken from the above URL, plus my custom filter.
[Route("api/user/create")]
[AuthoriseRequest("Can_Create_User")]
[System.Web.Http.HttpPost]
.
public class AuthoriseRequest : Attribute
{
public string PermissionName { get; set; }
public AuthoriseRequest(string permissionName)
{
PermissionName = permissionName;
}
}
.
public class AuthoriseRequestActionFilter : IActionFilter<AuthoriseRequest>
{ ... }
.
public void OnActionExecuting(AuthoriseRequest request, HttpActionContext context)
{
var createUserRequest = (CreateUserRequest)context.ActionArguments["createUserRequest"];
var validator = _requestValidator.Validate(createUserRequest);
if (validator.IsValid())
{
var isAuthenticated =
_authenticationManager.AuthenticateRequest(...);
if (!isAuthenticated)
{
//THIS DOESN'T SET THE RESPONSE
var unauthorisedRequestResponse = new APIResponse<string>(null, false, validator.ToString());
context.Response = context.Request.CreateResponse(
HttpStatusCode.OK,
unauthorisedRequestResponse);
}
}
}
.
public sealed class ActionFilterDispatcher : IActionFilter
{
private readonly Func<Type, IEnumerable> container;
public ActionFilterDispatcher(Func<Type, IEnumerable> container)
{
this.container = container;
}
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext context,
CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
var descriptor = context.ActionDescriptor;
var attributes = descriptor.ControllerDescriptor.GetCustomAttributes<Attribute>(true)
.Concat(descriptor.GetCustomAttributes<Attribute>(true));
foreach (var attribute in attributes)
{
Type filterType = typeof(IActionFilter<>).MakeGenericType(attribute.GetType());
IEnumerable filters = this.container.Invoke(filterType);
foreach (dynamic actionFilter in filters)
{
actionFilter.OnActionExecuting((dynamic)attribute, context);
}
}
return continuation();
}
public bool AllowMultiple { get { return true; } }
}
.
public static void RegisterGlobalFilters(GlobalFilterCollection filters, Container container)
{
GlobalConfiguration.Configuration.Filters.Add(new ActionFilterDispatcher(container.GetAllInstances));
container.RegisterCollection(typeof(IActionFilter<>), typeof(IActionFilter<>).Assembly);
filters.Add(new HandleErrorAttribute());
}
Thanks.

MvxExpandableListAdapter SetItemsSource is not called

I have this code snippet to using MvxExpandableListView
public class ExpandView : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.ExpandView);
MvxExpandableListView list = FindViewById<MvxExpandableListView>(Resource.Id.TheExpandView);
list.SetAdapter(new MyCustomAdaptor(this, (IMvxAndroidBindingContext)BindingContext));
}
private class MyCustomAdaptor : MvxExpandableListAdapter
{
private IList _itemsSource;
public MyCustomAdaptor(Context context, IMvxAndroidBindingContext bindingContext)
: base(context, bindingContext)
{
}
protected override void SetItemsSource(IEnumerable value)
{
Mvx.Trace("Setting itemssource");
if (_itemsSource == value)
return;
var existingObservable = _itemsSource as INotifyCollectionChanged;
if (existingObservable != null)
existingObservable.CollectionChanged -= OnItemsSourceCollectionChanged;
_itemsSource = value as IList;
var newObservable = _itemsSource as INotifyCollectionChanged;
if (newObservable != null)
newObservable.CollectionChanged += OnItemsSourceCollectionChanged;
if (value != null)
{
}
else
base.SetItemsSource(null);
}
}
}
When the view is loaded the override method SetItemsSource does not get called , but when I try to navigate away from the page SetItemsSource gets called with a null value.
You need to set the property ItemsSource.Looking at the source of MvxAdapter:
[MvxSetToNullAfterBinding]
public virtual IEnumerable ItemsSource
{
get { return _itemsSource; }
set { SetItemsSource(value); }
}
The setter of ItemsSource calls the method SetItemsSource.
MvxExpandableListAdapter inherits MvxAdapter.
Source:
https://github.com/MvvmCross/MvvmCross/blob/4.0/MvvmCross/Binding/Droid/Views/MvxExpandableListAdapter.cs
https://github.com/MvvmCross/MvvmCross/blob/4.0/MvvmCross/Binding/Droid/Views/MvxAdapter.cs

How to disable a global filter in ASP.Net MVC selectively

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"})]

MVC Action filter across all the application

I am trying to create authorization action filter the will fire on each request to check if the user is allow to do some stuff.
So, i created the following classes/interfaces:
public interface IGlobalAuthorizationFilter : IGlobalFilter, IAuthorizationFilter
{
}
public interface IGlobalFilter
{
bool ShouldBeInvoked(ControllerContext controllerContext);
}
public class GlobalFilterActionInvoker : ControllerActionInvoker
{
protected FilterInfo GlobalFilters;
public GlobalFilterActionInvoker()
{
GlobalFilters = new FilterInfo();
}
public GlobalFilterActionInvoker(FilterInfo filters)
{
GlobalFilters = filters;
}
public GlobalFilterActionInvoker(IEnumerable<IGlobalFilter> filters)
: this(new FilterInfo())
{
foreach (IGlobalFilter filter in filters)
RegisterGlobalFilter(filter);
}
public FilterInfo Filters
{
get { return GlobalFilters; }
}
public void RegisterGlobalFilter(IGlobalFilter filter)
{
if (filter is IGlobalAuthorizationFilter)
GlobalFilters.AuthorizationFilters.Add((IGlobalAuthorizationFilter) filter);
}
protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
FilterInfo definedFilters = base.GetFilters(controllerContext, actionDescriptor);
foreach (IAuthorizationFilter filter in Filters.AuthorizationFilters)
{
var globalFilter = filter as IGlobalFilter;
if (globalFilter == null ||
(globalFilter.ShouldBeInvoked(controllerContext)))
{
definedFilters.AuthorizationFilters.Add(filter);
}
}
return definedFilters;
}
}
public class ApplicationControllerFactory : DefaultControllerFactory
{
private readonly IUnityContainer _container;
public ApplicationControllerFactory(IUnityContainer container)
{
this._container = container;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if ( controllerType == null )
{
throw new HttpException(404, "The file " + requestContext.HttpContext.Request.FilePath + " not found.");
}
IController icontroller = _container.Resolve(controllerType) as IController;
if (typeof(Controller).IsAssignableFrom(controllerType))
{
Controller controller = icontroller as Controller;
if (controller != null)
controller.ActionInvoker = _container.Resolve<IActionInvoker>();
return icontroller;
}
return icontroller;
}
}
And the class with the function that need to be called, but its not..
public class AuthenticationActionFilter : IGlobalAuthorizationFilter
{
public bool ShouldBeInvoked(System.Web.Mvc.ControllerContext controllerContext)
{
return true;
}
public void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
}
}
And, the Global.asax registration stuff:
IUnityContainer unityContainer = new UnityContainer();
unityContainer.RegisterType<IUserService, UserManager>();
unityContainer.RegisterType<IAppSettings, AppSettingsHelper>();
unityContainer.RegisterType<ICheckAccessHelper, CheckAccessHelper>().Configure<InjectedMembers>().ConfigureInjectionFor<CheckAccessHelper>(new InjectionConstructor());
unityContainer.RegisterType<IActionInvoker, GlobalFilterActionInvoker>().Configure<InjectedMembers>().ConfigureInjectionFor<GlobalFilterActionInvoker>(new InjectionConstructor());
unityContainer.RegisterType<IGlobalAuthorizationFilter, AuthenticationActionFilter>();
IControllerFactory unityControllerFactory = new ApplicationControllerFactory(unityContainer);
ControllerBuilder.Current.SetControllerFactory(unityControllerFactory);
So, as i said, my problem is the function: "ShouldBeInvoked" never called.
Any help?
I believe this filter would only be invoked on actions decorated with [Authorize] do you have that on the methods you want this filter to run?

Resources