I have the following sample model:
public class FooModel
{
public List<Bar> myList { get; set; }
}
public class Bar
{
public string description { get; set; }
public int pos { get; set; }
public int myField { get; set; }
}
And, in my cshtml file I have this:
#foreach (var bar in Model.myList)
{
<div class="row">
<div class="col-2">PLACEHOLDER</div>
</div>
...
}
So basically, the field myField will be a match (when it has some value set) to a pos of another item. What I want, is to autogenerate a dropdown in PLACEHOLDER containing all the descriptions (and pos as value), of all Bar, with the one that matches selected.
I tried this approach (sample) but this shouldn't work, since I am in a foreach inside my Model
#Html.DropDownListFor(col=> col.myField, new SelectList(Model.myList.description, "Id", "Text"), "Choose... ")
Can I do this automatically or will I have to create the dropdown manually?
Thanks!
Related
I want to get value of Dictionary from View.
In the view, I have a main/first foreach loop that retrieve data from Model, and inside the main/first loop, I need to retrieve the ListAttribute value according to the first loop by Id.
**//Code in the View - First loop to retrieve data from Model**
#model IEnumerable<ABC.Web.Models.Room.Housing>
foreach (var item in Model.OrderBy(x => x.Id))
{
<div class="col-xs-18 col-sm-4">
<div class="thumbnail">
*...(remainer of the code)*
//Here to insert second loop to retrieve *ListAttribute*
}
//Code in the Model
namespace ABC.Web.Models.Room
{
public class Housing
{
public string[] FloorPlan { get; set; }
public Dictionary<string, ListAttribute> ListAttributes { get; set;}
public string Latitude { get; set; }
}
public partial class ListAttribute
{
public string name { get; set; }
public string icon { get; set; }
public string value { get; set; }
}
}
If item is an object of type Housing, then you can do this:
foreach (KeyValuePair<string, ListAttribute> listItem in item.ListAttributes)
{
// Then you will have...
// listItem.Key -> string
// listItem.Value -> ListAttribute
}
Source
I have a view model like so:
public class ListingPlanEditorViewModel
{
public ListingPlan Plan { get; set; }
public IEnumerable<Directory> SiteDirectories { get; set; }
}
One property is an object of type ListingPlan here:
public class ListingPlan
{
public int? ListingPlanID { get; set; }
public int DescriptionLinesCount { get; set; }
public List<Directory> Directories { get; set; }
}
The object Directory looks like this:
public class Directory
{
public int DirectoryID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
I have a controller that returns a ListingPlanEditorViewModel to the view:
public ActionResult ConfigurePlan(int? listingIdentifier)
{
ListingPlan plan = new ListingPlan()
{
DescriptionLinesCount = 10,
Directories = new List<Directory>()
{
new Directory()
{
DirectoryID = 3
},
new Directory()
{
DirectoryID = 4
}
}
};
ListingPlanEditorViewModel model = new ListingPlanEditorViewModel()
{
Plan = plan,//_listingRepository.GetListingPlan(listingIdentifier, null),
SiteDirectories = _database.GetDirectories()
};
return View(model);
}
I would like to create a multiselect box that will bind the selected values back to the Plan property in the ListingPlanEditorViewModel, setting the DirectoryID property for each selection. So after binding I should have a List of Directory objects. All with their DirectoryID's set.
I'm having some trouble doing this. I can create the multiselectbox with the correct select options in it, but I am unable to retrieve them in my post action which looks like this:
#using (Html.BeginForm("ConfigurePlan", "ListingPlan"))
{
<div class="form-body">
#Html.ListBoxFor(model => model.Plan.Directories, new MultiSelectList(Model.SiteDirectories, "DirectoryID", "Name"))
</div>
<button type="submit">submit</button>
}
You have to create an [] or List of IDs in the ViewModel that will store selected values.
public class ListingPlanEditorViewModel
{
public ListingPlan Plan { get; set; }
public IEnumerable<Directory> SiteDirectories { get; set; }
public int[] DirectoryIDs {get;set;}
}
The View will change according. The Directories selected will be stored in DirectoryIDs.
#using (Html.BeginForm("ConfigurePlan", "ListingPlan"))
{
<div class="form-body">
#Html.ListBoxFor(model => model.DirectoryIDs, new MultiSelectList(Model.SiteDirectories, "DirectoryID", "Name"))
</div>
<button type="submit">submit</button>
}
Now on POST Action you can query the database and get the Directories that was selected by user.
Note: You can't just get the full objects because the ListBoxFor will generate a <select multiple ... > ... </select> tag won't know how to bind to your object.
I'm trying to find the best correct way to do the following:
I have a ViewModel for a character editor called CharacterViewModel. This CharacterViewModel is populated with a Character object, a list of available ability scores a character can have, which are in another table.
I created an edit template for the drop down, and I'm trying to find a way to recuperate the list of edited abilities. I can't seem to get them back on the controller.
Here is the ViewModel code:
public class CharacterViewModel : DbContext
{
public Character Character { get; set; }
[UIHint("CharacterAbilityScores")]
public IEnumerable<CharacterAbilityScore> CharacterAbilityScores { get; set; }
public IEnumerable<SelectListItem> AbilityScoresSelectList { get; set; }
public IEnumerable<AbilityModifiersAndBonusSpellDTO> AbilityModifiersAndBonusSpellDTO { get; set; }
public CharacterViewModel()
: base("name=CharacterModels")
{
}
}
Here is the controller code for populating the ViewModel:
public async Task<ActionResult> Edit(int? id)
{
Character character = db.Characters.Find(id);
var model = new CharacterViewModel();
model.Character = character;
model.CharacterAbilityScores = character.CharacterAbilityScores;
// Creating the list of ability scores for the view
model.AbilityScoresSelectList = from amabs in db.AbilityModifiersAndBonusSpells
select new SelectListItem()
{
Value = amabs.score.ToString(),
Text = amabs.score.ToString()
};
return View(model);
}
The edit method signature in the controller (the CharacterAbilityScores property and the other complex ones are always empty on the return trip):
public async Task<ActionResult> Edit(CharacterViewModel characterViewModel)
Here is the related code in the edit view:
#model CampaignManager.Models.CharacterViewModel
#using (Html.BeginForm())
{
<div class="form-group">
#Html.EditorFor(model => model.CharacterAbilityScores, new { AbilityScoresSelectList = Model.AbilityScoresSelectList })
</div>
}
And finally, the EditorTemplate:
#model IEnumerable<CampaignManager.Entities.CharacterAbilityScore>
<table>
#foreach (var abilityScore in Model)
{
<tr>
<td>#abilityScore.Ability.Abbreviation</td>
<td>
#{
if (ViewData["AbilityScoresSelectList"] != null)
{
#Html.HiddenFor(z => abilityScore);
#Html.HiddenFor(z => abilityScore.AbilityId);
#Html.DropDownListFor(x => abilityScore.AbilityId, (IEnumerable<SelectListItem>)ViewData["AbilityScoresSelectList"], dropDownHTMLOptions);
}
}
</td>
<tr>
}
</table>
I've tried many different HiddenFor tricks, storing the whole collection, storing different id's... I'm a bit lost in there I'll admit. Maybe I'm doing this all wrong and I need another approach?
UPDATE
Here is the model for the CharacterAbilityScore entity:
public partial class CharacterAbilityScore
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CharacterId { get; set; }
[Key]
[Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AbilityId { get; set; }
public int AbilityScore { get; set; }
public virtual Ability Ability { get; set; }
public virtual AbilityModifiersAndBonusSpell AbilityModifiersAndBonusSpell { get; set; }
public virtual Character Character { get; set; }
}
EditorFor() is designed to wok with collection where the EditorTemplate is the type in the collection (in your case you have made the EditorTemplate's model a collection (not the type) and are then giving each element a duplicate id attribute (invalid html) and duplicate name attributes (which cant be bound to a collection).
Change the template (Views/Shared/EditorTemplates/CharacterAbilityScore.cshtml) to:
#model CampaignManager.Entities.CharacterAbilityScore
<tr>
<td>#Html.DisplatFor(m => m.Ability.Abbreviation)</td>
<td>#Html.DropDownListFor(m => m.AbilityId, (IEnumerable<SelectListItem>)ViewData["AbilityScoresSelectList"])</td>
</tr>
and in the main view
#model CampaignManager.Models.CharacterViewModel
#using (Html.BeginForm())
{
<table>
#Html.EditorFor(model => model.CharacterAbilityScores, new { AbilityScoresSelectList = Model.AbilityScoresSelectList })
</table>
}
Side notes:
You have not posted the model for CharacterAbilityScore so a have
assumed it contains properties Abbreviation (for display only) and
AbilityId (associated with the dropdown).
You can not use #Html.HiddenFor() on a complex object (the value
will be the .ToString() output of the object) and having
#Html.HiddenFor() for the same property as the dropdown (and
located before #Html.DropDownListFor()) means that you will bind
to the hidden input on post back (i.e. the original value, not the
selected value from the dropdown)
I also recommend your view models do not derive from DbContext.
The purpose of a view model is to define the properties you want to
display/edit in the view
Can't get all my ViewModel properties back after the postback (After user entered some values on HttpPost)
There are numerous questions here related to losing data or getting nulls after the postback
I tried some of them and played around on my case, I think the scenario is a bit different,
Using a PartialView or Editor Templates(except a list property), Always the returned result properties are null.
In partialView approach always all the properties are null, I think maybe I missed a piece.
In the "custom editor template approach for the type", I'll have just "EnteredNums" List returned. (Maybe because these are what the template have EditorFor for them, but what is the solution here if that's the case?)
Don't know weather if it's important here or not, the application also uses Unity. I don't think it be the problem here.
The HttpGet Passed model is the same as HttpPost : DataVm
The name of the action is also the same : ProcessEnteredData
=================== Controller and action
[HttpPost]
public ActionResult ProcessEnteredData(DataVm vm)
{
if (ModelState.IsValid)
{
foreach (NumType num in vm.EnteredNums)
{
int i1 = num.Score1;
int i2 = num.Score2;
string profTitle = vm.Profile.Title;
Repository.Context.EnteredNums.Add(num);
}
return RedirectToAction("ShowTable");
}
else
{
return View(vm);
}
}
==============
The Partial View of Custom Editor Template are similar :
#model xxxx.NumType
#Html.LabelFor(m => m.TheTitle)
#Html.TextBoxFor(m => m.Score1)
#Html.TextBoxFor(m => m.Score2)
#Html.HiddenFor(m => m.Profile)
// Profile or ProfileId - Just used to see could it bring the property back or not as a test
============
NumType Model
[Key]
public int NumTypeId { get; set; }
[ForeignKey("Profile")]
[Required]
public int ProfileId { get; set; }
public int Score1 { get; set; }
public int Score2 { get; set; }
public int BoxId { get; set; }
public Box Box { get; set; } // something not important here
public virtual Profile Profile { get; set; }
============
The ViewModel
public class DataVm
{
public Profile Profile { get; set; }
public string TheTitle { get; set; }
public List<NumType> EnteredNums { get; set; }
// In the Editor template approach it's the only item with data and others are null
public List<Box> Boxes { get; set; }
}
=========
View for PartialView approach :
#model xxxx.DataVm
#using (Html.BeginForm("ProcessEnteredData", "Profile", FormMethod.Post))
{
#Html.AntiForgeryToken()
Model.EnteredNums = new List<NumType>();
foreach(var box in Model.Boxes)
{
NumType num = new NumType();
num.Profile = Model.Profile;
num.Box = box;
int iCount = Model.EnteredNums.Count;
Model.EnteredNums.Add(num);
#Html.Partial("NumView", Model.EnteredNums[iCount]);
}
<input type="submit" value="Do Process" />
}
===================
View for Editor for approach :
// instead of #Html.Partial :
#Html.EditorFor(m => m.EnteredNums[iCount]);
I have a List of Recipes and each Recipe has a number of RecipeLines, I want to loop Each Recipe with it's corresponding Recipe Lines foreach Looping inside of the parent Recipe foreach.
Recipe Class
namespace XXX.Models
{
public class Recipe
{
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public string RecipeInstructions { get; set; }
public virtual List<RecipeLine> RecipeLines { get; set; }
}
}
RecipeLine Class
namespace XXX.Models
{
public class RecipeLine
{
public int RecipeLineID { get; set; }
public float Quantity { get; set; }
public int MeasurementID { get; set; }
public int RecipeID { get; set; }
public int IngredientID { get; set; }
public virtual Measurement Measurement { get; set; }
public virtual Recipe Recipe { get; set; }
public virtual Ingredient Ingredient { get; set; }
}
}
No need to list the Ingredient and Measurement Class, but they are structured fine.
Now let's look at the ViewModel
namespace XXX.ViewModels
{
public class RecipeLineViewModel
{
public IEnumerable<Recipe> Recipes { get; set; }
public IEnumerable<RecipeLine> RecipeLines { get; set; }
}
}
And the PartialsController
namespace XXX.Controllers
{
public class PartialsController : Controller
{
private XXXDb db = new XXXDb();
public ActionResult RecipeList()
{
RecipeLineViewModel viewModel;
viewModel = new RecipeLineViewModel();
viewModel.Recipes = db.Recipes.ToList();
viewModel.RecipeLines = db.RecipeLines.Include(r =>
r.Measurement).Include(r => r.Ingredient);
return PartialView("_RecipeList", viewModel);
}
}
}
Now the partial view is Views > Partials > __RecipeList.cshtml
RecipeList Partial View
#model XXX.ViewModels.RecipeLineViewModel
#foreach (Recipe recipe in Model.Recipes)
{
<div>#recipe.RecipeName</div>
<div>#recipe.RecipeInstructions</div>
foreach (RecipeLine recipeLines in Model.RecipeLines)
{
<div class="row">
<div class="large-12 columns">
#recipeLines.Quantity #recipeLines.Measurement.MeasurementEn
#recipeLines.Ingredient.IngredientNameEn
</div>
</div>
}
}
}
(I printed the RecipeID at the end of each recipeLine to show that each recipe is repeating out Recipe Lines for RecipeID = 1 and not their Lines related to their own RecipeID. I'm a newbie to this stuff, I'm assuming that somewhere I should be telling the Recipe Lines about the RecipeID they should be looping for????
Here is the result I'm getting:
In your Recipie model you have have a List<RecipieLine> property which one would assume is to maintain one or more RecipieLine model objects. In your RecipieLineViewModel you have two separate IEnumerable collections exposing your Recipie and RecipieLine objects.
In your actual view, you are iterating over your Recipie collection:
#foreach (Recipe recipe in Model.Recipes)
Looks good, however, the following line is your issue:
foreach (RecipeLine recipeLines in Model.RecipeLines)
This enumerates over the RecipieLine model objects stored in your RecipieLineViewModel RecipieLine collection, however, it currently has no way of knowing which Recipie you are currently enumerating in your outer foreach.
You either want to provide a where LINQ clause of some sort using the data available to you from the current Recipie object enumeration, or alter the inner foreach to use the current Recipie enumeration rather than the Model.
Update
For completeness, here is an example of how to use the current Recipe object enumeration to access its associated RecipieLines:
#forech (var recipie in Model.Recipies)
{
<div>#recipe.RecipeName</div>
<div>#recipe.RecipeInstructions</div>
foreach (var recipieLine in recipie.RecipieLines)
{
<div class="row">
<div class="large-12 columns">
// Do something with recipieLine here
</div>
</div>
}
}
The above would be my preferred method as it is more efficient that performing a LINQ query each enumeration, and removes the IEnumerable<RecipieLine> from your ViewModel which is effectively duplicate code.
Add a where Linq clause to foreach.
#foreach (Recipe recipe in Model.Recipes)
{
<div>#recipe.RecipeName</div>
<div>#recipe.RecipeInstructions</div>
foreach (RecipeLine recipeLines in Model.RecipeLines.Where(rl => rl.RecipeID == recipe.RecipeID))
{
Upd:
Also you can definently include RecipeLine stuff in Recipes and you won't need RecipeLine Collection at all! Just loop through recipe.RecipeLines
Something like this:
viewModel.Recipes = db.Recipes.Include("RecipeLine").Include("RecipeLine.Measurement").
.Include("RecipeLine.Ingredient");