What is a better way to display data from multiple view models - asp.net-mvc

I have two view models
public class DataImportViewModel
{
public WccrViewModel wccrVM { get; set; }
public string Status { get; set; }
public string ValidationResult { get; set; }
…
}
public class WccrViewModel
{
public double? WccrId { get; set; }
public string WccrDesc { get; set; }
}
and my view
#model List<Mass.ViewModels.DataImportViewModel>
#using (Html.BeginForm("Validation", "DataImport", FormMethod.Post))
{
<input id="ProcessReshop" type="submit" value="Reshop" />
for (int i = 0; i < 1; i++)
{
<div class="display-field">
#Html.DisplayFor(model=>model[i].wccrVM.WccrId)
</div>
<div class="display-field">
#Html.DisplayFor(model => model[i].wccrVM.WccrId)
</div>
}
A table (not shown) is populated with the DataImportViewModel. I want to have the WccrViewModel displayed on top of the Table. I want to be able to access the WccrViewModel without the for loop...but it shows as a good example. How can I use wccrVM.WccrId without the for loop? Thanks in advance...cheers

How about another viewmodel class that contains both a DataImportViewModel and a WccrViewModel?
Your viewmodel should be tailored to the view, and if the view calls for both those objects, why not create a view model with what it expects?

Related

View Not Returning Multiple Lists in Asp.net MVC [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 5 years ago.
i am new to web from desktop.
i have two models.
first
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public double Stock { get; set; }
public Category Category { get; set; }
public string ImagePath { get; set; }
}
and second is
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public bool isSelected { get; set; }
}
and a ViewModel for passing it to my view
public class ProductsVM
{
public IList<Item> items { get; set; }
public IList<Category> categories { get; set; }
}
and my controller action method looks like this
[HttpGet]
public ActionResult Products()
{
ViewBag.Message = "...Products...";
ProductsVM productsVm = new ProductsVM();
productsVm.items = db.Items.ToList();
productsVm.categories = db.Categories.ToList();
return View(productsVm);
}
[HttpPost]
public ActionResult Products(ProductsVM model)
{
ViewBag.Message = "...Categories...";
return View(model);
}
i have used it in my view like this
#using (Html.BeginForm())
{
<div class="row">
<div class="col-md-2">
#foreach (var it in Model.categories.ToList())
{
<div class="input-group">
#Html.CheckBoxFor(i => it.isSelected, new { Name = "ChkCategory", id = "ChkCategory"+it.Id, #class = "Categories" }) #it.Name
#Html.HiddenFor(i => it.Name)
</div>
}
</div>
#*Loading Items...*#
<div class="col-md-10">
#for (int i = 0; i < Model.items.Count() / 3; i++)
{
<div class="row">
#foreach (var item in Model.items.Skip(i * 3).Take(3))
{
<div class="col-md-4 col-sm-6 col-xs-12">
<img src="#Url.Content(item.ImagePath)" alt="#item.Description" class="thumbnail" />
</div>
}
</div>
}
</div>
</div>
<input type="submit" value="submit" />
}
#section scripts
{
<script src="~/Scripts/Products.js"></script>
}
i have tried all the ways to get my model back when i post the from to controller
i am posting the from on checkbox click event using ajax from my products.js file.
but in my controller action method it always show the ViewModel as null.
what should i do? am i doing something wrong.
Products.js
$(function () {
console.log('Inside js......');
$('.Categories').click(function (e) {
console.log(this.id, $("#" + this.id).is(":checked"));
$.ajax({
type: "POST",
url: "/Home/Products",
success: function () {
console.log("ajax successfull....");
},
error: function () {
console.log("ajax error....");
}
});
});
});
This is your ProductsVM:
public class ProductsVM
{
public IList<Item> items { get; set; }
public IList<Category> categories { get; set; }
}
This is your action method to which you are posting:
[HttpPost]
public ActionResult Products(ProductsVM model)
When you submit your form, it will take the values from your form's controls and using the names of the controls, it will post them to the Products action. In your view (form), you have a checkbox with the name chkCategory and a hidden input with the name Name. When you post your form, it will send chkCategory and its value, and the hidden item with the name Name. When it arrives on the server side, the MVC will look for an action method named Products in your controller. Then the default binder will try to look for chkCategory and Name properties to see if the action accepts them. It will not find it. Then it will try to see if it can create a ProductsVM and it cannot because ProductsVM has 2 properties: items and categories and they do not match what you are posting so it will just choose that action and pass it null.
You have many issues in your code and it is not playing nicely with the whole MVC framework. I suggest you read Understanding MVC Model Binding and try some simple examples to get a hang of it and then try what you are doing.

ASP .NET MVC4 Adding new items to view and model binding

I create a website for my wife. She's a teacher and she would like to have a possibility to create exercises for their students. The case is that she would like to create for instance the following exercise:
Exercise 1: Fill the sentence using a correct word:
My wife is 30 ............. old
I live in this city for 30 .........
I have the following model:
public class Exercise
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ExerciseType Type { get; set; }
public DifficulityLevel DifficulityLevel { get; set; }
public List<ExerciseItem> Items { get; set; }
public DateTime TimeOfCreation { get; set; }
public DateTime TimeOfModification { get; set; }
}
public class ExerciseItem
{
[Key]
public Guid Id { get; set; }
public string Content { get; set; }
public List<ExerciseItemOption> Options { get; set; }
public ExerciseItemOption CorrectSelection { get; set; }
}
I creates a View for my Exercise. I can fill in the basic properties like Name, Description, Difficulity Level and Type. Then I would like to create a button "Add exercise item". When clicked, a partial view (or something else) should be added dynamically where new ExerciseItem can be provided.
I've tried to following:
I've added a button
#Ajax.ActionLink("Add exercise item",
"AddExerciseItem",
"Exercise", new AjaxOptions() { HttpMethod="GET", InsertionMode = InsertionMode.InsertBefore, UpdateTargetId="ExerciseItems"})
and the appropriate div:
<div id="ExerciseItems"></div>
My action method looks as follows:
public ActionResult AddExerciseItem()
{
return PartialView("ExerciseItem", new ExerciseItem());
}
and the partial view:
#model ElangWeb.Models.ExerciseItem
<fieldset>
<legend>ExerciseItem</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.DisplayNameFor(model => model.Content)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Content, new { style = "width:200px" })
</div>
</fieldset>
It works fine. However when I click button for creating a whole exercise, I do not have ExerciseItem collection in my model:
public ActionResult Create(Exercise exercise)
{
using (PersistanceManager pm = new PersistanceManager())
{
exercise.Id = Guid.NewGuid();
exercise.TimeOfCreation = DateTime.Now;
exercise.TimeOfModification = DateTime.Now;
pm.ExcerciseRepository.Add(exercise);
}
return RedirectToAction("Index");
}
How should I change the code in order to bind my list of added ExerciseItem objects to my model Exercise?
Check out this article about model binding. You basically need to create special names for the exercise items so that they get bound correctly.
e.g. partial:
#model ElangWeb.Models.ExerciseItem
<fieldset>
<legend>ExerciseItem</legend>
<label>content</label>
<input type="hidden" name="ExcersiseItem.Index" value="SomeUniqueValueForThisItem" />
<input type="text" name="ExcersiseItem[SomeUniqueValueForThisItem].Name" value="#Model.Content" />
</fieldset>
You can also look at my answer to this question MVC3 Non-Sequential Indices and DefaultModelBinder. Thanks Yarx for finding it, I was actually trying to find it :)

Using Common Properties in Partial View

I am creating a partial view to display a record's audit stamps...i.e.
CreatedOn
CreatedBy
ModifiedOn
ModifiedBy
I currently have created a new sub-class in all my viewmodels with .AuditStamps that contains these properties, populate that in my controller and pass it to the partial.
Here is an example of one of my view Models. Note the AudiStamps sub-class
public class ItemViewModel
{
public Item Item { get; set; }
public AuditStampsViewModel AuditStamps { get; set; }
}
Here is my partial, suing this sub-class
#model OTIS.AppServ.Shared.AuditStampsViewModel
<hr />
<div class="twoColumn floatLeft">
<div class="editor-field">
<b>Created By:</b>
#Html.DisplayFor(model => model.CreatedByName) on #Html.DisplayFor(model => model.CreatedOn)
</div>
</div>
<div class="twoColumn floatLeft">
<div class="editor-field" style="text-align:right;">
<b>Modified By:</b>
#Html.DisplayFor(model => model.ModifiedByName) on #Html.DisplayFor(model => model.ModifiedOn)
</div>
</div>
But I was wondering since all my classes will have these common properties, do I really need to go through adding and populating this sub-class. The only reason I am, is because I couldn't figure out how to declare a generic model in the partial...I was hoping the partial would just work if I didn't declare the model, but it doesn't. I get an error:
An expression tree may not contain a dynamic operation
How can I achieve this? Seems like something an Interface would solve just using classes, but not sure if the same concept applies using Partials (i.e. stating a partial implements an IAuditStamps inteface???)
In the end, I set the model of the partial to be an interface, and then make sure my classes also implemented an interface like so:
Create the IAuditStamps interface
public interface IAuditStamps
{
int CreatedById { get; set; }
string CreatedByName { get; set; }
DateTime CreatedOn { get; set; }
int ModifiedById { get; set; }
string ModifiedByName { get; set; }
DateTime ModifiedOn { get; set; }
}
My Class that implements the interface
public partial class Item : IEntityId, ICompanyId, IAuditStamps
The ItemViewModel (which uses the above class Item)
public class ItemViewModel
{
public Item Item { get; set; }
}
The Partial
#model OTIS.domain.IAuditStamps
<hr />
<div class="twoColumn floatLeft">
<div class="editor-field">
<b>Created By:</b>
#Html.DisplayFor(model => model.CreatedByName) on #Html.DisplayFor(model => model.CreatedOn)
</div>
</div>
<div class="twoColumn floatLeft">
<div class="editor-field" style="text-align:right;">
<b>Modified By:</b>
#Html.DisplayFor(model => model.ModifiedByName) on #Html.DisplayFor(model => model.ModifiedOn)
</div>
</div>
Calling the partial:
#Html.Partial("_AuditStamps", Model.Item)
That's a prime candidate for a ViewModel base class that all your view models can inherit from:
public abstract class ViewModel
{
public DateTime CreatedOn { get; set; }
public string CreatedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public string ModifiedBy { get; set; }
}
public class ItemViewModel : ViewModel
{
public Item Item { get; set; }
}
Your partial could then have the ViewModel as its model type to bind to the relevant properties. Any view requiring that partial should have its model inheriting from the base ViewModel, so the properties will all be available for model binding.

How do I bind checkboxes to the List<int> property of a view model?

I've been reading the various posts on view models and check boxes, but my brain is starting to lock up and I need a little push in the right direction.
Here's my simplified view model. I have checkboxes that need to populate the lists with their values. I don't think this can happen automagically. I'm not sure how to bridge the gap between an array of string values and a List correctly. Suggestions?
public int AlertId { get; set; }
public List<int> UserChannelIds { get; set; }
public List<int> SharedChannelIds { get; set; }
public List<int> SelectedDays { get; set; }
Have your View Model like this to represent the CheckBox item
public class ChannelViewModel
{
public string Name { set;get;}
public int Id { set;get;}
public bool IsSelected { set;get;}
}
Now your main ViewModel will be like this
public class AlertViewModel
{
public int AlertId { get; set; }
public List<ChannelViewModel> UserChannelIds { get; set; }
//Other Properties also her
public AlertViewModel()
{
UserChannelIds=new List<ChannelViewModel>();
}
}
Now in your GET Action, you will fill the values of the ViewModel and sent it to the view.
public ActionResult AddAlert()
{
var vm = new ChannelViewModel();
//The below code is hardcoded for demo. you mat replace with DB data.
vm.UserChannelIds.Add(new ChannelViewModel{ Name = "Test1" , Id=1});
vm.UserChannelIds.Add(new ChannelViewModel{ Name = "Test2", Id=2 });
return View(vm);
}
Now Let's create an EditorTemplate. Go to Views/YourControllerName and Crete a Folder called "EditorTemplates" and Create a new View there with the same name as of the Property Name(ChannelViewModel.cshtml)
Add this code ro your new editor template.
#model ChannelViewModel
<p>
<b>#Model.Name</b> :
#Html.CheckBoxFor(x => x.IsSelected) <br />
#Html.HiddenFor(x=>x.Id)
</p>
Now in your Main View, Call your Editor template using the EditorFor Html Helper method.
#model AlertViewModel
<h2>AddTag</h2>
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(m => m.AlertId)
#Html.TextBoxFor(m => m.AlertId)
</div>
<div>
#Html.EditorFor(m=>m.UserChannelIds)
</div>
<input type="submit" value="Submit" />
}
Now when You Post the Form, Your Model will have the UserChannelIds Collection where the Selected Checkboxes will be having a True value for the IsSelected Property.
[HttpPost]
public ActionResult AddAlert(AlertViewModel model)
{
if(ModelState.IsValid)
{
//Check for model.UserChannelIds collection and Each items
// IsSelected property value.
//Save and Redirect(PRG pattern)
}
return View(model);
}
Part of My View Model:
public List<int> UserChannelIds { get; set; }
public List<int> SharedChannelIds { get; set; }
public List<int> Weekdays { get; set; }
public MyViewModel()
{
UserChannelIds = new List<int>();
SharedChannelIds = new List<int>();
Weekdays = new List<int>();
}
I used partial views to display my reusable checkboxes (I didn't know about editor templates at this point):
#using AlertsProcessor
#using WngAlertingPortal.Code
#model List<int>
#{
var sChannels = new List<uv_SharedChannels>();
Utility.LoadSharedChannels(sChannels);
}
<p><strong>Shared Channels:</strong></p>
<ul class="channel-list">
#{
foreach (var c in sChannels)
{
string chk = (Model.Contains(c.SharedChannelId)) ? "checked=\"checked\"" : "";
<li><input type="checkbox" name="SharedChannelIds" value="#c.SharedChannelId" #chk /> #c.Description (#c.Channel)</li>
}
}
All three checkbox partial views are similar to each other. The values of the checkboxes are integers, so by lining up my view model List names with the checkbox names, the binding works.
Because I am working in int values, I don't feel like I need the extra class to represent the checkboxes. Only checked checkboxes get sent, so I don't need to verify they are checked; I just want the sent values. By initializing the List in the constructor, I should be avoiding null exceptions.
Is this better, worse or just as good as the other solution? Is the other solution (involving an extra class) best practice?
The following articles were helpful to me:
http://forums.asp.net/t/1779915.aspx/1?Checkbox+in+MVC3
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Binding list with view model
This site handles it very nicely
https://www.exceptionnotfound.net/simple-checkboxlist-in-asp-net-mvc/
public class AddMovieVM
{
[DisplayName("Title: ")]
public string Title { get; set; }
public List<CheckBoxListItem> Genres { get; set; }
public AddMovieVM()
{
Genres = new List<CheckBoxListItem>();
}
}
public class MembershipViewData
{
public MembershipViewData()
{
GroupedRoles = new List<GroupedRoles>();
RolesToPurchase = new List<uint>();
}
public IList<GroupedRoles> GroupedRoles { get; set; }
public IList<uint> RolesToPurchase { get; set; }
}
//view
#model VCNRS.Web.MVC.Models.MembershipViewData
#{
ViewBag.Title = "MembershipViewData";
Layout = "~/Views/Shared/_Layout.cshtml";
int i = 0;
}
#using (Html.BeginForm("Membership", "Account", FormMethod.Post, new { id = "membershipForm" }))
{
<div class="dyndata" style="clear: left;">
<table width="100%" cellpadding="0" cellspacing="0" class="table-view list-view">
foreach (var kvp2 in Model.GroupedRoles)
{
string checkBoxId = "RolesToPurchase" + kvp2.RoleType;
<tr>
<td width="240px">
<label class="checkbox-label" for="#checkBoxId">
<input type="checkbox" class="checkbox" name="RolesToPurchase[#i]"
id="#checkBoxId" value="#kvp2.RoleType" />
#kvp2.Key
</label>
</td>
</tr>
i++;
}
<tr style="background-color: #ededed; height: 15px;">
<td colspan="5" style="text-align: right; vertical-align: bottom;">
#Html.SubmitButton(Resources.MyStrings.Views_Account_Next)
</td>
</tr>
</table>
</div>
}
//Post Action
[HttpPost]
public ActionResult Membership(MembershipViewData viewData)
{
..........................
}
}

How to pass an entire ViewModel back to the controller

I have a ViewModel that contains two objects:
public class LookUpViewModel
{
public Searchable Searchable { get; set; }
public AddToSearchable AddToSearchable { get; set; }
}
The two contained models look something like this:
public class Searchable
{
[Key]
public int SearchableId { get; set; }
public virtual ICollection<AddToSearchable> AddedData { get; set; }
}
public class AddToSearchable
{
[Key]
public int AddToSearchableId { get; set;}
[Required]
public int SearchableId { get; set; }
[Required]
public String Data { get; set; }
[Required]
public virtual Searchable Searchable { get; set; }
}
I have a view that uses my LookUpViewModel and receives input to search for a SearchableId. If the Searchable object is found, a LookUpViewModel object is created and passed to the View. The view then displays editor fields for AddToSearchable.Data. Once submitted, I want the LookUpViewModel to be passed to an action method to handle all the back-end code. The only problem is, the LookUpViewModel passed to my action method contains a null reference to Searchable and a valid reference to AddToSearchable.. i.e. I'm missing half of my data.
Here's an example of what my view looks like:
#model HearingAidTrackingSystem.ViewModels.LookUpViewModel
#using (Html.BeginForm("LookUp", "Controller", "idStr", FormMethod.Post))
{
<input type="text" name="idStr" id="idStr"/>
<input type="submit" value="Search" />
}
#if (Model.Searchable != null && Model.AddToSearchable != null)
{
using (Html.BeginForm("AddMyStuff", "Controller"))
{
Html.HiddenFor(model => model.Searchable.SearchableId);
Html.HiddenFor(model => model.Searchable.AddedData);
Html.HiddenFor(model => model.AddToSearchable.AddToSearchableId);
Html.HiddenFor(model => model.AddToSearchable.SearchableId);
Html.HiddenFor(model => model.AddToSearchable.Searchable);
<div class="editor-field">
#Html.EditorFor(model => model.AddToSearchable.Data)
#Html.ValidationMessageFor(model => model.AddToSearchable.Data);
</div>
<input type="submit" value="Submit" />
}
}
and here are my action methods:
public ActionResult LookUp(LookUpViewModel vm)
{
return View(vm);
}
[HttpPost]
public ActionResult LookUp(string idStr)
{
int id = /*code to parse string to int goes here*/;
Searchable searchable = dal.GetById(id);
LookUpViewModel vm = new LookUpViewModel { Searchable = searchable,
AddToSearchable = new AddToSearchable() };
//When breakpoint is set, vm contains valid references
return View(vm);
}
[HttpPost]
public ActionResult AddMyStuff(LookUpViewModel vm)
{
//**Problem lies here**
//Do backend stuff
}
Sorry for the lengthy post. I tried my best to keep it simple. Any suggestions you may have.. fire away.
Two methods to fix it:
You can add to do HiddenFor() for all properties of Model.Searchable.
You can use serialization to transfer your Model.Searchable into text presentation and repair it from serialized form in controller.
Update: The problem is: You need to use #Html.HiddenFor(), not Html.HiddenFor();.

Resources