I have certain code with in the same controller class that looks almost the same, such as setting viewbags to populate all the drop down lists, the same code applies in my Post and get Create and Edit action methods.
So I have created a private method at the end of my controller class as follow:-
private void populateViewBags()
{
string controllerName = RouteData.Values["controller"].ToString();
ViewBag.PossibleDataCenters = repository.AllDataCenter().OrderBy(a => a.Name).ToList();
ViewBag.PossibleZones = repository.AllZone().OrderBy(a => a.Name).ToList();
List<string> s = new List<string>();
s.Add(controllerName.ToLower());
ViewBag.Products = repository.GetProducts(s).OrderBy(a => a.COMPONENTNAME).ToList();
ViewBag.Sites = repository.GetSDOrg().OrderBy(a => a.NAME).ToList();
ViewBag.Customers = repository.FindAccountDefinition(null).ToList();
}
And I am calling this method inside my action method. So is it the right way to re-use the code?
Thanks
There are two standard ways you can do this.
1st approach - override OnActionExecuting and/or OnActionExecuted methods of your controller class:
public class HomeController: Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
string controllerName = RouteData.Values["controller"].ToString();
ViewBag.ControllerName = controllerName;
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
string controllerName = RouteData.Values["controller"].ToString();
ViewBag.ControllerName = controllerName;
}
}
You can also make abstract base controller which implements those methods and then inherit concrete controllers from abstract one, so that you don't duplicate code code in each controller.
2nd approach - make custom ActionFilter attribute and decorate each controller that needs to perform additional actions.
public class MyActionFilterAttribute: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string controllerName = filterContext.RouteData.Values["controller"].ToString();
filterContext.Controller.ViewBag.ControllerName = controllerName;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
string controllerName = filterContext.RouteData.Values["controller"].ToString();
filterContext.Controller.ViewBag.ControllerName = controllerName;
}
}
Then just decorate controllers, like:
[MyActionFilter]
public class HomeController: Controller
{
// ....
}
UPDATE: Additional flexibility of filter approach, if you need filter on per-action basis, instead of all actions in the controller, it's also possible:
public class HomeController: Controller
{
[MyActionFilter]
public ActionResult MyAction()
{
//...
}
}
Related
I have the following controller:
[NoCache]
public class AccountController : Controller
{
[Authorize(Roles = "admin,useradmin")]
public ActionResult GetUserEntitlementReport(int? userId = null)
{
var byteArray = GenerateUserEntitlementReportWorkbook(allResults);
return File(byteArray,
System.Net.Mime.MediaTypeNames.Application.Octet,
"UserEntitlementReport.xls");
}
}
public class NoCache : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var response = filterContext.HttpContext.Response;
response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
response.Cache.SetValidUntilExpires(false);
response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
response.Cache.SetCacheability(HttpCacheability.NoCache);
response.Cache.SetNoStore();
base.OnResultExecuting(filterContext);
}
}
As you can see, the controller is decorated with the [NoCache] attribute.
Is there any way to prevent this attribute from being applied to the GetUserEntitlementReport action?
I know I can remove the attribute at the controller level but that is not my favorite solution because the controller contains many other actions and i don't want to have to apply the attribute to each action independently.
You could create a new Attribute which can be used on individual Actions to opt out of the NoCache specified at the controller level.
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public sealed class AllowCache : Attribute { }
Mark any Actions where you want to allow caching with [AllowCache]
Then in the code for your NoCacheAttribute only disable caching if the AllowCache attribute is not present
public class NoCacheAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var actionDescriptor = filterContext.ActionDescriptor;
bool allowCaching = actionDescriptor.IsDefined(typeof(AllowCache), true) ||
actionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowCache), true);
if(!allowCaching)
{
//disable caching.
}
}
}
There is a attribute you can apply on action.
That is [OverrideActionFilters].
[NoCache]
public class AccountController : Controller
{
[Authorize(Roles = "admin,useradmin")]
[OverrideActionFilters]
public ActionResult GetUserEntitlementReport(int? userId = null)
{
var byteArray = GenerateUserEntitlementReportWorkbook(allResults);
return File(byteArray,
System.Net.Mime.MediaTypeNames.Application.Octet,
"UserEntitlementReport.xls");
}
}
This can be accomplished by implementing and registering a custom filter provider. As a starting point, I would derive from FilterAttributeFilterProvider and override GetFilters to remove the NoCache attribute when appropriate.
If I define an Action filter that implements IActionFilter like so:
public class FooAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{ }
public void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new ContentResult
{
Content = "Test",
ContentEncoding = Encoding.UTF8,
ContentType = "text"
};
}
}
And apply it to the following action method:
[Foo]
public ActionResult Index()
{
return View();
}
When I do this the Index action code does not run and neither does the OnResultExecuted method. Is this because I set the Result property in the OnActionExecuting method? Are there any other things that will cause the normal flow to be interrupted?
I think its just the Result property that has this effect..
See here : http://books.google.be/books?id=gzfFQrs_qQAC&lpg=PA442&ots=EXCefpt5-O&dq=iactionfilter%20onactionexecuting&pg=PA442#v=onepage&q=iactionfilter%20onactionexecuting&f=false
User can cancel the action by setting Result to something non-null
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)
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?
I want to create an action filter that will be used by ONLY two controllers in my app... This action filter is supposed to be checked for every action inside the TWO controllers.
Heres my action filter code
public class AllowedToEditEHRFilter : IActionFilter
{
IUnitOfWork unitOfWork;
IRepository<EHR> ehrRepository;
public AllowedToEditEHRFilter(IUnitOfWork dependency)
{
unitOfWork = dependency;
ehrRepository = unitOfWork.EHRs;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
int ehrId;
if (int.TryParse(filterContext.HttpContext.Request.QueryString["ehrId"], out ehrId))
{
EHR ehr = ehrRepository.FindById(ehrId);
if (ehr.UserName != Membership.GetUser().UserName)
filterContext.Result = new ViewResult { ViewName = "InvalidOwner" };
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
Now Im just not sure how can I configure MVC framework so that the action filter gets triggered at the appropriate times.
Im using this as reference but that is applying a GLOBAL actionfilter and it doesnt specify how to limit to just some of your controllers.
Please help.
btw Im using NinjectMVC3
This depends on what the appropriate time is.
See my original blog post
http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/
Or read the other pages of the doc:
https://github.com/ninject/ninject.web.mvc/wiki/Conditional-bindings-for-filters
https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations
(Probably I should link them)
Basically you need to configure a binding for the filter and define some condition:
kernel.BindFilter<AllowedToEditEHRFilter>(FilterScope.Action, 0).When...
e.g.
.WhenActionHas<AllowedToEditEHRAttribute>()
Update:
In your case simply
kernel.BindFilter<AllowedToEditEHRFilter>(FilterScope.Controller, 0).When(
(controllerContext, actionDescriptor) =>
controllerContext.Controller is PhysicalTestsController)
To apply the action filter to only some of your controllers, simply add the attribute to the controllers in question.
[AllowedToEditEHR]
public class YourController : Controller
{
...
For this to work, you should rename your filter to AllowedToEditEHRAttribute, i.e. replace "Filter" by "Attribute". This is a standard naming convention for attributes.
Update: To inject dependencies in the filter, just decorate it's constructor with the [Inject] attribute.
public class AllowedToEditEHRFilter : IActionFilter
{
IUnitOfWork unitOfWork;
IRepository<EHR> ehrRepository;
public AllowedToEditEHRFilter(IUnitOfWork dependency)
{
unitOfWork = dependency;
ehrRepository = unitOfWork.EHRs;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
int ehrId;
if (int.TryParse(filterContext.ActionParameters["ehrId"].ToString(), out ehrId))
{
EHR ehr = ehrRepository.FindById(ehrId);
if (ehr.UserName != Membership.GetUser().UserName)
filterContext.Result = new ViewResult { ViewName = "InvalidOwner" };
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
And the CustomFilterProvider
public class ConfiguredFilterProvider : IFilterProvider
{
private readonly IKernel _kernel;
public ConfiguredFilterProvider(IKernel kernel)
{
_kernel = kernel;
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
List<Filter> list = new List<Filter>();
if (controllerContext.Controller is PhysicalTestsController)
{
list.Add(new Filter(
_kernel.Get(typeof(AllowedToEditEHRFilter)),
FilterScope.Global, order: null
));
}
return list;
}
}
And in Ninject
kernel.Bind<IFilterProvider>()
.To<ConfiguredFilterProvider>();
It might not be the cleanest solution but its working.
You just have to decorate that two controllers with the action filter like this
[AllowedToEditEHRFilter]
public class YourController : Controller
{
...
}
However, I am not sure if it is allowed to have a complex object passed in a constructor of that filter.
Instead of implementing IActionFilter, extend ActionFilterAttribute and then assign the attribute to the two controllers you want to affect:
public class AllowedToEditEHRFilter : ActionFilterAttribute
{
// ...
}
and:
[AllowedToEditEHRFilter]
public class MyController : Controller
{
// ...
}