Imagine you have a secured site and a View that can be generated in multiple ways, depending on the user role. Say, an Admin sees all, Manager sees some columns and some action buttons, User sees other columns and other action buttons.
How would you implement this? As far as I see it, there are three main options:
Controller with [Authorize] attribute and the action returning 1 View per user role, being those views tailored to the role;
Controller with [Authorize] attribute and the action returning 1 View for all roles, with logic to hide/show columns, fields, buttons;
Controller with [Authorize] attribute and the action returning 1 View, which based on the roles renders different Partial Views.
I prefer the third approach, but do you see a better way to implement this?
Thank you in advance
Depending on the complexity of your view, either the first or the third option would seem ok to me. As for the second option; Its generally recommended to avoid logic in views so I'd stay away from that one.
If you go for the third option you should consider using EditorTemplates and DisplayTemplates. This will allow you to make your (main) view agnostic to what partial view to render.
Make your viewmodel (or part of it) inherit from a single base class. Create display and/or editor templates for each view model type and in your view just say Html.DisplayFor( ... ) or Html.EditorFor( ... ). MVC will automatically pick the right template, without the need for logic in your view.
What I have been doing for menus and other navigational items is that I have a ViewModel class for it. Here is a simplified version.
ViewModel
public class Action
{
public string DisplayName { get; set; } // localized
public string Url { get; set;
}
public class MenuViewModel
{
public List<Action> Actions { get; set; }
public MenuViewModel()
{
this.Actions = new List<Action>();
}
}
I fill that depending on the user role. Admin gets more links etc.
That ViewModel is part of "main" view model
public class AlbumEditorViewModel
{
public MenuViewModel Menu { get; set; }
}
Then I'll pass that view model for the partial view that is responsible for the menu.
View (Razor)
#model AlbumEditorViewModel
.. razor stuff here ..
#Html.Partial("Menu", Model.Navigation)
.. razor stuff here ..
Partial View
#model MenuViewModel
<ul>
#foreach (var action in Model.Actions)
{
<li>
#GridHelper.GetAction(action)
</li>
}
</ul>
I hope that this will give you ideas
Related
I'm working on a new MVC/Razor website, and we've gotten to the point where we need to put in a real menu structure. Currently, our menus are hardcoded into the global shared layout, we need to populate the menus from code, so that we can determine, after login, which menus will be displayed for a user.
We'll want the menus to appear on pretty much every page, except for login and a few other special cases. So we'll continue to one them to be rendered in the shared layout.
It looks simple enough to create an #Html.DisplayFor template that would render a menu item. The question is, how to get data to it?
The shared layout is used with quite a number of views, handling a number of different models, being loaded from a number of controllers.
Adding a List member to each model, and then populating it in each controller, seems tedious, bothersome, and error prone.
Or we could skip adding the collection to each model, and instead have each controller stick it in the ViewBag. That doesn't seem all that great, either.
The only possibility I've been able to dream up, to keep from having to repeat this for every controller, is to define a common base class, derived from Controller, that all of the controllers that use the shared layout could derive from, in turn, that would stuff the MenuItem collection into the ViewBag.
But I'm wondering if I'm missing something. Is there some preferred way of dealing with this situation?
Shared/base view models is a way to go as you mentioned, but in my opinion its not very "single responsibility". Having to inherit from a base model and add menu items on each page is tedious as you mentioned.
If I was you I would use:
#Html.Action("action", "controller")
https://msdn.microsoft.com/en-us/library/ee703457.aspx
This would call an action method, and then you can bind a specific menu model and also return a specific partial view that would be rendered in the view that called it
#Html.Action("TopMenu", "Controller")
[ChildActionOnly]
public PartialViewResult TopMenu()
{
return PartialView(new MenuModel());
}
Create a TopMenu.cshtml
You can even go as far as passing in values into the controller action to modify the output model/view.
You can call #Html.Action from your layout/shared view.
EDIT
Added [ChildActionOnly] as highlighted in comment as this prevents access to the action unless it was called from a parent action and should be used here
If I understand your question correctly...
I prefer to have a MasterViewModel that every ViewModel for every page inherits from. The MasterViewModel holds things common to every page, like the user's name, navigation rules, etc. The shared layout then uses #model MasterViewModel, which is populated by an ActionFilterAttribute.
public class MasterViewModel
{
public IEnumerable<string> NavItems {get; set;}
}
public class HomeIndexViewModel : MasterViewModel
{
// stuff for the home view
}
I then have a MasterViewModelAttribute:
public class MasterViewModelAttribute : ActionFilterAttribute
{
[Inject]
public IDatabase Database { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
ViewResultBase viewResult = filterContext.Result as ViewResultBase;
// not a view result
if (viewResult == null)
{
return;
}
MasterViewModel model = viewResult.ViewData.Model as MasterViewModel;
// not a MasterViewModel view model
if (model == null)
{
return;
}
model.NavItems = // set your nav items
}
}
Then, every controller derives from a base controller, which implements the attribute:
[MasterViewModel]
public class BaseController : Controller
{
}
And for each controller:
public class HomeController : BaseController
{
public ActionResult Index()
{
return View(new HomeIndexViewModel());
}
}
And finally, the layout:
#model MasterViewModel
// omitted
<div class="nav">
#foreach (var item in Model.NavItems)
{
<a></a>
}
</div>
The thing I like about the pattern is that you still get strong type-checking by using ViewModels (as opposed to the ViewBag), and you don't have to deal with all the extra nav stuff when you want to unit test an action, for example.
This also has an advantage over using partial views and #Html.Action() in that you don't incur a second request to the server for every page load.
I'm trying to refactor an editor screen. The editor's model was the domain model, and I'm moving to a view model for this screen because I need to only allow edit of a couple of fields if the user has a certain role. I want to use the same editor view for both creating and editing the model, though that may be part of my problem.
I've read elsewhere on SO that view models should be simplistic, so there is no need for something like an interface hierarchy in view models. However, how can I use the same strongly-typed view for two different view models, one for create and one for edit, since the view models will have almost-the-same-but-slightly-different properties depending on the user role?
Here's a simplified example using the two view models I created:
public class RequirementCreateView
{
public int Id { get; set; }
public string Name { get; set; }
public string Justification { get; set; }
public string ImpactIfNotFunded { get; set; }
... etc for about 40 properties ...
}
public class RequirementEditView
{
public int Id { get; set; }
public string Name { get; set; }
public string Justification { get; set; }
public string ImpactIfNotFunded { get; set; }
public string Decision { get; set; }
public string Status { get; set; }
... etc for about 40 properties ...
}
The two view models are identical except the Edit model has two extra properties Decision and Status that can only be set by someone with the appropriate role. I use AutoMapper to map from the domain Requirement object to the view model and vice-versa for the create/update action.
However, now that I have two view models obviously I can't use a single strongly-typed editor screen because things like ValidationMessageFor(m => Model.Name) won't work. That led me to consider an interface hierarchy for this set of view models, like so:
IRequirementEditorView --> common properties
|--> IRequirementCreateView --> create-specific properties (none right now)
|--> IRequirementEditView --> edit-specific properties
And then have the editor view screen reference IRequirementEditorView. But again, that goes against the current wisdom on simplistic view models. But then the alternative is to duplicate my editor screen, which violates DRY.
This obviously is a common problem, but I'm stumped right now. Any advice?
Thanks.
Edit I should clarify after looking at some other SO similar posts: when I say I'm using the "same" editor view screen, I am using two separate views, Create.cshtml and Edit.cshtml. Each of these then simply references a partial view that contains the actual editor form like so:
#Html.Partial("Controls/RequirementEditor", Model, ViewData)
That is what I had in place when I was using the domain object as the view model.
Turns out there was another way of thinking about this. Shortly after I posted this question I made a few changes and I'm much happier with the result. Here's what I did:
View model now has additional bool properties AllowDecisionEdit and AllowStatusEdit.
View model has two new IEnumerable properties DecisionsList and StatusList.
1 and 2 are populated in the controller and passed to the appropriate view (create or edit).
The create view calls Html.BeginForm() then calls the partial Html.Partial("Controls/RequirementEditor/Editor"). The edit view does the same but also adds Html.HiddenFor(m => Model.Id) before the partial call to ensure the edit form works properly.
So now I have one edit model, one editor partial form, two separate views (create and edit) that each use the same partial and the same editor, and the functionality is turned on or off in the controller. My approaches above smelled very, very bad and I wasn't happy with either one. Once I did this everything felt right with the world. :)
In MyMainView I have multiple PartialViews.
Each PartialView is strongly typed to a property exposed in MyMainView and each partial view also contains some common information.
eg
public class MainModel
{
public SubModel1 { get; set; }
public SubModel2 { get; set; }
public SubModel3 { get; set; }
public CommonStuff { get; set; }
}
Is there an accepted practice for achieving the sharing of this Common property?
I was thinking of having a property in the SubModel that reffered back to its parent. Is this recommended/not recommended?
eg.
public class SubModel1
{
public int Number { get; set; }
public MainModel ParentModel { get; set; }
}
Main goal here is to avoid loading up the same data over and over again.
For the model I am currently looking at, its a series of LookUp dictionaries that are shared across the View, and used in things like drop down lists.
This post will help you to solve the confusion.
In your case mostly you may have to query the complete model in the main action and feed it to the main view. From the main view pass the main model to the partial views that depends upon both the sub model as well as the common properties using Html.RenderPartial or only the sub models to the partial views those needs only that.
You should use child actions if you are displaying a sub model in a partial view and that partial view doesn't depends upon the main model, simply the partial view is completely independent of the main view.
You can also go for child actions suppose you need apply caching behavior for a particular section.
You can avoid child actions suppose you want to display the same model in two or more partial views in the same view, because you have to query the data again and again.
So the solution is you should judiciously choose child actions or Html.RenderPartials based upon the above comments.
You could use child actions. Phil Haack blogged about them. This way you could encapsulate the entire common functionality into a separate Model/Controller/View lifecycle which is distinct from the main one and embed it as a widget.
Just starting out in ASP.NET MVC - I have a page in ASP.NET MVC3 that has a searchbox, and when the user fills it out and searches, below the search box a webgrid shows the searchresults.
I have based my (razor) view on a model List<articles> so the view knows what an article is and my webgrid can show me a list of articles. However, I also need the searchbox to be validated. I have a "searchmodel" that has the searchbox as a required field, but I can't base the view on both the List of articles as well as the "searchmodel" (can I?).
Ofcourse I would like the built-in (clientside and serverside) validation of MVC to work, but to do that I would need to base the view on my "searchmodel" and the gridview would no longer work.
Can anyone explain how I would go about this?
Thanks,
Erik
Define a view model:
public class MyViewModel
{
[Required]
public string Search { get; set; }
public List<articles> Articles { get; set; }
}
Now strongly type your view to MyViewModel and have your controller action pass an instance of this new view model to the view instead of simply a List<articles>. This way you have everything.
I'm working on my first ASP.NET MVC 3 application and I've got two Controllers with Views, IceCreamController/IceCreamView and RecipeController/RecipeView, and on each the user can make a selection and display the recipe.
Selection causes a PartialView to be displayed which an Edit link on it. When clicked the EditView for this recipe is displayed, allowing the user to edit the attributes of the recipe item selected.
Great. This works fine except currently the POST action in the RecipeController looks like so:
[HttpPost]
public ActionResult Edit(RecipeViewModel viewModel)
{
// updates the underlying model with the viewModel
// other things not germane to the discussion
return View();
}
and that ends up always showing the Index view for Recipe, which isn't what I want. Rather, I'd like to be able to do is send the user back to the appropriate View (IceCreamView or RecipeView) when they've submitted their changes.
I assume that others have done something similar to this. How do you communicate which Controller/Action should be redirected to when the Edit is done?
Note:
I added a bit above to clarify that I've got two separate Controllers (IceCreamController and RecipeController) and each has a View that can select and ultimately do a
#Html.Partial("_Recipe", model.recipe)
to display the details of a particular recipe. My problem is how to get the page redirected back to either IceCreamView or RecipeView by the Edit Action on RecipeController - essentially, how do I communicate where it should go since the recipe details could have been displayed by either path.
Solution Employed:
As you can read below in the comments to Darrin's answer, since I've got more than a single controller involved, a solution is to utilize the viewmodel to pass in the controller/action that should be redirected to following when the Edit post action is completed.
As I've got more than a single instance of this situation (arriving at an Edit page via multiple paths), I think creating a simple BaseViewModel to hold this functionality might be in order and then have all the applicable viewmodels inherit from that BaseViewModel.
I'm don't think it needs to be anything more than something like:
public BaseViewModel
{
public BaseViewModel(string controller, string action)
{
ControllerName = controller ?? string.empty;
ActionName = action ?? string.empty;
}
public string ControllerName { get; set; }
public string Action { get; set; }
}
And then a viewmodel's constructor could just be modified to pass in the controller/action and hand that off to the base class.
There may be other solutions to this and if so, I'd like to hear them.
[HttpPost]
public ActionResult Edit(RecipeViewModel viewModel)
{
// updates the underlying model with the viewModel
// other things not germane to the discussion
return View("IceCreamView");
}
or if you wanted to redirect you could have a controller action that would serve this view and then return RedirectToAction("IceCream"); which is probably more correct rather than directly returning a view from a POST action in case of success.