On the site I'm working on, there are users with different permissions on the site.
Given the schedule ID and employee ID that we're currently looking at, we can get their role-specific permissions.
Right now, our BaseModel has a property that properly accesses the DB and grabs this info.
For all views that pass a model to the view, everything runs fine.
The problem lies in Controller Methods where no model is passed. In a few views, all they're supplied is a few ViewBag entries, and work fine.
However, I /need/ the CurrentPermissions property in those pages nonetheless, for the layout. Whether or not the permissions have one boolean value set true/false, something may/may not be displayed/populated.
So, my option seem to be:
Somehow throw my CurrentPermissions into a ViewBag entry for all views, and access them through that instead of the base model.
I'm not sure how to do this. I've seen people using OnActionExecuting, but that fails since my connection to TransactionManager is not yet set up at that point.
Somehow throw just the BaseModel into those views that don't currently pass a model. I'm refraining from this as much as possible. I'm not sure how I would go about doing such, but it seems like that would over-complicate the situation.
How can I go about pushing this CurrentPermissions object (generated from a call to my TransactionManager) to every view (specifically, the Layouts!)
Your approach is what we use in out projects... and we use this approach to systematically remove the use of ViewBag changing it to ViewModels.
Other approach we have used (for UserPreferences in my case) is adding an ActionFilter that ends including the preference in the ViewBag. You decorate the actions needing it with [IncludePreferences] in my case (that is the name of my filter attribute.
EDIT ActionFilter:
public class IncludePreferencesAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller as BaseController;
// IController is not necessarily a Controller
if (controller != null)
{
//I have my preferences in the BaseController
//and cached but here you can query the DB
controller.ViewBag.MyPreferences = controller.TenantPreferences;
}
}
}
In you action you decorate it using [IncludePreferences]
As a temporary solution, I'm doing the following at the top of my Layout:
#{ OurModel.SupervisorRestriction CurrentSupervisorRestrictions = ViewBag.CurrentSupervisorRestrictions ?? Model.CurrentSupervisorRestrictions; }
This way, if we're passing in an object then it works just fine. Otherwise, I'll directly pass in a ViewBag.CurrentSupervisorRestrictions from the controller. There are only a few cases, so it's not that bad.
Better suggestions would be great, though.
Related
I guess I have a pretty standard problem as I want to pass certain data from an asp.net MVC 5 controller to a shared view, in my case navigation.
I have a template that shows username and userpicture in the navigation shared view, so I need to pass the respective data to it.
My layout structure:
Shared\Layout
Shared\Header
Shared\Navigation
%Body%
Shared\Footer
Where the Layout ist the master view and of course I also have my other views like Home\Index.
As you can imagine, I have to show the username and his userpicture in every view except the login/registration or any error views.
These views do not use the layout at all, so everytime a view is rendered using the layout structure, the user is already logged in.
So I was researching about ways to pass data from my controller to the navigation view, although my controller returns the Index view and would appreciate some clarification on their disadvantages and valid choices in my use case:
Use case:
My project has a pretty dumb MVC application that the user can access. Once he logs into the MVC app authenticates the user against the same webapi where it get's it's data from and stores the access token as well as other user details for further requests. I'm not yet sure where to store that data. As far as I understand it, the options would be Cookies, Session and local storage.
As I am pretty new to asp.net, MVC and C# in general, I didn't yet figure out how to make the [Authorize] Attribute work inside the MVC app so it can mark the user as authenticated :/
I guess the key problem is that the MVC app does not have access to the database and therefore cannot check the login and populate the User Identity.
How to transfer data from controller to view:
ViewBag: The easiest way of passing data to the view. It is not strongly typed and can be accessed in all views. I was told it is kind of a bad practise to use it and was advised to use viewModels.
ViewData: seems to be kind of the same thing as viewdata.
ViewModel: A strongly typed model that is passed to the view and needs to be declared in any view that uses it. So if I want to use it in my navigation view, I'd need to declare it there. The big disadvantage of this approach is that every viewmodel needs to have kind of a baseViewModel so they have a common structure which appearently can cause problems later down the road and also prevents me from inheriting other models to populate my viewModelStructure.
Cookies: Obviously I can store data in cookies during login and then access them in the view, but the cookies HAVE to be there so I would not be able to save this information in the session or local storage
Session: I can also store data in the session, but the session expires when the user closes the browser tab.
LocalStorage: This is pretty new to me so I can't judge it.
User Identity: I just discovered that I can also access the user's identity from Context.User.Identity.
Global Filter like the [Authorize] attribute or a custom one: If I understand it correctly, with a global filter I can populate needed data automatically in every controller action and exclude the ones that dont need it like Login/register etc. I'm not yet sure how to apply this way because of my project structure (see above).
RenderAction: I could also call another controller method via the RenderAction helper method to always render that section of the page.
How do you guys solve this problem?
If you need more clarification, please do ask.
Thanks :)
Use a base ViewModel. You can still use inheritance to build up functionality in your view models, they'll all just share a common base view model, as you said.
I'm not sure what problems you envisage with this approach, but I would suggest they're outweighed by the benefit of strongly typed, maintainable view models, that can be used by all your views, including partial views and _Layout.
A suggestion to get you started based on your layout structure:
public abstract class ViewModelBase {
public HeaderViewModel Header {get;}
public NavigationViewModel Navigation {get;}
public FooterViewModel Footer {get;}
public ViewModelBase(HeaderViewModel header, NavigationViewModel navigation, FooterViewModel footer) {
Header = header;
Navigation = navigation;
Footer = footer;
}
}
public class HeaderViewModel {
// properties
public HeaderViewModel(...) {
}
}
public class NavigationViewModel {
// properties
public NavigationViewModel(...) {
}
}
public class FooterViewModel {
// properties
public FooterViewModel(...) {
}
}
In an intranet web application at my company, numerous operations have a granular, custom security system which is used in each action/http method in our MVC controllers. Basically there are two enums; one is a set of actions that can be performed (this is extremely granular; practically every possible action has a corresponding value in the enum) and one is a set of our subcompanies. For the context of this question, I will call these enums Action and Company.
Each user in our system is associated to one Company and zero or more Actions. Inside each method in our controllers, somewhere along the way there is a check for if the current user has the right Action and Company value to be using that feature. Every controller has a "UserHelper" injected into it which contains the Company and list of Actions the authenticated user is associated with.
Approaching it this way, there is a lot of code duplication in that every method is doing its own check on these enum values and reacting to violations when necessary. I am hoping to reduce this all to a System.Attribute or System.Web.Mvc.AuthorizeAttribute which we can put on controllers or methods to automatically handle violations in a uniform way and not have to check for it within the methods. Something akin to:
public class MyController : Controller
{
[RequireActionAndCompanyAttribute(Action = Action.MyMethod, Company = Company.AbcInc)]
MyMethod()
{
// do stuff, but don't bother checking for the security values
}
}
As mentioned, I am assuming I can inherit from System.Attribute or System.Web.Mvc.AuthorizeAttribute for this purpose. However, I'm struggling. I'm not sure how to adapt AuthorizeAttribute to use our own internal security implementation (based on Action and Company) rather than one of the ASP.NET Membership systems. And the plain old System.Attribute seems so vague that I'm thinking it wasn't designed for this kind of use at all. I'm also not sure how I'm supposed to pass anything to the methods in the attribute. When I put the attribute on a method, I just want to specify what Action and Company are required to continue, like in the code snippet above. But then, how do I pass the user's actual values for these into the attribute's validation method?
Use a custom attribute inherited from ActionFilterAttribute instead of the AuthorizeAttribute. You can inject your UserHelper in this class and override the OnActionExecuting method and set the Result property of the context when your condition isn't met.
Has anyone here structured your views, controller and actions in such a way that the page titles are set from the database. How do you identify one view from another ? In which phase of the lifecycle do you retrieve the page title and set it for the view/action method
a lot of posts are scattered for asp.net, php but then a efficient way for mvc I am yet to find
You could use action filters:
public class ControllerAndActionNameAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewBag.ControllerName = filterContext.RequestContext.RouteData.Values["controller"].ToString();
filterContext.Controller.ViewBag.ActionName = filterContext.RequestContext.RouteData.Values["action"].ToString();
base.OnActionExecuting(filterContext);
}
}
But instead of putting the action- or controllername into the viewbag, you could load stuff from your database based on action and/or controller.
What if you have service or helper method that retrieves all the titles from the database and stores them to some sort of cache or static variable think Dictionary. Depending on how you plan to update with the site running determines when or how.
Then expose a static helper method that the views can call to retrieve their title.
You could probably even incorporate a T4 template to set a property for each view to call the method with the correct retrieval key
I have a very large (77 actions) controller that I am using to make a site with wizard-like functionality. The site is like a "job application manager" with multiple components such as an admin component and an end-user component. The component I'm working with is the part where the user would actually fill out a job application. The way things are structured with the other components, it makes the most sense to put all of the job application stuff in the same controller. All of these actions perform similar things but on different models, like so:
public class ExampleController : Controller
{
public ActionResult Action1()
{
Guid appId = new Guid(Session["AppId"].ToString());
... // logic to pull up correct model
return View(model)
}
[HttpPost]
public ActionResult Action1(FormCollection formValues)
{
Guid appId = new Guid(Session["AppId"].ToString());
... // logic to update the model
return RedirectToAction("Action2");
}
public ActionResult Action2()
{
Guid appId = new Guid(Session["AppId"].ToString());
... // logic to pull up the correct model
return View(model)
}
... // on and on and on for 74 more actions
}
Is there any way to reduce some of the constant redundancy that's in every one of the actions? Here is what I am thinking:
Creating a member variable Guid to store the appId and then overriding OnActionExecuting to populate this variable. Is this a good idea?
Implementing some kind of paging to cut down on the number of actions. Any suggestions on how to do that?
I would say yes to your first point and "it depends" to your second. Don't change your design just because you have a lot of methods, if all 77 ActionResult methods make sense to have, then keep them around.
Using a member variable and overriding OnActionExecuting seems like a great way to refactor that appID Guid code into a single place, so you can quickly and easily modify it in the future.
Normally for wizard view, a single action and page is used with multiple divs which can be shown according to the steps.
For example, a registration wizard screen having 4 steps, can be be handled in a single page with divs for each steps. You can make use of JavaScript and css to make it a wizard flow.
Make use of ajax to update different models, if necessary in between the steps.
You may want to put your logic (Job Manager related) in a single repository/manager class. Different controllers associated with different views(e.g AdminController, EndUserController etc) can call methods from same repository/manager class.
Another option could be replacing this..
Guid appId = new Guid(Session["AppId"].ToString());
..with a call to something like the following:
private Guid GetAppId(){
return new Guid(Session["AppId"].ToString());
}
Now you could just use GetAppId() instead wherever you currently use appId. You could of course cache the GUID in the form of a class variable, as you suggest, but it might be a good idea to limit the access and use of that variable to a method like this (get it's value via the method). Might be a little more flexible in case you want to change something later.
As for dividing the page into several pages; sure, go ahead, if it makes sense and feels right. Over 70 actions in one class does sound like a lot. If it makes more sense to keep them there however, you could try to move as much logic as possible out from the methods themselves, and into helper-classes instead. I always try to keep the actions as small as possible, and put the logic in separate classes, each of which is tailored to do one specific thing.
My point is, if each action is no more than 2-4 lines, then 70+ actions is not necessarily a problem.
After I found this in our code, I realized a few things:
"get it done now, get it right later" has a limit
I don't actually know where this goes with MVC3
#model int
#using Company.Core.Logic.Financial
#using Company.Core.Repositories
#{
var values = from AccountPlan e in new AccountPlanRepository().GetItemList()
where String.IsNullOrEmpty(e.PromoCode) // filter out promotional plans
select new { id = e.AccountPlanId, name = e.Description };
var items = new SelectList(values, "id", "name", Model);
}
#Html.DropDownListFor(m => m, items)
This specifically is an editor template (#Html.EditorFor(m => m.AccountPlan)), but it made me realize that I don't know where this kind of code goes for common things like menu-builders. If you're using Layouts for MVC3 (and who isn't), where is the code for building a menu across the top based on the user's roles? I'd imagine the view code would be iterating through pre-built menu items and HTML-ifying them, but since the Model is strong-typed, does that mean that all Models need to have knowledge of the menu items?
For once, this is where Webforms made more sense to me, since this would go in the CodeBehind, but I really want to get away from that.
edit: even though I started asking about Layout code, I'm under the assumption it works for EditorTemplates and DisplayTemplates as well. If this is an incorrect assumption, please let me know where these are supposed to go.
edit2: What I ultimately want is to have a clean, possibly even dependency-injectable place to run code that's called from an EditorTemplate. Perhaps this is a case where the EditorTemplate calls immediately into a RenderAction?
Looks like this solves the problem pretty well (see the marked answer, not the original question):
http://forums.asp.net/t/1566328.aspx/1?Building+ASP+NET+MVC+Master+Page+Menu+Dynamically+Based+on+the+current+User+s+Role+s+quot+
Basically, call RenderAction(...) and it will build the model it needs, rather than forcing you to have every model require a list of menu items.
For me personally, I do a lot of menu filtering based off of active directory groups so I need to know what their access levels are across the entire application.
I create a new controller that I call ControllerBase
public class ControllerBase : Controller
{
//authorization group setting an menu creation here.
//set properties and objects to ViewBag items to access from the front end.
protected override void Dispose(bool disposing)
{
_db.Dispose();
base.Dispose(disposing);
}
}
and then on all the other controllers in my project I just extend from ControllerBase
public class HomeController : ControllerBase
{}
This keeps all my server logic in one file for managing permissions and gives all my pages access to these variables when I need to hide or show different ui elements based on permissions.
Child Actions are perfect for this scenario. The logic required to generate the view is wrapped up in a controller action, like normal, and the view that wants to make use of the child action simply renders the action..
You can also cache these partial views, which would make sense for something like a main menu - as presumably the users permissions won't change that often.
e.g.
[OutputCache(Duration = 300)]
[ChildActionOnly]
public ViewResult MainMenu()
{
var model = GetMenuModel();
return View(model);
}
The view that wants to render the child action does so like this.
#{ Html.RenderAction("MainMenu", "Account"); }
And thus the view calling the ChildAction has no need to know what model the view requires.
There are also overloads on RenderAction, should your child action require you to pass parameters to it.
You shouldn't (have to) access the Repository inside the View. That belongs in the Controller.
And the Menu is implemented in the Masterpage, you don't give much details on specifics.