.Net MVC When to use OnActionExecuting? - asp.net-mvc

Some very basic question.
When to initialize or assign in OnActionExecuting?
Very simple scenario:
public partial class OrderController : DefaultController
{
private int customerId = 0;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
customerId = WebSecurity.CurrentUserId;
base.OnActionExecuting(filterContext);
}
}
Should I assign customerId in OnActionExecuting so I can reuse it in code in each action?
Readability
Eliminating repeating
or should I just assign it in separate actions each time?
We do not need this assignment or in case of initialization of some object in each action)?

the answer is, of course, it depends on your team's coding style and other circumstances.
Another option you haven't considered is creating a custom action filter to contain this logic, and reuse it with only one line of code above the action-method signature.
Then if you decide to apply it to all/any action-methods, you can apply it to the controller, or to a base-controller even.

Related

Adding users to roles outside of controller using MVC template

I'm using the standard MVC template and can happily create users and add them to roles from my controller.
My question is can I do this from one of my Models?
To clarify I'd like to move the new user logic from my controller to my UserSetting class
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(UserSettingsViewModel model)
{
if (ModelState.IsValid)
{
UserSetting userSetting = new UserSetting(model);
UserSetting.Create(userSetting);
}
}
My UserSetting Class:
public class UserSetting : Controller
{
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
public static void Create(UserSetting userSetting)
{
switch (userSetting.AccountType)
{
// InternalUser
case 0:
AddOrUpdateInternalUser(userSetting);
break;
// ExternallUser
case 1:
AddOrUpdateExternalUser(userSetting);
break;
// InternaDoctor
}
}
public async void AddOrUpdateInternalUser(UserSetting userSetting)
{
var db = new V2Context();
db.UserSettings.AddOrUpdate(userSetting);
await UserManager.RemoveFromRolesAsync(userSetting.UserName, UserManager.GetRoles(userSetting.UserName).ToString());
await UserManager.AddToRolesAsync(userSetting.UserName, "newrole");
db.SaveChanges();
}
}
Honestly, your code doesn't look too bad to me, at least in the sense of being concise. The controller is supposed to be responsible for things like working with the database, so I don't see a strong need for you to abstract any of that away. You might be able to turn like three lines of code into one by calling a method on a helper class, but does that really make any difference in the long run? No, and in fact, one could argue that you are actually adding complexity at that point, such that you now have to look in two places rather than one, just to move such a small amount of code out.
Also, if you were to move this code out to a helper class, then you would also need to inject your context into it. A lot of developers make the fatal mistake of trying to new up their context inside another class, and that will only cause problems.
Personally, I just try to keep my action methods light and create protected methods on the controller, which the actions utilize, to do the heavy lifting. It's the same practice Microsoft follows in their sample code for things like Identity. Long and short, keep the code in the controller unless there's an exceptionally good reason to move it out.
If your actions are getting too heavy, refactor the code out into private/protected methods (depending on whether you care about being able to subclass the controller or not) on the controller. Then, if you have code that is applicable to more than one controller, factor that out into some helper class that each can utilize.

Routing with parameters not specified in the method's signature

I'm not positive, but I think this is an MVC routing problem. I still don't fully understand how routing works in MVC.
Lets say I have several action methods that I've applied a custom filter attribute to:
[MyAttribute]
public ActionResult Method1()
{
...
}
[MyAttribute]
public ActionResult Method2()
{
...
}
These methods normally don't accept any parameters. Unfortunately the requirements have changed and now all of these methods might be receiving an optional parameter called "MyParameter". If the parameter is passed in then a property in the ViewBag will be set.
In the actual project, there are ~70 of these methods. I was hoping to do it via the "MyAttribute" filter, because it already exists. So I would add something like this to the "MyAttribute" filter:
public class MyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
... // other existing code
// If a certain parameter was passed, I want to set a ViewBag property.
if( filterContext.ActionParamenters.ContainsKey("MyParameter") )
filterContext.Controller.ViewBag.MyProperty = filterContext.ActionParamenters["MyParameter"];
}
}
But unless the method's definition has "MyParameter" in it as a parameter, it doesn't see it in the ActionParameters collection. This is what makes me think it is a routing issue.
I would really prefer to not need to add the argument to all of the methods, but if there's no other way to then I can. Any suggestions or ideas would be appreciated.
I found and went with this:
ViewBag.MyProperty = filterContext.HttpContext.Request.Prams["MyParameter"];
Will this won't work in action filter?
ViewBag.MyProperty =
filterContext.Controller.ValueProvider.GetValue("MyParameter").AttemptedValue;

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.

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

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