I'm developing a website in ASP.NET MVC where I would like to show different sections of a view for users with different security levels. In essence, the views are the same, but users with higher levels of security must be able to see sections that shouldn't be seen by users with security levels above of, for example, administrators.
I don't know how to do this in an object oriented way. I think this can be done with inheritance, but I don't know how to implement inheritance in the view.
Also, I know I can code a lots of ifs, doing something like
<% if (User has some security level) { %>
<span>show this info</span>
<% } %>
but this doesn't smell well. The fact is that I don't know how to use object oriented principles or design for this task.
I think this is a common task, so I think there is a pattern to accomplish this task. For example, Stackoverflow does this when shows some options (edit, delete, etc) for the user who posted a question (or answer or comment) and hides the same options to everybody else.
Depending on the complexity of what you are doing, the if statement route may be sufficient. If not, then you could look at using partial views and write an HtmlHelper extension that allow you to render a partial based on a particular role. It might look something like this:
<% Html.RenderPartialWithRole( "AdminSection",
Model,
ViewData,
User,
"Administrator",
null ) %>
public static void RenderPartialWithRole( this HtmlHelper helper,
string partialName,
object model,
ViewDataDictionary viewData,
IPrincipal user,
string role,
object htmlAttributes )
{
if (user != null && !string.IsNullOrEmpty(role) && user.IsInRole(role))
{
helper.RenderPartial( partialName, model, viewData, htmlAttributes );
}
}
Using a strongly typed view, have a Boolean property on your model like ShowSection (one for each section, logic to set in your controllor). Have the sections in divtags with good Ids. Then use JavaScript or jquery to set the div tags display style based on the Boolean property.
You could have all the sections in partials, with views built to include the Sections available to the different permission levels. So one for admins, one for every level. Then your Controller has the logic to decide which view to use. So any OO part would be in the controller, not the view.
This isn't an object-oriented approach, but it's related. The interesting part of this question is how to get ride of the if-statements. The usual method to get rid of if's or case's is to use a lookup table of criteria and effects. There are other techniques that use this same idea, such as data-directed programming (http://en.wikipedia.org/wiki/Data-directed___programming) and dispatch tables (http://en.wikipedia.org/wiki/Dispatch_table). Many language implementations use type dispatch tables to implement virtual method calls.
Assuming partial views are OK for this problem, the lookup table could be a list of pairs. The first element of the pair is a role name to check against the current user. The second element of the pair is the name of teh partial view to render when the role check succeeds.
We initialize the table in the controller (or whereever) and assign it to ViewData, then use Html.RenderViewByRole to choose and render the correct partial view:
<% Html.RenderPartialByRole(User, (List<Dispatch>)ViewData["rolePartial"]); %>
public static class MyHelper {
public static void RenderPartialByRole(this HtmlHelper helper, IPrincipal user, List<Dispatch> rolePartial) {
foreach (Dispatch d in rolePartial) {
if (d.CheckRole(user)) {
helper.RenderPartial(d.PartialName);
break;
}
}
}
}
public class Dispatch {
string _roleName;
string _partialName;
public Dispatch(string roleName, string partialName) {
_roleName = roleName;
_partialName = partialName;
}
public bool CheckRole(IPrincipal user) {
return user.IsInRole(_roleName);
}
public string PartialName {
get { return _partialName; }
}
}
public class HomeController : Controller {
List<Dispatch> rolePartial = new List<Dispatch>();
private void InitTable() {
rolePartial.Add(new Dispatch("admin", "adminPartial"));
rolePartial.Add(new Dispatch("report", "reportPartial"));
rolePartial.Add(new Dispatch("guest", "guestPartial"));
}
public HomeController() {
InitTable();
}
public ActionResult Index() {
ViewData["rolePartial"] = rolePartial;
return View();
}
}
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 have two models: Reptile and Species. A Reptile has a Species, stored as an ID in the database:
How should I set up the details controller action/view for Reptile so that it displays the Title property of the Species instead of the ID that the Reptile uses?
My initial thought was just to grab the data in the controller and pass it in the ViewBag, but this seems inappropriate, and overly complex when it's time to setup the list action.
What's the proper way to do this?
It seems like I need to make a view model, but what confuses me is how to properly design it so that there aren't too many database calls.
Here is my initial attempt at a ViewModel:
public class ReptileDetailsModel
{
[Required]
public String Species { get; set; }
//etc...
public ReptileDetailsModel(Reptile reptile)
{
this.Species = reptile.Species.Title;
// etc...
}
}
Another way to achieve the same thing in more generic way is to use AutoMapper
Few advantages I can think of:
Automatically map exact properties (you only need specify anything that is exception to the rule)
Centralized in one class / method, whatever
Ability to ignore, map to another classes properties, even custom logic
Non intrusive, it is up to you how / when you want to use it.
In your particular instance I would create a mapper something like
Mapper.CreateMap<Reptile, ReptileDetailsModel>()
.ForMember(dest => dest.Species,
options => options.MapFrom(source => source.Species.Title));
This mapper info need to be registered somewhere. In MVC projects I have been involved, I would register a mapper into global.asax.
Then in your controller, you would want to invoke the mapper to map your reptile instance to your model
ReptileDetailsModel model = Mapper.Map<ReptileDetailsModel>(reptile);
There are many ways to use the AutoMapper within MVC, but this is probably a start.
I didn't realize it at the time, but I was using:
public ActionResult Index()
{
using (var db = new ModelsContainer())
{
return View(db.Reptiles.ToList());
}
}
This was causing the database (and thus model property) to expire before the view was rendered, causing this error (adding for search engines):
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
Set the Species class as a model for your strongly typed Reptile View. Then display the Title property of it. Your action method should look like this:
public ActionResult Reptile(Reptile rep)
{
return View(db.Species.Where(x=>x.ID == rep.SpeciesID).Single());
}
this way you would only need to call database once in order to generate the view.
I have a view model like this:
public class EditVM
{
public Media.Domain.Entities.Movie Movie { get; set; }
public IEnumerable<Genre> Genres { get; set; }
}
Movie is the real entity I wish to edit. Genres is simply present to populate a drop down. I would prefer that when I call:
#Html.TextBoxFor(m => m.Movie.Title)
inside my strongly typed view that the input control have a name = "Title" instead of "Movie.Title"
I do not wish to split my view into partial views or lose my strongly typed view by using ViewData or the like.
Is there a way to express to the View that I do not wish to have the Movie. prefix? I noticed that you can set:
ViewData.TemplateInfo.HtmlFieldPrefix = "x";
in the controller, but unfortunately it seems only to allow adding an additional prefix. Setting it to "" does nothing.
Is there any work around for this? Or am I stuck with the unfortunate prefix that isn't really necessary in this case if I wish to keep strongly typed views and lambdas?
Thanks for any help.
Update:
Here's the controller actions to maybe make things a bit clearer.
public ActionResult Edit(int? id)
{
var vm = new EditVM
{
Movie = id.HasValue ? _movieSvc.Find(id.Value) : new Movie(),
Genres = AppData.ListGenres()
};
return View(vm);
}
[HttpPost]
public void Edit([Bind(Prefix = "Movie")]Movie m)
{
_movieSvc.AddOrUpdateMovie(m); //Exceptions handled elsewhere
}
No, in order to do what you want you would have to rewrite the Html helpers, and then you would have to write your own model binder. Seems like a lot of work for minimal gain.
The only choice is a Partial view in which you pass the Movie object as the model. However, this would require you to write your own model binder to have it be recognized.
The reason you have to do m.Movie.Title is so that the ID has the correct name, so the model binder can recognize it as a member of your model.
Based on your update:
Your options are:
Use non-strongly typed helpers.
Use a partial view.
Rewrite the stronly typed helpers
Don't use the helpers at all, and write the values to the HTML
Personally, i'd just use 1 or 2, probably 2.
EDIT:
Based on your update above. Change your code to this (note, Genres does not get posted back to the server, so m.Genres will just be null on postback):
[HttpPost]
public void Edit(EditVM m)
{
_movieSvc.AddOrUpdateMovie(m.Movie); //Exceptions handled elsewhere
}
EDIT:
I did just think of an alternative to this. You could simply do this:
#{ var Movie = Model.Movie; }
#Html.TextBoxFor(m => Movie.Title)
However, if there was a validation error, you would have to recreate your EditVM.
I have a view model like this
I think that you might have some misunderstanding about what a view model is. A view model shouldn't contain any reference to your domain models which is what those Movie and Genre classes seem to be. I mean creating a new class that you suffix with VM and in which you stuff all your domain models as properties is not really a view model. A view model is a class that is specifically designed to meet the requirements of your view.
A much more correct view model would looks like this:
public class EditVM
{
public string MovieTitle { get; set; }
public IEnumerable<GenreViewModel> Genres { get; set; }
}
and in your view you would have:
#Html.EditorFor(x => x.MovieTitle)
#Html.EditorFor(x => x.Genres)
Another option is to either use the TextBox(string name, object value) overload instead of the TextBoxFor:
#Html.TextBox("Title", Model.Movie.Title)
You could also specify the input tag HTML instead of using a helper.
Another option is to take EditVM as your postback parameter. This is what I would do. My post action parameter is always the same type of the .cshtml model. Yes there will be properties like lists that are null, but you just ignore those. It also allows you to gracefully handle post errors as well because if there is an error you'll need to return an instance of that view model anyhow, and have the values they submitted included. I usually have private methods or DB layer that handles retrieving the various lists that go into the ViewModel, since those will be empty on postback and will need to be repopulated, while not touching the properties that were in the post.
With your post method as it is now, if you need to return the same view, you've gotta create a new EditVM and then copy any posted values into it, and still populate the lists. With my method, you eliminate one of those mapping steps. If you are posting more than one thing, are you going to have umpteen different parameters on your post action? Just let them all come naturally into a single parameter typed to the EditVM of the View. While maybe having those null properties in the VM during the postback feels icky, you get a nice predictable consistency between View and postback IMO. You don't have to spend alot of time thinking about what combination of parameters on your post method will get you all the pieces of data from the form.
For those that create ViewModels (for use by typed views) in ASP.NET MVC, do you prefer to fetch the data from a service/repository from within the ViewModel, or the controller classes?
For example, we started by having ViewModels essentially being DTOs and allowing our Controllers to fetch the data (grossly oversimplified example assumes that the user can only change employee name):
public class EmployeeViewModel
{
public String Name; //posted back
public int Num; //posted back
public IEnumerable<Dependent> Dependents; //static
public IEnumerable<Spouse> Spouses; //static
}
public class EmployeeController()
{
...
public ActionResult Employee(int empNum)
{
Models.EmployeeViewModel model = new Models.EmployeeViewModel();
model.Name = _empSvc.FetchEmployee(empNum).Name;
model.Num = empNum;
model.Dependents = _peopleSvc.FetchDependentsForView(empNum);
model.Spouses = _peopleSvc.FetchDependentsForView(empNum);
return View(model);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Employee(Models.EmployeeViewModel model)
{
if (!_empSvc.ValidateAndSaveName(model.Num, model.Name))
{
model.Dependents = _peopleSvc.FetchDependentsForView(model.Num);
model.Spouses = _peopleSvc.FetchDependentsForView(model.Num);
return View(model);
}
this.RedirectToAction(c => c.Index());
}
}
This all seemed fine until we started creating large views (40+ fields) with many drop downs and such. Since the screens would have a GET and POST action (with POST returning a view if there was a validation error), we'd be duplicating code and making ViewModels larger than they probably should be.
I'm thinking the alternative would be to Fetch the data via the Service within the ViewModel. My concern is that we'd then have some data populated from the ViewModel and some from the Controller (e.g. in the example above, Name would be populated from the Controller since it is a posted value, while Dependents and Spouses would be populated via some type of GetStaticData() function in the ViewModel).
Thoughts?
I encountered the same issue. I started creating classes for each action when the code got too big for the action methods. Yes you will have some data retrieval in classes and some in the controller methods. The alternative is to have all the data retrieval in classes, but half the classes you won't really need, they will have been created for consistency sake or have all the data retrieval in the controller methods, but again, some of those methods will be too complex and needed to have been abstracted into classes... so pick your poison. I would rather have a little inconsistency and have the right solution for the job.
As for putting behavior into the ViewModel, I don't, the point of the ViewModel is to be a thin class for setting and extracting values from the View.
There have been cases where I've put conversion methods in the ViewModel. For instance I need to convert the ViewModel to the corresponding entity or I need to load the ViewModel with data from the Entity.
To answer your question, I prefer to retrieve data from with in the controller/action methods.
Typically with DropDowns, I create a dropdown service. DropDowns tend to be the same data that spans views. With the dropdowns in a service I can use them on other views and/or Cache them.
Depending on the layout, 40 plus fields could create a cluttered view. Depending the type of data, I would try to span that many fields across multiple views with some sort of tabbed or wizard interface.
There's more than that ;-) You can fetch in model binder or action filter. For the second option, check Jimmy Bogard's blog somewhere around here. I personally do it in model binders. I use ViewModel like this: My custom ASP.NET MVC entity binding: is it a good solution?. It is processed by my custom model binder:
public object BindModel(ControllerContext c, BindingContext b)
{
var id = b.ValueProvider[b.ModelName]; // don't remember exact syntax
var repository = ServiceLocator.GetInstance(GetRepositoryType(b.ModelType));
var obj = repository.Get(id);
if (obj == null)
b.ModelState.AddModelError(b.ModelName, "Not found in database");
return obj;
}
public ActionResult Action(EntityViewModel<Order> order)
{
if (!ModelState.IsValid)
...;
}
You can also see an example of model binder doing repository access in S#arp Architecture.
As for static data in view models, I'm still exploring approaches. For example, you can have your view models remember the entities instead of lists, and
public class MyViewModel
{
public MyViewModel(Order order, IEmployeesSvc _svc)
{
}
public IList<Employee> GetEmployeesList()
{
return _svc.GetEmployeesFor(order.Number);
}
}
You decide how you inject _svc into ViewModel, but it's basically the same as you do for controller. Just beware that ViewModel is also created by MVC via parameterless constructor, so you either use ServiceLocator or extend MVC for ViewModel creation - for example, inside your custom model binder. Or you can use Jimmy Bogard's approach with AutoMapper which does also support IoC containers.
The common approach here is that whenever I see repetative code, I look to eliminate it. 100 controller actions doing domain-viewmodel marshalling plus repository lookup is a bad case. Single model binder doing it in generic way is a good one.
I wouldn't be fetching data from the database in your ViewModel. The ViewModel exists to promote separation of concerns (between your View and your Model). Tangling up persistance logic in there kind of defeats the purpose.
Luckily, the ASP.NET MVC framework gives us more integration points, specifically the ModelBinder.
I've got an implementation of a generic ModelBinder pulling information from the service layer at:-
http://www.iaingalloway.com/going-further-a-generic-servicelayer-modelbinder
It doesn't use a ViewModel, but that's easily fixed. It's by no means the only implementation. For a real-world project, you're probably better off with a less generic, more customised solution.
If you're diligent, your GET methods don't even need to know that the service layer exists.
The solution probably looks something like:-
Controller action method:-
public ActionResult Details(MyTypeIndexViewModel model)
{
if( ModelState.IsValid )
{
return View(model);
}
else
{
// Handle the case where the ModelState is invalid
// usually because they've requested MyType/Details/x
// and there's no matching MyType in the repository
// e.g. return RedirectToAction("Index")
}
}
ModelBinder:-
public object BindModel
(
ControllerContext controllerContext,
BindingContext bindingContext
)
{
// Get the Primary Key from the requestValueProvider.
// e.g. bindingContext.ValueProvider["id"]
int id = ...;
// Get an instance of your service layer via your
// favourite dependancy injection framework.
// Or grab the controller's copy e.g.
// (controllerContext.Controller as MyController).Service
IMyTypeService service = ...;
MyType myType = service.GetMyTypeById(id)
if (myType == null)
{
// handle the case where the PK has no matching MyType in the repository
// e.g. bindingContext.ModelState.AddModelError(...)
}
MyTypeIndexViewModel model = new MyTypeIndexViewModel(myType);
// If you've got more repository calls to make
// (e.g. populating extra fields on the model)
// you can do that here.
return model;
}
ViewModel:-
public class MyTypeIndexViewModel
{
public MyTypeIndexViewModel(MyType source)
{
// Bind all the properties of the ViewModel in here, or better
// inherit from e.g. MyTypeViewModel, bind all the properties
// shared between views in there and chain up base(source)
}
}
Build your service layer, and register your ModelBinder as normal.
Here's another solution: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/06/29/how-we-do-mvc-view-models.aspx
Main points there:
Mapping is done by a mediator - in this case it is AutoMapper but it can be your own class (though more to code). This keeps both Domain and ViewModel concentrated on the domain/presentation logic. The mediator (mapper) will contain (mostly automatic) logic for mapping, including injected services.
Mapping is applied automatically, all you do is tell the action filter the source/destination types - very clean.
(Seems to be important for you) AutoMapper supports nested mappings/types, so you can have your ViewModel combined of several independent view models, so that your "screen DTO" is not messy.
Like in this model:
public class WholeViewModel
{
public Part1ViewModel ModelPart1 { get; set; }
public Part2ViewModel ModelPart2 { get; set; }
}
you re-use mappings for specific parts of your View, and you don't write any new line of code, since there're already mappings for the partial view models.
If you don't want AutoMapper, you have have IViewModelMapper interfaces, and then your IoC container will help your action filter to find appropriate
container.Resolve(typeof(IViewModelMapper<>).MakeGenericType(mysourcetype, mydesttype))
and it will also provide any required external services to that mapper (this is possible with AutoMapper, too). But of course AutoMapper can do recursions and anyway, why write additional AutoMapper ;-)
Consider passing your services into the custom ViewModel on its constructor (ala Dependency Injection). That removes the model population code from your controller and allows it to focus on controlling the logical flow of the application. Custom ViewModels are an ideal place to abstract the preparation of things like SelectLists that your droplists will depend on.
Lots of code in the controller for things like retrieving data isn't considered a best practice. The controller's primary responsibility is to "control" the flow of the application.
Submitting this one late... Bounty is almost over. But...
Another mapper to look at is Automapper: http://www.codeplex.com/AutoMapper
And overview on how to use it: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx
I really like it's syntax.
// place this somewhere in your globals, or base controller constructor
Mapper.CreateMap<Employee, EmployeeViewModel>();
Now, in your controller, I would use multiple viewmodels. This enforces DRY by allowing you to reuse those viewmodels elsewhere in your application. I would not bind them all to 1 viewmodel. I would refactor to something like:
public class EmployeeController()
{
private IEmployeeService _empSvc;
private ISpouseService _peopleSvc;
public EmployeeController(
IEmployeeService empSvc, ISpouseService peopleSvc)
{
// D.I. hard at work! Auto-wiring up our services. :)
_empSvc = empSvc;
_peopleSvc = peopleSvc;
// setup all ViewModels here that the controller would use
Mapper.CreateMap<Employee, EmployeeViewModel>();
Mapper.CreateMap<Spouse, SpouseViewModel>();
}
public ActionResult Employee(int empNum)
{
// really should have some validation here that reaches into the domain
//
var employeeViewModel =
Mapper.Map<Employee, EmployeeViewModel>(
_empSvc.FetchEmployee(empNum)
);
var spouseViewModel =
Mapper.Map<Spouses, SpousesViewModel>(
_peopleSvc.FetchSpouseByEmployeeID(empNum)
);
employeeViewModel.SpouseViewModel = spouseViewModel;
return View(employeeViewModel);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Employee(int id, FormCollection values)
{
try
{
// always post to an ID, which is the employeeID
var employee = _empSvc.FetchEmployee(id);
// and bind using the built-in UpdateModel helpers.
// this will throw an exception if someone is posting something
// they shouldn't be posting. :)
UpdateModel(employee);
// save employee here
this.RedirectToAction(c => c.Index());
}
catch
{
// check your domain model for any errors.
// check for any other type of exception.
// fail back to the employee screen
RedirectToAction(c => c.Employee(id));
}
}
}
I generally try to stay away from saving multiple entities on a controller action. Instead, I would refactor the employee domain object to have AddSpouse() and SaveSpouse() methods, that would take an object of Spouse. This concept is known as AggregateRoots, controlling all dependancies from the root - which is the Employee() object. But, that is just me.
I'm building an asp.net MVC 2 app.
I have a list view which lists items based on a parameter. In the database I have a parent and child table, so my list view lists all the child records for where the parent's id matches the value specified in the parameter.
This is my controller and model:
public ActionResult List(int ParentID)
{
return View(new Models.ChildListModel(ParentID));
}
public class ChildListModel
{
public int ParentID {get;set;}
public ManagementUserListModel(int iParentID)
{
this.ParentID = iParentID;
this.Children = DataAccessLayer.ListChildrenForParent(iParentID);
}
public List<Child> Children {get;set;}
}
I also have a details and create action for that controller. The details and create view have a "back to list" action, which I want to go back to the list view, and maintain the original ParentID. So far I've been doing this by creating a hidden field called ParentID in the list, edit, create and details views, so that the model's ParentID property will get populated correctly:
<%= Html.HiddenFor(model => model.ParentID) %>
Then in the "Back to List" action in each view I pass the ParentID:
<%=Html.ActionLink("Back to List", "List", new {ParentID = Model.ParentID}) %>
This all works, but I'm not a big fan of storing raw IDs in the html. Are there any better ways to do this? Is there some built in way to encrypt the data (kind of like the standard asp.net viewstate did?) I'm just trying to achieve some sort of tamper resistance, and trying to avoid using session state (TempData, etc) because I don't want to have to handle session timeouts.
You may take a look at this article. You could use the new Html.Serialize extension method in your view which allows you to serialize entire objects and encrypt it:
<%= Html.Serialize("person", Model, SerializationMode.Encrypted) %>
Which serializes the Model into a hidden field and encrypts the value. To get the model back you use the DeserializeAttribute in the controller action to which the form is submitted:
public ActionResult Edit([Deserialize]Person person) { }
Another way to do this that doesn't expose the id and also doesn't add to the weight of the subsequent form post (like Webform's ViewState does and Html.Serialize would) is to use the session as the backing store inside your Model class.
public class ChildListModel
{
public int ParentID {
get
{
return (int)HttpContext.Current.Session["ChildListModel.ParentID"];
}
set
{
HttpContext.Current.Session["ChildListModel.ParentID"] = value;
}
}
public ManagementUserListModel(int iParentID)
{
this.ParentID = iParentID;
this.Children = DataAccessLayer.ListChildrenForParent(iParentID);
}
public List<Child> Children {get;set;}
}
If you like, you could even store the entire Parent object in your model that way instead of just it's ID - that would add to the session size on the server which may or may not be desirable (depending on how long your session lasts and whether it is set to be stored in memory or in sql server, etc.)
The easiest way would be to keep the parentid in the URL. It will look a bit strange for the Create Action but I still think that this the less troublesome way.
If you keep state, you will alwas have the problem that you can not hit F5 and you can not bookmark the page.
The backlink is a simple ActionLink in this case.
Urls would be:
/YourController/List/YourParentParameterValue
/YourController/Detail/YourParentParameterValue/YourDetailParameterValue
/YourController/Create/YourParentParameterValue