Custom Filter Attribute does not run on code call - asp.net-mvc

I am using a Custom Action Filter to authorize users to Actions, some of which return an ActionResult while others return a JsonResult.
For every regular action system performs OK. But, now I have another requirement to implement where my design fails.
The View posts to:
[AuthorizationFilter(Entity = AuthEntity.MyItem, Permission = AuthPermission.Write)]
public JsonResult Edit(MyModel model)
where I check the user's authorization for Write operation. This check performs OK. But actually my Action just checks a condition and the redirects the Action to another Action in the Controller as follows:
[AuthorizationFilter(Entity = AuthEntity.MyItem, Permission = AuthPermission.Write)]
public JsonResult Edit(MyModel model)
{
if (model.Id == 0)
{
return Insert(model);
}
else
{
return Update(model);
}
}
Also the Update Action checks for a certain state which requires another authorization:
public JsonResult Update(MyModel model)
{
if (model.StatusId == (int)Shared.Enumerations.Status.Approved)
{
return UpdateRequiresApproval(model);
}
else
{
return UpdateRequiresNonApproval(model);
}
}
[AuthorizationFilter(Entity = AuthEntity.MyItem, Permission = AuthPermission.Approve)]
public JsonResult UpdateRequiresApproval(MyModel model)
The thing is, although I have a custom attribute filter defined on UpdateRequiresApproval action it does not run the filter (possibly) because it is being redirected by another action by means of a code call, but not from the View directly.
How can I make my filter run when code falls to the UpdateRequiresApproval action?
Regards.

Related

OnActionExecuting fires multiple times

I'm not sure if this is the correct way to go about the problem I need to solve... however in an OnActionExecuting action filter that I have created, I set a cookie with various values. One of these values is used to determine whether the user is visiting the website for the very first time. If they are a new visitor then I set the ViewBag with some data so that I can display this within my view.
The problem I have is that in some of my controller actions I perform a RedirectToAction. The result is OnActionExecuting is fired twice, once for the original action and then a second time when it fires the new action.
<HttpGet()>
Function Index(ByVal PageID As String) As ActionResult
Dim wo As WebPage = Nothing
Try
wp = WebPages.GetWebPage(PageID)
Catch sqlex As SqlException
Throw
Catch ex As Exception
Return RedirectToAction("Index", New With {.PageID = "Home"})
End If
End Try
Return View("WebPage", wp)
End Function
This is a typical example. I have a data driven website that gets a webpage from the database based on the PageID specified. If the page cannot be found in the database I redirect the user to the home page.
Is it possible to prevent the double firing in anyway or is there a better way to set a cookie? The action filter is used on multiple controllers.
Had the same issue. Resolved by overriding property AllowMultiple:
public override bool AllowMultiple { get { return false; } }
public override void OnActionExecuting(HttpActionContext actionContext)
{
//your logic here
base.OnActionExecuting(actionContext);
}
You can save some flag value into TempData collection of controller on first executing and if this value presented, skip filter logic:
if (filterContext.Controller.TempData["MyActionFilterAttribute_OnActionExecuting"] == null)
{
filterContext.Controller.TempData["MyActionFilterAttribute_OnActionExecuting"] = true;
}
You could return the actual action instead of redirecting to the new action. That way, you dont cause an http-request, thereby not triggering the onactionexecuting (i believe)
Old question, but I just dealt with this so I thought I'd throw in my answer. After some investigating I disovered this was only happening on endpoints that returned a view (i.e. return View()). The only endpoints that had multiple OnActionExecuting fired were HTML views that were composed of partial views (i.e. return PartialView(...)), so a single request was "executing" multiple times.
I was applying my ActionFilterAttribute globally to all endpoints, which was working correctly on all other endpoints except for the view endpoints I just described. The solution was to create an additional attribute applied conditionally to the partial view endpoints.
// Used specifically to ignore the GlobalFilterAttribute filter on an endpoint
public class IgnoreGlobalFilterAttribute : Attribute { }
public class GlobalFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Does not apply to endpoints decorated with Ignore attribute
if (!filterContext.ActionDescriptor.GetCustomAttributes(typeof(IgnoreGlobalFilterAttribute), false).Any())
{
// ... attribute logic here
}
}
}
And then on my partial view endpoints
[HttpGet]
[AllowAnonymous]
[IgnoreGlobalFilter] //HERE this keeps the attribute from firing again
public ActionResult GetPartialView()
{
// partial view logic
return PartialView();
}

How can you use session variable to determine view?

I have my global.asax setup with the following Session_Start:
protected void Session_Start()
{
HttpContext.Current.Session.Add("sourceCode", "default");
}
On my controller I have the following:
public ActionResult Index(string sourceCode)
{
if (sourceCode != null && sourceCode != "default")
{
Session["sourceCode"] = sourceCode;
return View();
}
else
{
return View();
}
}
I want to be able to display different partial layouts based on this session variable. What is the proper way to do this? Can I load a partial view from the controller or do I need to handle that on the view?
This is a variable that I want to use site wide to determine special pricing and landing page creatives. Do I have to set this same structure up on every single controller or is there a more global way of doing this?
Thanks,
Brian
If you want to show the layout in all the pages, you might want to add the logic in the layout file. There, you will add something like that (assuming razor)
#if(HttpContext.Current.Session["someValue"]){
#*render some partial*#
}else{
#*render some other partial*#
}
By the convention of MVC, controller should decide which view it should open. For this in controller you have code like this:
public ActionResult Index(string sourceCode)
{
if (sourceCode != null && sourceCode != "default")
{
Session["sourceCode"] = sourceCode;
ViewData["PartialView"] = "partialviewname1";
}
else
{
ViewData["PartialView"] = "partialviewname2";
}
return View();
}
and in view you can write code something like this:
<div>
#Html.Partial(Convert.ToString(ViewData["PartialView"]))
</div>
and if you have decide which partial view you have to load on each and every request then you can write above logic in global action filter. Global action filter get executed before any requested action method. To know more about global action filter you can explore this link.
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs

Controller searching for view instead of returning a different view method

I have two controller actions outlined below:
public ViewResult TitleStorylines(int id)
{
var vm = Service.Get(id);
vm.IsEditable = User.HasPermission(SecurityPermissionType.ManageStorylines);
return View(vm);
}
public ViewResult TitleStorylinesCreate(TitleStorylineModel model)
{
var created = Service.Create(model);
return TitleStorylines(created.TitleId);
}
I only have one view in my project, called TitleStorylines, which the first controller action handles fine. But when I call the second method, it gives me an error saying that it can't find the view called TitleStorylinesCreate even though I'm explicitly calling the previous method. What gives?
Did you try ?
return View("TitleStorylines",created.TitleId);
EDIT: Based on your update : I guess you are posting your form back the TitleStorylinesCreate. So probably after saving, dont you want to redirect the user back to the Get action of same ?
[HttpPost]
public ViewResult TitleStorylinesCreate(TitleStorylineModel model)
{
var created = Service.Create(model);
return RedirectToAction("TitleStorylines",new { #id=created.TitleId});
}
In the above example we are doing the PRG pattern. Post -> Redirect -> Get
After saving, we are redirecting them back to the first method. It will be a HTTP GET method.
public ActionResult TitleStorylinesCreate(TitleStorylineModel model)
{
var created = Service.Create(model);
return RedirectToAction("TitleStorylines",new { #id=created.TitleId});
}

Best approach to don't request same info over and over

On my controller I have it inherit a MainController and there I override the Initialize and the OnActionExecuting.
Here I see what is the URL and by that I can check what Client is it, but I learned that for every Method called, this is fired up again and again, even a simple redirectToAction will fire the Initialization of the same controller.
Is there a better technique to avoid this repetition of database call? I'm using Entity Framework, so it will take no time to call the DB as it has the result in cache already, but ... just to know if there is a better technique now in MVC3 rather that host the variables in a Session Variable
sample code
public class MyController : MainController
{
public ActionResult Index()
{
return View();
}
}
public class MainController : Controller
{
public OS_Clients currentClient { get; set; }
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// get URL Info
string url = requestContext.HttpContext.Request.Url.AbsoluteUri;
string action = requestContext.RouteData.GetRequiredString("action");
string controller = requestContext.RouteData.GetRequiredString("controller");
object _clientUrl = requestContext.RouteData.Values["cliurl"];
if (_clientUrl != null && _clientUrl.ToString() != "none")
{
// Fill up variables
this.currrentClient = db.FindClientById(_clientUrl.ToString());
}
base.Initialize(requestContext);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// based on client and other variables, redirect to Disable or Login Actions
// ... more code here like:
// filterContext.Result = RedirectToAction("Login", "My");
base.OnActionExecuting(filterContext);
}
}
is it still best to do as:
public OS_Clients currentClient {
get {
OS_Clients _currentClient = null;
if (Session["CurrentClient"] != null)
_currentClient = (OS_Clients)Session["CurrentClient"];
return _currentClient;
}
set {
Session["CurrentClient"] = value;
}
}
It seems that you dealing with application security in that case I would suggest to create Authorization filter, which comes much early into the action. You can put your permission checking code over there and the framework will automatically redirect the user to login page if the permission does not meet AuthorizeCore.
Next, if the user has permission you can use the HttpContext.Items as a request level cache. And then you can create another ActionFilter and in action executing or you can use the base controller to get the user from the Httpcontext.items and assign it to controller property.
If you are using asp.net mvc 3 then you can use the GlobalFilters to register the above mentioned filters instead of decorating each controller.
Hope that helps.
In your base controller, you need to cache the result of the first call in a Session variable.
This makes sure the back-end (DB) is not called unnecessarily, and that the data is bound to the user's Session instead of shared across users, as would be the case with the Application Cache.

ASP.NET MVC - Reusing Action Behaviors

This question pertains primarily to good design.
Suppose I have a controller action like DeletePage that can be invoked in two separate views of the same controller. Assuming the delete logic is not contained in the action itself, but rather some conditional checks and the like that call the correct business logic, it doesn't make sense to duplicate the structure of the delete action when I can instead have a private method that returns an ActionResult which I call in both actions which can cause a delete. My question is where is the best place to place a reusable action method like this? Right now I'm just marking them private and sticking them in a region of the controller class, but perhaps an sealed inner class would make more sense for such a method- or somewhere else entirely.
Thoughts?
public ActionResult EditPage(int id, FormCollection formCollection)
{
var page = _pagesRepository.GetPage(id);
if (page == null)
return View("NotFound");
if (page.IsProtected)
return View("IllegalOperation");
if (formCollection["btnSave"] != null)
{
//...
}
else if (formCollection["btnDelete"] != null)
{
return DeletePage(page);
}
return RedirectToAction("Index");
}
public ActionResult DeletePage(int id)
{
var page = _pagesRepository.GetPage(id);
if (page == null)
return View("NotFound");
return DeletePage(page);
}
// Reusable Action
private RedirectToRouteResult DeletePage(Page page)
{
if(page != null && !page.IsProtected)
{
_pagesRepository.Delete(page);
_pagesRepository.Save();
FlashMessage(string.Format(PageForms.PageDeleted, page.Name), MessageType.Success);
return RedirectToAction("Index");
}
return RedirectToAction("Index");
}
I don't see why you need to make your reusable method an action method. Why not just a private method that returns void/bool/etc indicating the result of the save, and let your public action method return the RedirectToAction()? Effectively it's the same result, but I think it's a clearer approach.
public ActionResult DeletePage(int id)
{
var page = _pagesRepository.GetPage(id);
if (page == null)
return View("NotFound");
DeletePage(page);
return RedirectToAction("Index");
}
//reusable method
private void DeletePage(Page page)
{
//your same validation/save logic here
}
In the future you might consider moving this private DeletePage method into a separate service class that performs the validation and/or saving. Returning an an ActionResult would definitely not make sense in that case, so I think this example would be a more appropriate approach for your scenario.
In my opinion, your reusable code is an Action because it is returning an ActionResult. So your use is fine. The DeletePage(Page page) could potentially remain public.
I look forward to other opinions.
Personally I agree with Kurt. The concept of Deleting an unprotected page should be decoupled from what action the controller should perform. Secondly it's confusing from the code what should happen when the page is protected. In one action it redirects to the index, in the second it redirects to the "IllegalOperation" view. Personally I'd do something a little like...
public ActionResult DeletePage(int id) {
var page = _pagesRepository.GetPage(id);
if (!PageIsValidForDeletion(page)) {
string invalidPageView = FindViewForInvalidPage(page);
return View(invalidPageView);
}
DeletePage(page);
return RedirectToAction("Index");
}

Resources