How to handle site-wide querystring parameters in MVC? - asp.net-mvc

I need to be able to process a querystring parameter throughout the site (like ?promo=38 for example). I was trying the procedure specified here Passing a {sitename} parameter to MVC controller actions but it wasn't working. My guess it's because according to http://blog.stevensanderson.com/2007/11/20/aspnet-mvc-pipeline-lifecycle/ querystring processing happens after the controller is instantiated.
So what would be a simple way to accomplish what I want? namely, being able to do something like setting a base controller property, or setting a session variable, from a querystring parameter anywhere in my site, without having to manually specify something in all the controller actions?

Override OnActionExecuting() your base controller or in an Action Filter as suggested by #jrummell. An Action Filter might be the way to go, but you would still need to decorate all of your controllers or create a base controller and decorate that with it.
public class PromoActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewBag.Promo = filterContext.HttpContext.Request.QueryString("Promo");
base.OnActionExecuting(filterContext);
}
}
[PromoActionFilter]
public class BaseController : Controller
{
... Some Actions ...
}

Related

MVC + Multiple tenancy app

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.

Custom MVC3 caching filter attribute

I would like to implement caching at the action level in MVC in a certain way.
I am aware of the OutputCache attribute, but I can't cache the entire page.
I would like to cache the model returned by the action.
So basically, I want to create a filter that will prevent the action method from being invoked, but have MVC behave as if it was invoked.
Assume that I plan on ignoring any "return View("viewName")" assuming all will be "return View()".
You can create a filter that inherits from ActionFilterAttribute
This is what I use
public class CacheControlAttribute : ActionFilterAttribute
{
public CacheControlAttribute(HttpCacheability cacheability)
{
_cacheability = cacheability;
}
private readonly HttpCacheability _cacheability;
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
cache.SetCacheability(_cacheability);
cache.SetExpires(DateTime.Now);
cache.SetAllowResponseInBrowserHistory(false);
cache.SetNoServerCaching();
cache.SetNoStore();
}
}
You can do partial caching.
For example, you can make an action method which IS NOT invoked as a regular action, but rather renders a partial view (an HTML snippet eventually) by calling Html.RenderPartial(). That way, you don't cache the whole page, but only those fragments which change less frequently.

ASP.NET MVC: Can I say [Authorize Roles="Administrators"] on the Controller class, but have one public action?

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
{
}

is there a better way I can do this? in asp.net mvc

since my app has a sidebar menu that gets built dynamically through an xml everytime I want to load a view I need to pass all of this parameters.
ViewData["mainItems"] = navigation.getItems();
ViewData["controller"] = controller;//this is just a string with the name of my controller
how can I avoid doing this everytime but that the parameters keep getting being retrieved and passed?
You don't really need to pass the controller name. you have it in the viewContext.
See this SO question : Find what ASP.NET MVC controller in master view
If i understand what your problem is correctly, then you might want to create a base class controller and make all your controller inherit from this base controller. Then override the OnActionExecuted event which gets fired on every action.
public class BaseController : Controller {
protected override void OnActionExecuted(ActionExecutedContext filterContext) {
ViewData["mainItems"] = navigation.getItems();
ViewData["controller"] = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
base.OnActionExecuted(filterContext);
}

ASP.NET MVC : Context sensitive validation

I have bunch of action-methods that need to verify the ownership of the orderId passed to the action something like:
public ActionResult CancelOrder(int orderId) {
If (!MyDatabase.VerifyOwnership(orderId, User.Identity.Name) return View("You are an imposter!");
// ...
}
What's an easy way to verify orderId belongs to User.IdentityName without having to copy/paste same lines over and over?
I have tried ActionFilterAttribute but it doesn't have access to the context (MyDatabase object for example). What's a good way to handle this?
" but it doesn't have an access to the context"
Sure it does:
public class VerifyOwner : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var myController = (MyControllerType)filterContext.Controller;
if (!myController.MyDatabase.VerifyOwnership(orderId, User.Identity.Name)
//do what you do
base.OnActionExecuting(filterContext);
}
}
All you have to do is cast the Controller property to your controller type. This get really easy is you have a custom base Controller all your Controllers inherit from. Then set that base controller to have the MyDatabase property and you have an easy time using this attribute across multiple controllers.
Your controller seems to have access to your context. Therefore if you use an action filter attribute that implements IAuthorizationFilter you can cast the filterContext.Controller in the OnAuthorization method to your controller type and be able to do what you set out to in the first place. (Which I reckon is the way to go!)
Kindness,
Dan

Resources