Is there any undesirable effect doing ModelState.Clear() before action executing? - asp.net

If we are using ajax call to return partial view, then response from the action method may return cached output from partial view. The issue has been discussed on several SO post here, here, here
To resolve issue i have to do ModelState.Clear() in action method.
I have several action methods that returns partial view using Ajax call. So i thought instead of doing ModelState.Clear() in each method i can create custom ActionFilterAttribute like below
public class NoCacheAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.Controller.ViewData.ModelState.Clear();
base.OnResultExecuting(filterContext);
}
}
and then register it with global filters
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new NoCacheAttribute());
}
This seems to be working fine.
However I wanted to know if there is there any undesirable effect doing ModelState.Clear() before action executing? If not, then why that is not a default behavior in asp.net mvc

Related

ASP.NET MVC Action calls another action but doesn't trigger OnActionExecuting for the second one

I have a custom ActionFilterAttributes in my solution, and using OnActionExecuting to do some logs.
However, the problem occurs because I want to trace the entire path taken during an action, and if by chance an action makes a call to another for some reason OnActionExecuting only triggers on the first call.
This is my ActionFilterAttribute
public class LogFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Log(filterContext.ActionDescriptor.ActionName)
}
}
This is an example of a action that calls another.
[LogFilterAttribute]
public class CarouselController
{
public ActionResult GetCarousel()
{
return ShowDialog()
}
public ActionResult ShowDialog()
{
//some code
}
}
In my understanding, after the GetCarousel action is called, the OnActionExecuting should be triggered twice, once with the value of GetCarousel and another with the value of showDialog. However this is not the resulting behavior because it is only triggered for the first action.
What will be the problem? how can i get the expected result?
Thanks in advance

Set ViewBag property in the constructor of a ASP.NET MVC Core controller

My theme has some sort of breadcrumb. The controller is always the category. To avoid repeat myself, I want to set it in the constructor of the controller for all actions like this:
class MyController:Controller{
public MyController() {
ViewBag.BreadcrumbCategory = "MyCategory";
}
}
When I access ViewBag.BreadcrumbCategory in the layout-view, its null. In a Action it works:
class MyController:Controller{
public IActionResult DoSomething() {
ViewBag.BreadcrumbCategory = "MyCategory";
}
}
I'm wondering that setting a ViewBag property is not possible in a constructor? It would be annoying and no good practice to have a function called on every action which do this work. In another question using the constructor was an accepted answear, but as I said this doesn't work, at least for ASP.NET Core.
There is an GitHub issue about it and it's stated that this is by design. The answer you linked is about ASP.NET MVC3, the old legacy ASP.NET stack.
ASP.NET Core is written from scratch and uses different concepts, designed for both portability (multiple platforms) as well as for performance and modern practices like built-in support for Dependency Injection.
The last one makes it impossible to set ViewBag in the constructor, because certain properties of the Constructor base class must be injected via Property Injection as you may have noticed that you don't have to pass these dependencies in your derived controllers.
This means, when the Controller's constructor is called, the properties for HttpContext, ControllerContext etc. are not set. They are only set after the constructor is called and there is a valid instance/reference to this object.
And as pointed in the GitHub issues, it won't be fixed because this is by design.
As you can see here, ViewBag has a dependency on ViewData and ViewData is populated after the controller is initialized. If you call ViewBag.Something = "something", then you it will create a new instance of the DynamicViewData class, which will be replaced by the one after the constructor gets initialized.
As #SLaks pointed out, you can use an action filter which you configure per controller.
The following example assumes that you always derive your controllers from Controller base class.
public class BreadCrumbAttribute : IActionFilter
{
private readonly string _name;
public BreadCrumbAttribute(string name)
{
_name = name;
}
public void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
var controller = context.Controller as Controller;
if (controller != null)
{
controller.ViewBag.BreadcrumbCategory = _name;
}
}
}
Now you should be able to decorate your controller with it.
[BreadCrumb("MyCategory")]
class MyController:Controller
{
}
I have the same issue and solve it overriding the OnActionExecuted method of the controller:
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
ViewBag.Module = "Production";
}
Here is a better way to do this for .NET Core 3.x, use the ResultFilterAttribute:
Create your own custom filter attribute that inherits from ResultFilterAttribute as shown below:
public class PopulateViewBagAttribute : ResultFilterAttribute
{
public PopulateViewBagAttribute()
{
}
public override void OnResultExecuting(ResultExecutingContext context)
{
// context.HttpContext.Response.Headers.Add(_name, new string[] { _value });
(context.Controller as MyController).SetViewBagItems();
base.OnResultExecuting(context);
}
}
You'll need to implement the method SetViewBagItems to populate your ViewBag
public void SetViewBagItems()
{
ViewBag.Orders = Orders;
}
Then Decorate your Controller class with the new attribute:
[PopulateViewBag]
public class ShippingManifestController : Controller
That's all there is to it! If you are populating ViewBags all over the place from your constructor, then you may consider creating a controller base class with the abstract method SetViewBagItems. Then you only need one ResultFilterAttribute class to do all the work.

Removing "X-Frame-Options" header for a specific controller only

I am trying to remove the "X-Frame-Options" header for only a specific controller's actions using:
protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.HttpContext.Response.Headers.Remove("X-Frame-Options");
base.OnResultExecuting(filterContext);
}
However, that doesn't seem to work at all. The only way I can get it to work at all on my site is to add this code to the global.asax below. I am pretty sure I am missing the correct step in the ASP.NET MVC / IIS pipeline that allows me to overwrite the IIS setting of that header. Is this possible?
protected void Application_EndRequest()
{
Response.Headers.Remove("X-Frame-Options");
}
As for why I want to do this, I am building a widget that user's will be able to use on their personal sites through the use of an iframe, but allow them to post back information to our site. I realize there are security implications to turning this header off, and while I welcome any suggestions on how to mitigate those risks, I just want to know if what I am asking is possible.
OnResultExecuting happens too early in the MVC lifecycle. The header has not been set yet.
What you need is the OnResultExecuted method which is run after the View is rendered.
Here's how you write a filter class for what you are looking for:
using System.Web.Mvc;
namespace Test.Filters
{
public class RemoveXFrameOptionsAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Headers.Remove("X-Frame-Options");
base.OnResultExecuted(filterContext);
}
}
}
Then to use it, decorate whatever Controller or Action you want this filter applied.
[RemoveXFrameOptions]
public class TestController : Controller
{
public ActionResult Index()
{
return View();
}
}
or
public class TestController : Controller
{
[RemoveXFrameOptions]
public ActionResult Index()
{
return View();
}
}

How to set navigation variables in MVC Masterpages

I need to have some navigation options, that require keys that are specific to the current user, that reside in a masterpage. I need some advice on best practise.
In have the following links in a left nav in a masterpage
http://www.example.com/manageShop/123
http://www.example.com/addProductToShop/123
http://www.example.com/addStaffToShop/123
Where '123' is the shop id that the current user is the manager of. I need some way of passing this to the masterpage
Currently I'm going something to this effect:
<li><%= Html.ActionLink<ShopController>(x => x.ManageShop((int)Session["ShopKey"]), "Manage")%></li>
I thought this was a good idea as I only have to set the ShopKey once in the session and its done, the down side is that iv noticed that the session gets mixed if you have the site open is two tabs.
Alternatively I tried this:
<li><%= Html.ActionLink<ShopController>(x => x.ManageShop((int)ViewData["ShopKey"]), "Manage")%></li>
But this means you have to keep setting the ViewData in every action in every controller. Which is awful.
EDIT: I have had alook at filters like eu-ge-ne suggested below, but I dont this really solves my problem as I still have the issue of setting the ShopKey everywhere?
What is the solution?
You can create custom filter for this:
public class UserKeyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData["UserKey"] = UserKey;
}
}
and use it on your controller or controller actions
[UserKey]
public class YourController : Controller
{
// or
public class YourController : Controller
{
[UserKey]
public ActionResult Index()
{
or use Controller.OnActionExecuting() (or even create base controller for this as Arnis L. said):
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData["UserKey"] = UserKey;
}
}
// and then derive your controllers from BaseController
public class YourController : BaseController
{

ASP MVC Authorize all actions except a few

I have a controller and I would like to require Authorization for all actions by default except a couple. So in the example below all actions should require authentication except the Index. I don't want to decorate every action with the Authorize, I just want to override the default authorization in certain circumstances probably with a custom filter such as NotAuthorize.
[Authorize]
public class HomeController : BaseController
{
[NotAuthorize]
public ActionResult Index()
{
// This one wont
return View();
}
public ActionResult About()
{
// This action will require authorization
return View();
}
}
Ok, this is what I did. If there is a better way let me know.
public class NotAuthorizeAttribute : FilterAttribute
{
// Does nothing, just used for decoration
}
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Check if this action has NotAuthorizeAttribute
object[] attributes = filterContext.ActionDescriptor.GetCustomAttributes(true);
if (attributes.Any(a => a is NotAuthorizeAttribute)) return;
// Must login
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
What about [AllowAnonymous] ??
MVC4 has a new attribute exactly meant for this [AllowAnonymous] (as pointed out by Enrico)
[AllowAnonymous]
public ActionResult Register()
Read all about it here:
http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx
Here's what I would do, similar to Craig's answer with a couple of changes:
1) Create an ordinary attribute deriving from System.Attribute (no need to derive from FilterAttribute since you aren't going to be using anything FilterAttribute provides).
Maybe create a class hierarchy of attributes so you can test based on the hierarchy, e.g.
Attribute
AuthorizationAttribute
AuthorizationNotRequiredAttribute
AuthorizationAdminUserRequiredAttribute
AuthorizationSuperUserRequiredAttribute
2) In your BaseController override the OnAuthorization method rather than the OnActionExecuting method:
protected override void OnAuthorization(AuthorizationContext filterContext)
{
var authorizationAttributes = filterContext.ActionDescriptor.GetCustomAttributes(true).OfType<AuthorizationAttribute>();
bool accountRequired = !authorizationAttributes.Any(aa => aa is AuthorizationNotRequiredAttribute);
I like the approach of being secure by default: even if you forget to put an attribute on the Action it will at least require a user to be logged in.
Use a custom filter as described in Securing your ASP.NET MVC 3 Application.
Mark the controller with [Authorize]
[Authorize]
public class YourController : ApiController
Mark actions you want public with :
[AllowAnonymous]
Little late to the party, but I ended up creating a Controller-level auth attribute and an Action-level auth attribute and just skipping over the Controller auth if the Action had its own Auth attribute. See code here:
https://gist.github.com/948822

Resources