How to set navigation variables in MVC Masterpages - asp.net

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
{

Related

Asp.Net MVC 5 custom action routing under an area

i am currently trying to generate this url "/Cloud/Hosting/RoaringPenguin/Manage/Exclusions".
Here is the area registration
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Hosting_default",
"Cloud/Hosting/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
here is the controller
public class RoaringPenguinController : PortalControllerBase
{
public ActionResult Exclusions()
{
return View("Exclusions");
}
}
i have tried added a route onto the action itself like so
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
I have also tried adding some attributes to the controller itself
[RouteArea("Hosting")]
[RoutePrefix("RoaringPenguin")]
public class RoaringPenguinController : PortalControllerBase
but that doesn't seem to work either. If i leave all the attributes off then the final url i get is "/Cloud/Hosting/RoaringPenguin/Exclusions".
Does anyone know how i can get the "Manage" in the url as well?
Just to confirm i have the following set in my RegisterRoutes method under the RouteConfig class
routes.MapMvcAttributeRoutes();
Any help is appreciated. Thanks
Your default area route doesn't allow for the "Manage/Exclusions" part on the end. If you made the URL just /Cloud/Hosting/RoaringPenguin/Exclusions (minus the Manage part of the path) it would work fine.
If you need the route to be exactly that, then attribute routing is your best bet. However, your mentioned attempts at that are all missing something or another. Your controller should be decorated with both RouteArea and RoutePrefix to compose the first part of the path:
[RouteArea("Hosting", AreaPrefix = "Cloud/Hosting")]
[RoutePrefix("RoaringPenguin")]
public class RoaringPenguinController : Controller
However, it's typical to actually implement a base controller when dealing with areas, so that you can specify RouteArea in just one place:
[RouteArea("Hosting", AreaPrefix = "Cloud/Hosting")]
public class HostingBaseController : Controller
[RoutePrefix("RoaringPenguin")]
public class RoaringPenguinController : HostingBaseController
Then, on your action:
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
As you had.
Try with this code
[RouteArea("AreaName", AreaPrefix = "Cloud/Hosting")]
[RoutePrefix("RoaringPenguin")]
public class SampleController : Controller
{
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
{
return View("Exclusions");
}
}
or
[RoutePrefix("Cloud/Hosting/RoaringPenguin")]
public class RoaringPenguinController : PortalControllerBase
{
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
{
return View("Exclusions");
}
}
This will be the first line
routes.MapMvcAttributeRoutes();
After that only you have to write this line
AreaRegistration.RegistrationAllAreas();

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();
}
}

ASP.NET MVC: How to override parent Controller method

Let's say I have many Controllers, and a lot of this Controllers share common Action. What is the best way to share these common Actions to eliminate duplicating the code?
One way I know is to refactor these common Actions into parent abstract Controller, like so..
public abstract class BaseController : Controller {
//handles common help page for all controllers
public ActionResult Help(string helpTopic) {
..open help page..
return View(page);
}
}
//now Controller1 and Controller2 has the help page for free!
public class Controller1: BaseController {
}
public class Controller2: BaseController {
}
But if I attempt to override the Help action within any of the sub controllers like so..
//customized help page for Controller1
public class Controller1: BaseController {
public new ActionResult Help(string helpTopic) {
.. my own customized help page..
return View(page);
}
}
I will get error The current request for action 'Help' on controller type 'Controller1' is ambiguous between....
So how do I override parent controller method?
What I did to successfully override a parent controller's method is to mark the method as virtual and use the override keyword in the child controller method, like so...
public abstract class BaseController : Controller {
//handles common help page for all controllers
public virtual ActionResult Help(string helpTopic) {
..open help page..
return View(page);
}
}
//Controller1 has a customized help page
public class Controller1: BaseController {
public override ActionResult Help(string helpTopic) {
.. my own customized help page..
return View(page);
}
}
//Controller2 has help page for free!
public class Controller2: BaseController {
}

ASP.NET ActionFilters and inheritance

All my controllers inherit from a BaseController that has an ActionFilter attribute:
[AnalyticsData]
public class BaseController : Controller {}
public class AccountController : BaseController {}
Some of my Actions in my controllers reuse the AnalyticsData ActionFilter:
public class AccountController : BaseController
{
[AnalyticsData(Page="AccountProfile")]
public ActionResult Profile()
{
// return View
}
}
I notice that the AnalyticsData ActionFilter only runs once. This is a good thing and I only want it to run once, but I'm wondering how that happens. If I set my breakpoint inside the OnActionExecuting:
public class AnalyticsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// break point set here
}
}
...it only gets hit once when AccountController serves it Profile view.
How do ActionFilters and/or Attributes work that [AnalyticsData(Page="AccountProfile")] on the Action overrides/replaces [AnalyticsData] on BaseController?
The short answer is that the ASP.NET MVC framework code that retrievs the list of filters for each action removes duplicates (action filters of the same type) in such a way that it prefers actionfilters defined on the action method over ones defined on the controller (or its base class). In MVC 2 this logic is performed in a few internal methods in the ActionDescriptor class

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