When do I use View Models, Partials, Templates and handle child bindings with MVC 3 - asp.net-mvc-3

new to mvc3, i have a few questions, would appreciate if someone could answer/provide links:
When should I use View Models? Is it not recommended to use the domain? I find that my view models are copies of my domain objects, and don't see the value...
When should I use Partials? Is it only if the partial view will be reused?
When should I use Display Templates and Editor Templates? Can I use these without a view model?
How do I create an edit screen where the parent and list of child objects are both editable? i.e. a few fields at the top (parent) and a grid of fields below (like editable rows), particularly, how do i do the binding? automapper is not used.
Thanks!

When should I use View Models? Is it not recommended to use the domain? I find that my view models are copies of my domain objects, and don't see the value...
View models should always be used. You should not use your domain models in the view.
View models are not exact copies of the domain models. They always have some differences related to the specific requirements of the view. For example on one screen you would like to present some of the properties of your domain model and on other screen other properties. As a consequence to this you will also have different validation requirements as one property will be required on one screen and not required on other screen. So you will also have different data annotations on those view models.
When should I use Partials? Is it only if the partial view will be reused?
Not only if the view will be reused. Partials could be used to make your views more structured. Also if you are using AJAX, partials make it easier. You would send the AJAX request to a controller action which will return a partial view allowing you to update only portions of the DOM.
When should I use Display Templates and Editor Templates? Can I use these without a view model?
Always. You can use them with any strongly typed model, but you should always use a view models (see answer to previous question).
How do I create an edit screen where the parent and list of child objects are both editable? i.e. a few fields at the top (parent) and a grid of fields below (like editable rows), particularly, how do i do the binding? automapper is not used.
That's a pretty broad question but to answer it as always you start with defining your view models which will represent/contain the properties you would like to present on this screen for editing:
public class ChildViewModel
{
[Required]
public string Name { get; set; }
}
public class ParentViewModel
{
[Required]
public string Name { get; set; }
public IEnumerable<ChildViewModel> Children { get; set; }
}
then a controller:
public class HomeController: Controller
{
public ActionResult Index()
{
// TODO: Fetch an actual domain model from your repository,
// and map it to the view model (AutoMapper is a great tool for the job)
var model = new ParentViewModel
{
Name = "parent name",
Children = Enumerable.Range(1, 5).Select(x => new ChildViewModel
{
Name = "child " + x
})
};
return View(model);
}
[HttpPost]
public ActionResult Index(ParentViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// TODO: the model object here will contain the new values
// => user AutoMapper to map it back to a domain model
// and pass this domain model to the repository for processing
return RedirectToAction("Index");
}
}
and finally the view:
#model ParentViewModel
#using (Html.BeginForm())
{
<h2>Parent</h2>
<div>
#Html.LabelFor(x => x.Name)
#Html.EditorFor(x => x.Name)
#Html.ValidationMessageFor(x => x.Name)
</div>
<h2>Children</h2>
<table>
<thead>
<tr>
<th>Child name</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(x => x.Children)
</tbody>
</table>
<input type="submit" value="OK" />
}
and the last piece is the editor template for a child (~/Views/Home/EditorTemplates/ChildViewModel.cshtml):
#model ChildViewModel
<tr>
<td>
#Html.LabelFor(x => x.Name)
#Html.EditorFor(x => x.Name)
#Html.ValidationMessageFor(x => x.Name)
</td>
</tr>

Related

Print a value which is later known in the view in Razor

Is there in Razor a way to print some HTML on the page
while the value later known in the view?
For example: I would like to print the sum of an expensive calculation, and that sum should be before the items in the HTML. But with the example below, it will always print 0.
I would like to calculate the sum only once.
I would like to solve this in the view, not in a csharp helper or on the client side (css, javascript etc)
#{
var sum = 0;
}
<table>
#* I would like to print here the sum which is known after the foreach loop*#
<tr><td>total: #sum</td></tr>
#foreach (var item in items)
{
<tr>
#{
var x= item.expensiveCalculation();
sum+= x;
}
//print item with x
<td>#x</td>
</tr>
}
</table>
edit: It is very important that the expensiveCalculation() is only calculated once for each item!
Your model is not adapted to the requirements of the view. Full stop.
So when your model is not adapted to the requirements of your view you go ahead and define a view model so that all the expensive operations are done inside the controller.
So:
public class ItemViewModel
{
public decimal Price { get; set; }
public string Name { get; set; }
}
Now your view becomes strongly typed to the view model and there are no longer expensive operations there:
#model IEnumerable<ItemViewModel>
<table>
<tr>
<td>
total: #Model.Sum(item => item.Price)
</td>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#item.Name - #item.Price<td>
</tr>
}
</table>
and now inside your controller action prepare this view model and pass it to the view:
public class SomeAction()
{
IEnumerable<Item> items = ... go and fetch your domain models from wherever you are fetching them
// now let's build the view model
var model = new MyViewModel
{
Items = items.Select(x => new ItemViewModel
{
Name = x.Name,
Price = x.expensiveCalculation()
})
};
// and we are obviously passing the view model to the view, not the domain model
return View(model);
}
As you can see we are doing the expensive operations for each element inside the controller in order to bind it to the corresponding property of the view model (Price) but we are no longer paying this expensive operations price inside the view as we are simply summing over the view model pre-calculated properties.
And next time when you encounter a problem in ASP.NET MVC don't forget that view models are the solution to your problem.

MVC3 - Viewmodel with list of complex types

Apologies if this has been asked before; there are a million ways to phrase it so searching for an answer has proved difficult.
I have a viewmodel with the following properties:
public class AssignSoftwareLicenseViewModel
{
public int LicenseId { get; set; }
public ICollection<SelectableDeviceViewModel> Devices { get; set; }
}
A simplified version of SelectableDeviceViewModel would be this:
public class SelectableDeviceViewModel
{
public int DeviceInstanceId { get; set; }
public bool IsSelected { get; set; }
public string Name { get; set; }
}
In my View, I am attempting to display a list of editable checkboxes for the Devices property, inside an input form.
Currently, my View looks like this:
#using (Html.BeginForm())
{
#Html.HiddenFor(x => Model.LicenseId)
<table>
<tr>
<th>Name</th>
<th></th>
</tr>
#foreach (SelectableDeviceViewModel device in Model.Devices)
{
#Html.HiddenFor(x => device.DeviceInstanceId)
<tr>
<td>#Html.CheckBoxFor(x => device.IsSelected)</td>
<td>#device.Name</td>
</tr>
}
</table>
<input type="submit" value="Assign" />
}
The problem is, when the model gets posted back to the controller, Devices is null.
My assumption is that this is happening because even though I'm editing its contents, the Devices property is never explicitly included in the form. I tried including it with HiddenFor, but that just resulted in the model having an empty list instead of null.
Any idea what I'm doing wrong here?
My assumption is that this is happening because even though I'm
editing its contents, the Devices property is never explicitly
included in the form.
No, your assumption is wrong. The reason this doesn't get bound properly is because your input fields doesn't have correct names. For example they are called name="IsSelected" instead of name="Devices[0].IsSelected". Take a look at the correct wire format that needs to be used to bind to collections: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
But why this happens?
It happens because of the foreach loop that you used in your view. You used x => device.IsSelected as lambda expression for the checkbox but this doesn't take into account the Devices property at all (as you can see by looking at the generated source code of your web page).
So what should I do?
Personally I would recommend you using editor templates as they respect the navigational context of complex properties and generate correct input names. So get rid of the entire foreach loop in your view and replace it with a single line of code:
#Html.EditorFor(x => x.Devices)
and now define a custom editor template that will automatically be rendered by ASP.NET MVC for each element of the Devices collection. Warning: the location and name of this template are very important as this works by convention: ~/Views/Shared/EditorTemplates/SelectableDeviceViewModel.cshtml:
#model SelectableDeviceViewModel
#Html.HiddenFor(x => x.DeviceInstanceId)
<tr>
<td>#Html.CheckBoxFor(x => x.IsSelected)</td>
<td>#Html.DisplayFor(x => x.Name)</td>
</tr>
Another approach (which I don't recommend) is to change your current ICollection in your view model to an indexed collection (such as an IList<T> or an array T[]):
public class AssignSoftwareLicenseViewModel
{
public int LicenseId { get; set; }
public IList<SelectableDeviceViewModel> Devices { get; set; }
}
and then instead of the foreach use a for loop:
#for (var i = 0; i < Model.Devices.Count; i++)
{
#Html.HiddenFor(x => x.Devices[i].DeviceInstanceId)
<tr>
<td>#Html.CheckBoxFor(x => x.Devices[i].IsSelected)</td>
<td>#Html.DisplayFor(x => x.Devices[i].Name</td>
</tr>
}
EditorFor templates work and keep the code clean. You don't need the loops and the model is posted back correctly.
However, has anyone had problems with validation on complex viewmodels (nested EditorFor templates)? I'm using Kendo Validator and am running into all sorts of jquery errors.

How do I display a collection of editable entities in MVC3 and process them on postback to the Action?

I have a collection of entities, called Accounts. A single user will have multiple Accounts and can set percentages for each Account.
The UI will list all the Accounts, the editable field for each Account is the Percentage property. This will be represented by a textbox.
Once the user makes any adjustments, they will post the form back for processing.
I have used MVC in a few other projects and I understand model binding, but I am not sure how to proceed when dealing with a collection of models.
I am not looking for code or for someone to write out the complete answer for me (although, honestly I wouldn't complain....). I am looking for guidance in the direction to take.
Thanks in advance.
Two things:
1) You'll want to use a List/Collection of Account or List/Collection of AccountViewModel in your overarching page view model
So, you would have
public class MyPageViewModel
{
public Collection<Account> AccountList;
//AccountViewModel is what I'd use but for simplicity
//Other properties for view model
}
You now have a view model that contains a list of accounts.
2)
In your view you have two options.
a) Just create a for loop to spit out the HTML you want for each account (not especially pretty)
b) Create an editor template so you don't have to do anything special
Your page would then have this.
#Html.EditorFor(model => model.AccountList)
Here's how editor templates are set up.
This is from a project I'm working on right now. The take away here should be that you add an "EditorTemplates" folder under the view folder that matches what you're working on. By convention, MVC will look in this folder for a template when you use EditorFor. Now you just insert whatever HTML you want in that strongly typed template (which would take an Account or AccountViewModel object) and you'd be done. No need for a for loop in your view to spit out HTML for each account.
Really, that would be it at that point. Your post action would model bind to a type of MyPageViewModel which would have the updated data you wanted
Hope that gets you pointed in the right direction.
Here is an example of how to solve your task
First lets define a model:
public class Account
{
public int Id { get; set; }
public string Number { get; set; }
public decimal Percentage { get; set; }
}
public class UserEditModel
{
public ICollection<Account> Accounts { get; set; }
}
UserEditModel is a view model. It contains property Accounts declared as collection of Account.
I'll describe a table in a view to show accounts and edit their percentages:
<form action="Url.Action("Save")" method="POST">
<table>
<thead>
<tr>
<td>Number</td>
<td>Percentage</td>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Accounts.Count(); i ++)
{
<tr>
<td>
#Html.HiddenFor(m => m.Accounts[i].Id)
#Html.HiddenFor(m => m.Accounts[i].Number)
#Model.Accounts[i].Number
</td>
<td>
#Html.TextBoxFor(m => m.Accounts[i].Id)
</td>
</tr>
}
</tbody>
</table>
</form>
After submitting the form in Accounts property of the view model we'll have changed by user values of percentages.

Losing data in models and collections inside the ViewModel on postback

I am using a viewmodel that contains a few other models(entities) for each partial view.
I am rendering a partial view by passing the entity which is inside the ViewModel. My partial view has a few fields and some buttons. On click of button (which is inside my partial view) the form is being posted back with the data in a sub entity, whereas my viewmodel is always posted back as null...
I need the data to be present in my viewmodel on post back.
All views are strongly typed:
Code:
public class OrdersVM
{
public FiltersVM filterCriteria { get; set; }
public IEnumerable<MeterInventory> meters { get; set; }
public string assignTo { get; set; }
public IEnumerable<SelectListItem> AssigneeOptions { get; set; }
}
public partial class Meters
{
public int MTRNO { get; set; }
public string LOCName { get; set; }
}
public class FiltersVM
{
public string Center { get; set; }
public DateTime? DueDate { get; set; }
}
View Code
#model OrdersVM
#{
ViewBag.Title = "Orders";
}
#using (Html.BeginForm())
{
<div>
#Html.Partial("~/Views/Base/Filters.cshtml", Model.filterCriteria)
</div>
#foreach (var item in Model.meters)
{
<table>
<tr>
<td>
#Html.Encode(item.LOCNAME)
</td>
</tr>
</table>
}
}
Controller code
[HttpPost]
public ActionResult Index(OrdersVM orders, FiltersVM filters)
{
//orders is null
//filters has values
}
Thanks Olivehour. I am using the partial view "Filters.cshtml". and am rendering the same.
Below is the code for partial view :
#model ViewModels.FiltersVM <fieldset>
<legend>Order Assignment</legend>
<table id="tbl1" class="tableforcontrols">
<tr>
<td>
<div class="editor-label">
#Html.LabelFor(model => model.LDC)
</div>
</td>
<td>
<div class="editor-field">
<input type="submit" value="Search" id="btnSearch" name="button" />
</div>
</td>
<td>
<div class="editor-field">
<input type="submit" class="cancel" value="Reset" id="btnReset" name="button" />
</div>
</td>
</tr>
</table> </fieldset>
I tried with single argument "OrdersVM" (parent view model) but no luck.
[HttpPost]
public ActionResult Index(OrdersVM orders)
but if I pass the parent viewmodel to the partial view it was holding the data in OrdersVM.filterCriteria but not for properties (IEnumerable meters, string assignTo and Enumerable AssigneeOptions)
#Html.Partial("~/Views/Base/Filters.cshtml", Model)
I am new to MVC. Please let me know if any one finds the solution.
Thanks in advance.
It looks like you have a couple of problems here. One probable reason why the orders arg is null in your action method is because it doesn't look like you are rendering any input elements. You just have #Html.Encode(item.LOCNAME).
In order for the default model binder to construct an instance of OrdersVM and pass it to the action method, it needs to have input from the HTTP POST. You need something more like #Html.TextBoxFor(m => item.LOCNAME).
The second problem I think is that you have 2 arguments in the action method. Since the OrdersVM already has a FiltersVM property, you should just be able to have a single OrdersVM argument to the action method. During the HTTP POST, you can just access FiltersVM properties from OrdersVM.filterCriteria. This will lead to your 3rd challenge, though, since the meters property on OrdersVM is an IEnumerable collection.
To solve this, first have a couple reads of this article about model binding collections. It's old, but it still applies in MVC3. Read it and really wrap your head around it.
If you don't like using integers to index your collection fields, there is an HTML helper written by Steve Sanderson that allows you to index collection inputs using GUID's. We use this all the time, but it can be tricky -- mainly, you should always put the collection item in a partial view. For now, you might just be better off using integer-based indexing as outlined in the Haacked article.
It sounds like you are comming from Webforms. To transition to MVC you need to remove the thought of PostBack. This is concept that doesn't really exist on the web but Webforms faked it for us.
In MVC you usually start with a GET request like /edit/{someId}. From here you load the data for the viewmodel from the database and render the view. Now let's say that all data in the viewmodel is editable so each property have it's own input field. The user edits some data and saves the form. This issues a POST to the server.
Assume we have this POST method
[HttpPost]
public ActionResult Edit(MyViewModel model)
In this case you have all the data you need modelbinded because all data existed in the form.
You could do this and get the same view rendered because all data was databinded.
[HttpPost]
public ActionResult Edit(MyViewModel model){
return View(model);
}
Now let's pretend you have a dropdown in your form. Then you would have these two properties in your viewmodel.
public int CarId { get; set; }
public IEnumerable<SelectListItem> CarOptions {get; set; }
When you post the form this time the CarId will be populated in the ViewModel but not CarOptions because they are not a part of the form data. What you do if you would want to return the same view again is to reload the missing parts.
[HttpPost]
public ActionResult Edit(MyViewModel model){
model.CarOptions = LoadCarOptions();
return View(model);
}
It's certainly possible to modelbind that too if you put it in a hidden field. But it's easier and probably more effective to reload it from server/database again. This is the normal approach taken when working with MVC.

use Razor to fill dropdown with Linq2Sql data

I'm experimenting with ASP.NET MVC3 and want to simply populate a dropdown list with data I get from a LINQ2SQL class, like so
controller (I know, Linq doesn't belong in the controller)
var allUsers = (from u in _userDataContext.Users
select u).ToList();
ViewBag.allUsers = allUsers.ToList();
return View();
view:
<select id="drop_heroes">
#foreach (var u in ViewBag.allUsers)
{
<option value="#u.pk_userid">#u.email</option>
}
</select>
That works fine, but I would like to use Razor #Html.Dropdownlist to create the same dropdown list, but can't find any info to make this work with Linq data.
I know, Linq doesn't belong in the controller
Then why are you using it in a controller? Anyway, at least it's fine that you know it.
Here's an example. As always in an ASP.NET MVC application you start by defining a view model which will represent the data that you need in the view. So in your case you need to display a dropdown so you define a list of users and a selected user id:
public class MyViewModel
{
public string SelectedUserId { get; set; }
public IEnumerable<SelectListItem> Users { get; set; }
}
then you define a controller action which will populate this view model from your repository and handle it to the view:
public ActionResult Index()
{
var model = new MyViewModel
{
Users = _userDataContext.Users.ToList().Select(x => new SelectListItem
{
Value = x.pk_userid.ToString(),
Text = x.email
})
}
return View(model);
}
and finally you will have a view which will be strongly typed to your view model and use HTML helpers to generate the dropdownlist:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedUserId, Model.Users)
<button type="submit">OK</button>
}
Things to notice:
Usage of view models
Usage of a strongly typed view
Usage of strongly typed HTML helpers to generate markup such as form elements and input fields
Getting rid of weakly typed structures such as ViewBag
If you follow these simple rules you will see how much easier your life as an ASP.NET MVC developer will become.

Resources