I am creating a .NET MVC application and I have a view in which I want to display the following:
Category
followed by a list of items for the current Category
[possibly] followed by a list of sub items for the current item
I could create a custom ViewModel class that sends 3 separate IEnumerable lists (categories, items, and sub items). The problem with this approach is there is no easy way to render the data in the proper order (hierarchy) in the view. With this approach I would have three lists, and then need to add conditional logic during rendering. This would mean that each item list would be iterated multiple times as I try to slot the items into the proper positions on the page.
I would rather pass back the data in the proper order, perhaps as an XML file. What is the proper approach here?
I have also contemplated concatenating the data in the controller and passing the formatted text to the view, but this seems to violate the notion of having the view handle rendering.
I would stay away from XML here. Pass the proper hierarchy; that seems reasonable:
public class ViewModel
{
Category[] Categories { get; set; }
}
public class Category
{
Item[] Items { get; set; }
}
public class Item
{
Item[] SubItems { get; set; }
}
then you can have nested foreach loops inside your view.
What you should probably do is have a Custom Type with your list of Categories, and each item has a property where the sub-items are contained.
This can be easily achieved in Entity Framework, using Navigation Properites.
I would set up the Category Model class with an items property of type IEnumerable<item>.
Then include an IEnumerable<subItem> subItems property on the item class.
The view would iterate through these items and sub items very easily.
Edit: actually if sub items are exactly the same as items, then there could just be a single item class with an IEnumerable<item> subItems property. This would also allow for unlimited levels of sub items.
Related
I have a table with a dropdown list in each row, like this:
#Html.DropDownListFor(m => m.Transactions[i].CategoryID, ...)
and everything mostly works. I can select items, submit the form, and the model has my updated selections. So far so good.
The problem is that this isn't very reliable. Since the name of each dropdown is based on an index rather than an ID that means the match-up between the post values and the actual items living in the database are based on indices. Most of the time that works fine, but what if the list of items changes between the time the page loads and the time a user does a postback? The indices have changed, which means the post data won't match up correctly, and bad things happen. OR I've seen the browser incorrectly try to preserve selections in dropdowns between posts, but because the list of items is changing (what may be item #2 now may be item #3 by the time the page is refreshed) and everything is based on indices, the wrong dropdowns get the wrong values.
So basically, how can I force the dropdowns to generate a name and ID that looks more like this:
Transactions_CategoryID_12385652 // 12385652 is the CategoryID
rather than this:
Transactions_4_CategoryID // 4 is the array index
and still have the benefits of automatic binding?
Edit: The second issue I mentioned (input values being restored incorrectly after a refresh) seems to only happen with Firefox. https://bugzilla.mozilla.org/show_bug.cgi?id=46845
You'd have to write your own custom model binder as well as a Html extension to generate the element names in this way, or you could generate the markup manually using Razor.
Is there a particular reason you want need to do it this way? You're almost always following the conventions of the framework unless there's a good reason not to.
You can pass the exact collection you want to the view and bind that to the DropDownFor html helper.
Say you have a Person.
public class Person
{
public int Id { get; set; }
public int Name { get; set; }
}
And you want to add a new person. Create a view model. Create a property in this view model of type SelectList. This select list will hold the collection of the model you want to populate the dropdown list with.
public class PersonViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public SelectList MySelectList { get; set; }
}
Let's say you want the drop down list to hold the Person's Id as the selected value and the Person's name as the text value. You may want to create a second view model to represent that or use an anonymous object. Let's call the collection myCollection. And let's say that the collection is made up of objects that have two properties (and Id and Name property). Now simply pass this view model with a value for MySelectList.
var viewModel = new MyViewModel
{
MySelectList = new SelectList(myCollection, "Id", "Name")
};
In your razor view you can set up the #Html.DropDownListFor like so:
#Html.DropDownListFor(m => m.Id, Model.MySelectList)
In order to pass a select value to the view for the dropdownlist simply use the SelectList constructor overload that allows this value to be passed:
new SelectList(myCollection, "Id", "Name", selectedValue)
I have various data models -- let's called them Account, User, and Order. (Ultimately there are many more.)
I have views that will display a list of Account objects, User objects, or Order objects. Each of the views allows for filtering of these objects based on search criteria (each criteria being unique to the object).
What is the best way to implement paging using just a page's view model so that every action model doesn't have to contain some sort of page parameter? My goal is to get paging info to/from the page view a property on a page's view model.
Note: here's how I've started to approach this:
Generic view model:
public ListViewModel<T> {
public List<T> Models {get;set;}
public PageRange PageRange {get;set;}
}
Each view has a view model that may look something like this:
public AccountListViewModel : ListViewModel<Account> { }
Each view model contains elements helpful to the view (obviously) including criteria that might be passed back to the controller for filtering. So the list action becomes:
public ActionResult List(AccountListViewModel viewModel) {
var accounts = /* retrieve data based on properties of view model */
var trimmedAccounts = /* skip and take elements from accounts object based on viewModel.PageRange */
accounts.Models = trimmedAccounts
return View(viewModel);
}
This works except that the query string must contain "PageRange.PageStart=#" (where # is an integer) for it to bind properly. I'd like to just have MVC invoke binding of PageRange if it detects "start=" in the query string and wire it up to the view model.
Most of the samples I see online are showing CRUD with single entity class.
If I were to create a CRM application and one of the views needs to display customer information in read only mode and on the same page, display Contacts, Notes, Attachments, Addresses, how should the controller pass these different entities to the view?
Should I be creating another model class which will be a container for the various other entities and populate that model class with the various entities and pass back to the View.
In another scenario, say I wanted to display the Customer entity in Edit mode and the View has dropdowns for Customer Active Status, Customer StateCode, Customer Satisfaction dropdowns. Each of these dropdowns have other entity collections that are bound to them. So, again in this scenario, how should a controller pass back the model which has all these entities and not just Customer entity.
I continue to read on ViewModel pattern and I think this may be the way to go but would certainly appreciate more guidance and pointers.
You're exactly right. Create a ViewModel which represents the objects which your page requires.
Your first scenario could be something like the following:
public class CustomerInformationViewModel
{
IEnumerable<Contact> Contacts { get; set; }
IEnumerable<Note> Notes { get; set; }
}
Populate these in your controller and then access them in your view.
#foreach (var contact in Model.Contacts)
{
#Html.DisplayFor(c => c.Name)
}
For your second scenario, exactly the same. You want to bind to your customer properties but you also want some additional collections for drop down lists.
public class CustomerEditViewModel
{
Customer Customer { get; set; }
IEnumerable<Status> StatusOptions { get; set; }
IEnumerable<StateCode> StateCodeOptions { get; set ; }
}
Just keep in mind that you will need to re-populate these collections in your controller if you discover that your ModelState is invalid at the controller.
Here's my situation: I've got a number of specialized object types in my application, and I'm following the standard convention for displaying them with custom templates in the /Shared/DisplayTemplates folder, and editing them with templates in /Shared/EditorTemplates. But I also want to be able to display a custom filter template for each type, so I'd like to add a /Shared/FilterTemplates folder, and implement my own #Html.FilterFor method, so that showing a Filter template is exactly like showing a Display or Editor template.
Does this seem like the best way to handle this situation, or is there a more correct/elegant way to do this in MVC? Thanks in advance.
I'm always using EditorTemplates when data is sent back to server. I assume the user can submit the filter to the server to perform the actual filtering.
When creating filters I prefer to create a model for the filter like:
public class UserListFilterModel
{
public string Username { get; set; }
public bool IsEnabled { get; set; }
}
The view for UserListFilterModel goes into EditorTemplates/UserListFilterModel.ascx.
And then add it as a property on my view model for the page.
public class MyPageViewModel
{
public UserListFilterModel Filter { get; set; }
}
Then I add the filter model to the model for the page and displays it like this:
<%= Html.EditorFor(x => x.Filter)%>
You are probably wrapping the filter in a form to allow the user to submit the values so I think it belongs in EditorTemplates. The users is in fact editing the filter model.
(If you really want to separate them ing you could use the UIHintAttribute but I wouldn't)
Edit: I added some sample code.
I think you misunderstand how Templates work. Templates do not make sense in the context you are describing.
Templates work on a SINGLE data item (although that data item can contain multiple data items, which in turn have their own templates).
The concept of a Filter is to control multiple data items, thus they do not map well to a template.
What you could do is create a DisplayTemplate for your collection class that adds filtering, thus no need to create a custom type of template. Just use DisplayTemplates.
I have one view page (MyView.aspx) and many data sources to bind on this page.
Lets say it has Books, Publishers, Comments, etc.
Each one of those has an object which provides a List, List, etc.
In my view, what are my optiosn for multilple model biding?
I want to check to see if each one is empty, and then enumerate it. But I can't check Model.Count() because wouldn't Model be made of all those objects if I set the page to inheriet from ?
What are my options? Should I load each content area in a control/partial view?
Or can I just dump each object into ViewData and then check the count by casting in the view?
Thanks so much for taking a look at my problem.
Have you considered using a ViewModel that contains all Lists of all of your different data fields and using that to populate your View?
Example:
ViewModel:
public class MyViewModel
{
List<Book> Books {get; set;}
List<Publisher> Publishers {get; set;}
List<Comment> Comments {get; set;}
//Other fields...
//Constructors...
}
Then in your View you could simply check if a specific field was null prior to enumerating through it:
View:
if(Model.Books.Count() != 0)
{
//Enumerate through results here
}
Rionmonster's is probably the best solution - another is to use strongly typed partial views. You could load everything into the view data and then inject each segment into the respective partial view.