We use a BaseController to cache basic authentication information before every action executes:
public abstract class BaseController : Controller
{
protected bool IsLoggedIn { get; set; }
protected string Username { get; set; }
...
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var identity = base.User.Identity;
this.IsLoggedIn = identity.IsAuthenticated;
this.Username = identity.Name;
...
}
}
And our child controller has a actions for the main page (Index) and a partial view (GetNavigation):
[Authorize]
public partial class CollaborationController : BaseController
{
[HttpGet]
public virtual ViewResult Index()
{
var viewModel = this.MakeViewModel<FullPageViewModel>();
return this.View(MVC.Collaboration.Views.Index, viewModel);
}
[HttpGet]
public virtual PartialViewResult GetNavigation()
{
var viewModel = NavigationViewModel.Make(this.User);
return this.PartialView(MVC.Collaboration.Views.Navigation, viewModel);
}
}
And the partial view is rendered directly with Html.Action():
#Html.Action(MVC.Collaboration.GetNavigation())
Seems like it should work, but BaseController.OnActionExecuting does not get called. And I can't even call it directly because this.ControllerContext and base.User are both null. I also tried subclassing ActionFilterAttribute, but its OnActionExecuting method doesn't get called, either.
I know this is an old question but here is how I handle this. In my child controller I create the OnActionExecuting method and call the base controller from there.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
At least sort-of answered my own question:
Substituting
#Html.Action("GetNavigation", "Collaboration")
for
#Html.Action(MVC.Collaboration.GetNavigation())
fixes it. MVCContrib's syntax seems to be the culprit, anyone know why? Even better, anyone know a work-around that lets me avoid those nasty, non-refactoring-safe, magic strings?
Related
I need to be able to log all actions that are called from my asp.net mvc application. How and what would be the best way to achieve this? Where I log it to whether it be the console or log file doesn't matter.
You could create your own class which inherits from ActionFilterAttribute and then override the OnActionExecuting method.
Example
public class LogActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.RequestContext.RouteData.Values["Controller"];
var action = filterContext.RequestContext.RouteData.Values["Action"];
//
// Perform logging here
//
base.OnActionExecuting(filterContext);
}
}
public class HomeController : Controller
{
[LogAction]
public ActionResult Index()
{
return View();
}
}
Hope this helps!
Credit HeyMega for their answer. Here's an example of an expanded implementation I arrived at in MVC5.
public class LogActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.RequestContext.RouteData.Values.ContainsKey("Controller") ? filterContext.RequestContext.RouteData.Values["Controller"].ToString() : null;
var action = filterContext.RequestContext.RouteData.Values.ContainsKey("Action") ? filterContext.RequestContext.RouteData.Values["Action"].ToString() : null;
var area = filterContext.RequestContext.RouteData.DataTokens.ContainsKey("Area") ? filterContext.RequestContext.RouteData.DataTokens["Area"].ToString() : null;
var user = filterContext.RequestContext.HttpContext.User.Identity.GetUserId();
Task.Run(() => Generic().AreaActionLog(user, area, controller, action));
base.OnActionExecuting(filterContext);
}
}
I chose to separate the method doing the actual logging into a separate process, if anything goes wrong with the Database interaction, or the DB interaction takes several seconds, the UI is uninterrupted.
You can then decorate the entire controller with [LogAction] attribute like so.
[LogAction]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Contact()
{
return View();
}
}
Or selectively apply the attribute by decorating individual methods:
public class HomeController : Controller
{
[LogAction]
public ActionResult Index_Logs_Things()
{
return View();
}
}
Hope this helps someone.
You could try Audit.NET library with its Audit.MVC and the different data providers to store the logs on files, eventlog, sql, redis, mongo, and much more.
With the MVC extension you just need to decorate your controllers or actions with an attribute:
[Audit]
public class HomeController : Controller
{ ... }
Execute a static configuration to set the output of your logs:
Audit.Core.Configuration.Setup()
.UseFileLogProvider(_ => _
.Directory(#"C:\Logs"));
And it will provide the infrastructure to log the interactions with your MVC application.
I'm using an action filter on some of my controllers, irrelevant what it does, that uses Ninject for dependency injection. I set breakpoints in the controller constructor and the action filter constructor, and I found that for a single request, the controller's constructor gets called once and the action filter's 8 times. The following code produces the effect, virtually the same as that given in this answer.
Attribute:
public class NotificationAttribute : Attribute { }
public class NotificationActionFilter : IActionFilter
{
private IUnitOfWork _unitOfWork;
public NotificationActionFilter(IUnitOfWork uow)
{
_unitOfWork = uow;
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// do stuff
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
Binding:
kernel.BindFilter<NotificationActionFilter>(FilterScope.Controller, 0).WhenControllerHas<NotificationAttribute>();
Controller:
[NotificationAttribute]
public class TestController : Controller
{
public ActionResult Index()
{
return View();
}
}
It's always exactly 8, but I have no idea why. I've really been pulling my hair out over this one, just hoping someone might have dealt with something similar before.
The code:
public class TheFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
}
}
public class NotesController : BaseController
{
[TheFilter]
[HttpPost]
public ActionResult Edit(EditViewModel viewModel)
{
viewModel.Note.Modified = DateTime.Now;
viewModel.Note.ModifiedBy = User.Identity.Name;
var noteTable = StorageHelper.GetTable<Note>(viewModel.PageMeta.DataSourceID);
noteTable.AddOrUpdate(viewModel.Note);
return Home();
}
}
When I debug on return Home() and step through then I bypass the action filter and go straight to the Home() method.
Am I declaring the action filter correctly?
Make sure you're implementing
System.Web.Mvc.ActionFilterAttribute
and not
System.Web.Http.Filters.ActionFilterAttribute
They both have OnActionExecuting and OnActionExecuted Methods, so it can be a little deceiving.
Maybe you don't reach the method directly but call Edit action from other action?
put the filter on the controller and see what happens.
I was also facing the same issue I was just missing the override keyword before the OnExecuting method. It started working after adding override keyword.
Use the Onexecuting not onExecuted
public override void OnActionExecuting(ActionExecutingContext filterContext)
Here's my scenario:
I've successfully created a custom IIdentity that I pass to a GenericPrincipal. When I access that IIdentity in my controller I have to cast the IIdentity in order to use the custom properties. example:
public ActionResult Test()
{
MyCustomIdentity identity = (MyCustomIdentity)User.Identity;
int userID = identity.UserID;
...etc...
}
Since I need to do this casting for nearly every action I would like to wrap this functionality in an ActionFilterAttribute. I can't do it in the controller's constructor because the context isn't initialized yet. My thought would be to have the ActionFilterAttribute populate a private property on the controller that I can use in each action method. example:
public class TestController : Controller
{
private MyCustomIdentity identity;
[CastCustomIdentity]
public ActionResult()
{
int userID = identity.UserID;
...etc...
}
}
Question: Is this possible and how? Is there a better solution? I've racked my brain trying to figure out how to pass public properties that are populated in an attribute to the controller and I can't get it.
All you have to do is access the ActionExecutingContext of an overloaded OnActionExecuting() method and make identity public instead of private so your actionfilter can access it.
public class CastCustomIdentity : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
((TestController) filterContext.Controller).Identity = (MyCustomIdentity)filterContext.HttpContext.User;
base.OnActionExecuting(filterContext);
}
}
This could be even easier by using a custom base controller class that all of your controllers would inherit from:
public class MyCustomController
{
protected MyCustomIdentity Identity { get{ return (MyCustomIdentity)User.Identity; } }
}
and then:
public class TestController : MyCustomController
{
public ActionResult()
{
int userID = Identity.UserId
...etc...
}
}
You could use a custom model binder...
I can't remember why I used this method over the base controller method #jfar mentions (which is also a good option), but it works well for me and I actually kinda like it because my actions are more self describing through their parameters.
MyCustomIdentityModelBinder.cs
public class MyCustomIdentityModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.Model != null)
throw new InvalidOperationException("Cannot update instances");
//If the user isn't logged in, return null
if (!controllerContext.HttpContext.User.Identity.IsAuthenticated)
return null;
return controllerContext.HttpContext.User as MyCustomIdentity;
}
}
Inside your application start event in Global.asax.cs
System.Web.Mvc.ModelBinders.Binders.Add(typeof(MyCustomIdentity), new MyCustomIdentityModelBinder());
Then whenever you have a type of MyCustomIdentity as an action parameter, it'll automatically use the MyCustomIdentityModelBinder.
Eg.
public class TestController : Controller
{
public ActionResult Index(MyCustomIdentity identity)
{
int userID = identity.UserID;
...etc...
}
}
HTHs,
Charles
This must be simple, but I can't seem to figure it out. I am setting an action parameter inside an action filter as follows:
public class MyFilter : ActionFilterAttribute
{
public override void OnActionExecuting (ActionExecutingContext filterContext)
{
filterContext.ActionParameters["MyParam"] = "MyValue";
}
}
I am applying the filter to an entire controller as follows:
[MyFilter]
public class HomeController : Controller
{
public ActionResult Index()
{
// How do I access MyParam here?
return View();
}
}
}
How do I access MyParam inside an action method?
Maybe you could use:
[MyFilter]
public ActionResult Index(string MyParam)
{
//Do something with MyParam
return View();
}
You can decorate whole controller with [MyFilter] or only one action.
I'm hoping this will work:
var myParam = ValueProvider.GetValue("MyParam").RawValue as string;
Since ValueProvider is what modelbinders use to get the values I would think it should be able to get the value set in your filter.