I'm new to MVC and trying to get my head around Controllers. Please could you comment on the following scenario?:
It is a business requirement of our application that users must accept a "privacy policy" screen when using the application. I do this by setting a cookie when the user clicks a link in the initial Home controller index view.
But, I need to make sure that any entry point in our application (i.e not just from the index page but anywhere in the application) checks for the existence of the cookie. It makes sense to me that the value could be checked by each controller, but I don't want to duplicate the same "If cookie exists do something" code on each controller. So is there a next level up I can write my method?
Hoping this makes sense!
Use Custom ActionFilterAttribute.
Create a class CheckCookie:
public class CheckCookie : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!CookieExists)// Check the Cookie exists
{
//Redirect if the cookie does not exists
filterContext.Result = new RedirectResult("~/Redirect/NoCookie");
return;
}
}
}
Controller:
[CheckCookie]//Check the Cookie exists
public ActionResult Index()
{
return View();
}
Write a custom base controller (that itself inherits the standard ASP.NET MVC controller) that contains your cookie verification code, then have your other controllers derive from that.
For instance, to make sure all my actions by default require a user to be authenticated, I use something similar to this as a base controller:
[Authorize]
public class CoreController : Controller
{
/* ... */
}
Then my home controller looks like:
public class HomeController : CoreController
{
/* ... */
}
Related
Situation:
I have a controller (with its associated views), that after it was developed, it was decided that it will be excluded from the current phase, and instead, it will be included in some phase in the future.
I cannot simply exclude or delete the controller and the views from the solution because of what I mentioned that will be using the controller in the future. Something that came to my mind was to force each action on the controller to redirect to the main page if they are accessed, but I think this is not too "elegant".
Question:
What other method can I use to block the execution of an action in a controller when they are accessed through the URL?
Keeping unused code in your codebase like that is generally a bad idea. Assuming you have some sort of version control in place, keep a branch around with that controller, but delete it from your master branch. Merge that branch back in when you're bringing the feature back.
If, however, removing the code really isn't possible, I'd make a custom filter to redirect any requests to that controller to some other URL.
i.e.
[RedirectTo("Index", "Home")]
public class MyFutureController : Controller {
...
where you have
public class RedirectToFilter : ActionFilterAttribute {
public string RedirectAction { get; set;}
public string RedirectController { get; set; }
public RedirectToFilter(string redirectAction, string redirectController) {
RedirectAction = redirectAction;
RedirectController = redirectController;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = RedirectToAction(RedirectAction, RedirectController);
}
}
Any requests to FutureController will just get redirected to your Home/Index route. In the future, you just have to remove the attribute from the controller and you're good to go.
You can add a custom route, so that when request comes for this controller it actually redirects to some other controller, e.g. HomeController
Use an OnActionExecuting action filter on the controller.
public class FooController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Detail()
{
return View();
}
protected override void OnActionExecuting( ActionExecutingContext filterContext )
{
filterContext.Result = RedirectToAction( "Index", "Home" );
}
When you're ready to start using the controller just delete the OnActionExecuting method and you're all set.
Just decorate the action method with the [NonAction] attribute. More Here
You could simply write a view to display to the user who directs themselves there that it is still under construction or something of the like. Excluding or commenting out the controller is certainly an option, or you could rewrite all associated methods to reroute to another page.
I think the best solution is probably to just exclude it altogether, having it in your application doesn't benefit you or the user if you're not using it currently. The code should remain so you're free to tweak and refine it before you actually get a release version going.
So I have an MVC app that should change the Website title, and header color based on the domain the app is hit from. So I have a simple table setup in SQL as such:
DomainName (PK), WebsiteTitle, HeaderColor
Domain1.com, Website Title for Domain 1, #ebebeb
So I am trying to figure out the best way to return this information for each page view. Sure I can go ahead and lookup the site info in each model thats returned from the controller. But are there any other ways I can approach this? Maybe at a lower level in the stack?
Thank you!
There are many ways you can do this. ActionFilters are one way, or in a BaseController.
You need to determine if every action requires this, or if only certain actions.
If you decide every action, create a controller base, inheriting from Controller, then overriding OnActionExecuting. In that method you can make you calls to fetch and add the data to viewdata. Like so:
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData.Add("SiteTitle", "Site title");
base.OnActionExecuting(filterContext);
}
}
If you prefer to use a base viewmodel that has this information, it would be best to override OnActionExectued where you can get access to the actions results, and modify the base model to set your values. Like so:
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
var baseModel = (BaseViewModel) result.ViewData.Model;
baseModel.SiteTitle = "Site Title";
base.OnActionExecuted(filterContext);
}
}
Depending if you want an inheritence chain for your viewmodels. Either works. You'll also notice that I just set the values. Use whatever source for values you need. If you are pulling them from the db, I would cache the values so that for every action you are not hitting the db for it.
This problem is fundamentally identical to swapping layout or master pages for mobile vs desktop browsers. However, instead of looking at the device caps in a web request to determine which layout to use, you'd check the domain of the request.
See this article for a slightly complex (but thorough) overview of selecting mobile vs desktop views. Much of what the author says is focused on detecting screen solution, etc., which doesn't directly apply to you, but the mechanism for selecting the master or layout page should be just what you're looking for.
Or, you can handle this through inheritance.
Implement a base controller, like so:
public class BaseController : Controller
{
public string SiteTitle { get { .... } }
public string HeaderColor { get { ... } }
/// whatever other "global" properties you need
}
Then, each of your controllers inherit from BaseController
public class HomeController : BaseController
{
public ActionResult Index()
{
var myTitle = SiteTitle;
/// then, do whatever you want with it
return View();
}
}
In the property accessors in BaseController, read the title and whatever other properties you need from a .settings file or the AppSettings section in web.config.
Controller also provides events that can be used to set these properties so that you don't have to duplicate any code for getting those values into each view.
Given an ASP.NET MVC Controller class declaration:
public class ItemController : Controller
{
public ActionResult Index()
{
// ...
}
public ActionResult Details()
{
// ...
}
[Authorize(Roles="Admin, Editor")]
public ActionResult Edit()
{
// ...
}
[Authorize(Roles="Admin")]
public ActionResult Delete()
{
// ..
}
}
I need to reflect a list of methods in this class which may be invoked with the current user's permissions.
Please share some ideas of what could be done in this case.
Well for the new question think something along the lines of:
new ReflectedControllerDescriptor(typeof(ItemController)).GetCanonicalActions()
could be used to return the list of all available actions. I don't have ASP.NET MVC available to me at work, so I can't really check to see if the ActionDescriptor's returned by that will contain some parameter which says which members are allowed to execute them.
http://msdn.microsoft.com/en-us/library/system.web.mvc.actiondescriptor_members%28v=VS.90%29.aspx
That is the members of the ActionDescriptor, you might be able to find something in there. I'll see tonight if I can figure it out, this has gotten me kind of intrigued.
There's no universal user login/authentication system for all applications, thus this really isn't possible to create a 'universal solution'. You could create your own user login and authorization classes which you then add your own annotations to methods to do, but its going to have the same restrictions that the asp.net mvc system has, its only for your login/authorization system (or whoever extends that system).
I started off using the default project's AccountController, but I've extended/changed it beyond recognition. However, in common with the original I have a LogOn and LogOff action.
Clearly, the LogOn action must be accessible to everyone. However, since I've added lots of other actions to this controller (to create & edit users), I want 99% of the actions to require administrator role membership.
I could decorate all my actions with [Authorize Roles="Administrators"] but there's a risk I'll forget one. I'd rather make it secure by default, by decorating the controller class itself with that attribute, and then relax the requirement on my LogOn method. Can I do that?
(As in, can I do that out-of-the-box without creating custom classes, etc. I don't want to complicate things more than necessary.)
To override an controller Attribute at the Action level you have to create a custom Attribute and then set the Order property of your custom attribute to a higher value than the controller AuthorizeAttribute. I believe both attributes are then still executed unless your custom attribute generates a result with immediate effect such as redirecting.
See Overriding controller AuthorizeAttribute for just one action for more information.
So I believe in your case you will just have to add the AuthorizeAttribute on the Actions and not at the controller level. You could however create a unit test to ensure that all Actions (apart from LogOn) have an AuthorizeAttribute
You can use AuthorizeAttribute on your class
http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute.aspx
For relaxing you can implement for example a custom action filter attribute like this (I didn' test if it works).
public class GetRidOfAutorizationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// you can for example do nothing
filterContext.Result = new EmptyResult();
}
}
After way too much time, I came up with a solution.
public class OverridableAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var action = filterContext.ActionDescriptor;
if(action.IsDefined(typeof(IgnoreAuthorization), true)) return;
var controller = action.ControllerDescriptor;
if(controller.IsDefined(typeof(IgnoreAuthorization), true)) return;
base.OnAuthorization(filterContext);
}
}
Which can be paired with IgnoreAuthorization on an Action
public class IgnoreAuthorization : Attribute
{
}
Usually I protect my Actions with [Authorize] but this time I need to check if a user is authorized inside the action.
Eg
if(userIsAuthorized) {
//do stuff
}
else {
//return to login page
}
I believe I am using 'Forms Authentication'
This question is kind of similar to this but none of the answers given seemed to work.
EDIT: I have done some more digging- it seems if I breakpoint on an Action that has [Authorize], the User.Identity is set, but on Actions without it, the User.Identity is empty, even if I am logged in
If you just want to know if the user is logged in:
if (User.Identity.IsAuthenticated) { ... }
If you are trying to do anything role-specific:
if (User.IsInRole("Administrators")) { ... }
The User instance is a public property of the Controller class, so you always have access to it from a Controller you write. If no user is logged in you should have a GenericPrincipal for the User and a GenericIdentity for the User.Identity, so don't worry about checking for nulls.
Request.IsAuthenticated should work for what you're trying to do.
I suggest first figuring out what kind of Authorization your using. ;)
The answer you posted is correct. From what I remember poking around the [Authorize] attribute and related ActionFilter code MVC internally calls Page.User.Identity.IsAuthenticated just like those code examples.
Create an attribute like this: OnActionExecuting will get executed first before other code from the action
public class IsAuthenticatedAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//do your validations here. and redirect to somewhere if needed.
filterContext.HttpContext.Response.Redirect("/") //this will send user to home.
}
}
on each action where you need to check, add attribute like this:
[IsAuthenticatedAttribute]
public ActionResult ActionName(parameters?)
{
// no need to worry about checking here.
//do you action things
}
EDIT:
This one still completes the action and then only redirect it. Not so much useful.
Put annotation [Authorize] in every your Action.
Microsoft link.
Example:
public class AdministrationController : Controller
{
// GET: User/Create
[Authorize]
public ActionResult Create()
{
}
}