Pass Model to bootstrap - c#

In my project I have Class A, which has collection of class B.
Something like this:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<B> B { get; set; }
}
public class B
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsValid { get; set; }
}
In my view I'm iterating through A elements and do a div's list.
Now, when I'm clicking some div, I want to pass B elements to modal and show some values there.
Something like:
#model IEnumerable<A>
#foreach (var a in A)
{
<div class="container" data-toggle="modal" data-target="#testModal" <!-- how to pass this model here and use it in Modal?--> >
<p>#a.Name</p>
</div>
}
Is this possible?

You can pass it as json like:
#foreach (var a in A)
{
<div class="container" data-toggle="modal" data-target="#testModal"
data-value='#JsonConvert.Serialize(a.B)'>
<p>#a.Name</p>
</div>
}
then you can just read the json like:
var model = JSON.parse($('.container').data('value'));

Related

IGrouping Model not binding

I have the following classes as my viewmodel. Due to requirements, I have to use IGrouping instead of a normal IList or IEnumerable. I have to group ExtraCostOption according to the Types.
I have attached image of my view that will explain the requirement.
My problem is that the FirstCostOption do not get populated at all but all the other values are populated.
Could anyone point me to a direction?
public class ExtraCostOption
{
public string Name { get; set; }
public string Description { get; set; }
public bool IsSelected { get; set; }
public OptionCategory Category { get; set; }
public string SourceId {get;set;}
}
public class Product
{
public string Id {get;set;}
public string Cost {get;set;}
public List<IGrouping<string, ExtraCostOption>> FirstCostOption { get; set; }
}
My razor code is :
<div class="sm card content">
#if (Model.FirstCostOption .Any())
{
foreach (var optGroup in Model.FirstCostOption)
{
<h5>#optGroup.Key</h5>
for (int i = 0; i < optGroup.ToList().Count; i++)
{
<div class="card">
#Html.HiddenFor(m => optGroup.ToList()[i].SourceId)
<label class="control checkbox">
#Html.CheckBoxFor(m => optGroup.ToList()[i].IsSelected)
<span class="control-indicator"></span>
<span>
#optGroup.ToList()[i].Name
</span>
</label>
</div>
}
}
}
</div>
My controller code looks like this:
[ValidateAntiForgeryToken]
[System.Web.Mvc.HttpPost]
public async Task<ActionResult> Process([FromBody]Product viewModel)

CRUD operations for multiple related records in different tables from single view in MVC C#

I was hoping I could get some assistance on this functionality I am trying to achieve. I have searched, but i do not seem to be able to translate the examples/tutorials I have found to make it work in my own code.
For brevity, let's say I have 2 Models with a one to many relationship between the two. Let's say it is parents and children. One parent can have zero or many children. Each child can only have 1 parent:
namespace MyApp.Models
{
public partial class Parent
{
[Key]
public int parent_id { get; set; }
public string parent_name { get; set; }
public string parent_address { get; set; }
public ICollection<Child> Child { get; set; }
}
public partial class Child { get; set; }
{
[Key]
public int child_id { get; set; }
public int parent_id { get; set; }
public string child_name { get; set; }
public string child_allergies { get; set; }
public virtual Parent parent { get; set; }
}
}
Entity created the tables in the database properly, assigning primary/foreign keys where they needed to be.
I put the common fields in a viewmodel to render them in my view:
using MyApp.Models;
namespace MyApp.ViewModels
{
public class ParentChildViewModel
{
public int parent_id { get; set; }
public string parent_name { get; set; }
public string parent_address { get; set; }
public string child_name { get; set; }
public string child_allergies { get; set; }
}
}
I have my view written as below:
#model MyApp.ViewModels.ParentChildViewModel
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(model => model.parent_name)
#Html.EditorFor(model => model.parent_name)
</div>
<div>
#Html.LabelFor(model => model.parent_address)
#Html.EditorFor(model => model.parent_address)
</div>
<table id="child_table">
#{ Html.RenderPartial("_children"); }
</table>
<div>
<button id="add">Add Child</button>
<button id="rem">Remove Child</button>
</div>
<div>
<input type="submit" value="Create" />
</div>
}
<script type="text/javascript">
$("#add").click(function () {
$.ajax({
url: "#Url.Action("BlankChRow")",
cache: false,
success: function (html) {
$("#child_table").append(html);
}
});
return false;
});
$("#rem").click(function () {
$("#child_table tbody tr:last")
.remove();
return false;
});
</script>
I created a partial view for the child so I can repeat those:
#model MyApp.ViewModels.ParentChildViewModel
#using (Html.BeginCollectionItem("children"))
{
<tr>
<td>
<div>
#Html.LabelFor(model => model.child_name)
#Html.EditorFor(model => model.child_name)
</div>
</td>
<td>
<div>
#Html.LabelFor(model => model.child_allergies)
#Html.EditorFor(model => model.child_allergies)
</div>
</td>
</tr>
}
Then, in my controller (this is where I am stuck):
private ApplicationDbContext db = new ApplicationDbContext();
public ActionResult BlankChRow()
{
return PartialView("_children");
}
public ActionResult Create()
{
return View(new ParentChildViewModel());
}
[HttpPost]
public ActionResult Create(ParentChildViewModel pcvm)
{
var parent = new Parent()
{
parent_id = pcvm.parent_id,
parent_name = pcvm.parent_name,
parent_address = pcvm.parent_address
};
var child = new Child()
{
parent_id = pcvm.parent_id,
child_name = pcvm.child_name,
child_allergies = pcvm.child_allergies
};
if (ModelState.IsValid)
{
db.Parent.Add(parent);
db.Child.Add(child);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(pcvm);
}
I have experimented a few different ways of doing this... but I am unable to get this working the way I would like. Ideally, the View can be brought up, and while they enter the Parent's data, they could add one or many children. The children that are added would each be their own record in the Child table/entity while having the appropriate parent_id.
Any help is appreciated.
I see that you want to generate a list of children for a given parent but your viewmodel doesn't look so.
using MyApp.Models;
namespace MyApp.ViewModels
{
public class ParentChildViewModel
{
public int parent_id { get; set; }
public string parent_name { get; set; }
public string parent_address { get; set; }
public string child_name { get; set; }
public string child_allergies { get; set; }
}
}
Try a viewmodel with a list of children.
Something like this :
using MyApp.Models;
namespace MyApp.ViewModels
{
public class ParentChildViewModel
{
public int parent_id { get; set; }
public string parent_name { get; set; }
public string parent_address { get; set; }
public IEnumerable<Child> children { get; set; }
}
}
I'm supposing here that you just want to iterate over the list. That's why I use the IEnumerable interface instead of the IList interface.
To add a child in the list, you could call the "Create" function of your controller and pass the parent_id of the new child.
In your controller you can therefore create a new child for a given parent in the database using your application context.
Once the database transaction done, you can create a new ParentChildViewModel and fulfill it with the corresponding children and return it to the view.

MVC 6 Modelbinding complex dictionaries

I'm trying to post a form containing a dictionary of complex types.
The dictionary looks like this:
public Dictionary<Question, List<QuestionAlternative>> Questions { get; set; }
One question can have many alternatives.
The question itself looks like this:
public class Question
{
public Guid Id { get; set; }
public string Text { get; set; }
public Guid TestId { get; set; }
public Test Test { get; set; }
public ICollection<QuestionAlternative> QuestionAlternatives { get; set; }
public Guid? QuestionTypeId { get; set; }
public QuestionType QuestionType { get; set; }
}
And then there's the QuestionAlternative:
public class QuestionAlternative
{
public Guid Id { get; set; }
public string Alternative { get; set; }
public Guid QuestionId { get; set; }
public Question Question { get; set; }
}
My form looks like this:
<form asp-controller="Answer" asp-action="AnswerMe" method="post">
#foreach (var questionPair in Model.Questions)
{
#Html.Hidden("Questions[" + questionIndex + "].Key.Id", questionPair.Key.Id)
<br />
foreach (var alternative in questionPair.Value)
{
<label for="#alternative.Id">#questionPair.Key.Text</label>
<textarea id="#alternative.Id" name="#("Questions["+ questionIndex + "].Value["+ index +"]")"></textarea>
index++;
}
}
questionIndex++;
}
<input type="submit" class="button" value="Answer" />
</form>
The ActionResult that i'm posting to looks like this:
[AllowAnonymous]
[HttpPost("AnswerMe")]
public IActionResult AnswerMe([FromBody]AnswerViewModel model)
{
foreach(var item in model.Questions) //throws exception
....
}
and my viewmodel:
public class AnswerViewModel
{
public ODELDAL.Entities.Test Test { get; set; }
public Dictionary<Question, List<QuestionAlternative>> Questions { get; set; }
public Guid SelectedRole { get; set; }
}
When I post my form the dictionary named Questions is null.
Is it possible to achieve this with the default modelbinder or do i need to build a custom one.
If so, how would that solution look like?
Thanks in advance
Since a Question already possesses a collection of QuestionAlternative why not use a simple collect (List, array....etc) in your view model...
public class AnswerViewModel {
public ODELDAL.Entities.Test Test { get; set; }
public Question[] Questions { get; set; }
public Guid SelectedRole { get; set; }
}
...and an updated view...
<form asp-controller="Answer" asp-action="AnswerMe" method="post">
#for(int i = 0; i < Model.Questions.Length; i++) {
#Html.HiddenFor(m => m.Questions[i].Id)
<br />
for(int j = 0; j < m.Questions[i].QuestionAlternatives.Count; j++) {
#Html.LabelFor(m => m.Questions[i].QuestionAlternatives[j].Alternative,m.Questions[i].QuestionAlternatives[j].Text)
#Html.TextAreaFor(m => m.Questions[i].QuestionAlternatives[j].Alternative)
}
}
<input type="submit" class="button" value="Answer" />
</form>
The model binder will have an easier task of reconstructing your model using this approach as it will be able to use the expressions to generate the ids for the html tags.
For example, the hidden input will look something like this when generated
<input data-val="true" id="Questions_0__Id" name="Questions[0].Id" value="{Some-Guid-Value_Here}" type="hidden">

Nested lookup in MVC and Entity Framework

I'm running into a problem with a project I'm working on. I'm relatively new to MVC so I'm hoping this is something simple!
I'm trying to perform an edit on an object, but the object in question has a couple of ICollections that it can't seem to bind to.
The class I'm trying to update is called 'Key', and the definition is:
public partial class Key
{
public Key()
{
this.KeyFields = new HashSet<KeyField>();
this.KeyServices = new HashSet<KeyService>();
}
public int ID { get; set; }
public System.Guid APIKey { get; set; }
public string SiteURL { get; set; }
public bool Active { get; set; }
public virtual ICollection<KeyField> KeyFields { get; set; }
public virtual ICollection<KeyService> KeyServices { get; set; }
}
The KeyField and KeyService classes are:
public partial class KeyField
{
public int ID { get; set; }
public int SiteKey { get; set; }
public int Field { get; set; }
public bool Active { get; set; }
public virtual Field Field1 { get; set; }
public virtual Key Key { get; set; }
}
public partial class KeyService
{
public int ID { get; set; }
public int SiteKey { get; set; }
public int Service { get; set; }
public bool Active { get; set; }
public virtual Key Key { get; set; }
public virtual Service Service1 { get; set; }
}
The related Service class is:
public partial class Service
{
public Service()
{
this.KeyServices = new HashSet<KeyService>();
}
public int ID { get; set; }
public string Name { get; set; }
public int Service_View { get; set; }
public virtual ICollection<KeyService> KeyServices { get; set; }
public virtual View View { get; set; }
[NotMapped]
public bool IsSelected { get; set; }
}
and the related Field class is:
public partial class Field
{
public Field()
{
this.KeyFields = new HashSet<KeyField>();
}
public int ID { get; set; }
public string Title { get; set; }
public int Field_View { get; set; }
public virtual View View { get; set; }
public virtual ICollection<KeyField> KeyFields { get; set; }
[NotMapped]
public bool IsSelected { get; set; }
}
The View is using a custom model called 'KeyDetailsModel'. It contains an instance of the 'Key' object and 2 IEnumerables. These are there so that I can output all the fields and services in the database into a list of checkboxes on the View. The fields and services that should be preselected on the View are ICollections in the Key class.
the definition is:
public class KeyDetailsModel
{
public Key Key { get; set; }
public IEnumerable<Field> Fields { get; set; }
public IEnumerable<Service> Services { get; set; }
}
The is the code in the controller that sets up the 'display' page:
public ViewResult KeyDetails(int id)
{
var fieldChannel = new Repo<Field>();
var serviceChannel = new Repo<Service>();
//gets the key information
var key = _keyChannel.GetById(id);
//gets all the fields in the database
var fields = fieldChannel.GetAll();
//gets all the services in the database
var services = serviceChannel.GetAll();
//gets the KeyFields for the key, and sets the IsSelected flag for the related Field
foreach (var f in fields)
{
var selectedField = (from sf in key.KeyFields
where sf.ID == f.ID && sf.SiteKey == id
select sf).FirstOrDefault();
if (selectedField != null)
{
f.IsSelected = true;
}
}
//gets the KeyServices for the key, and sets the IsSelected flag for the related Service
foreach (var f in services)
{
var selectedService = (from ss in key.KeyServices
where ss.ID == f.ID && ss.SiteKey == id
select ss).FirstOrDefault();
if (selectedService != null)
{
f.IsSelected = true;
}
}
//create the model
var KeyDetailsModel = new KeyDetailsModel
{
Fields = fields,
Key = key,
Services = services
};
return View(KeyDetailsModel);
}
This is the method in the controller that will perform the edit:
[HttpPost]
public ActionResult KeyDetails(KeyDetailsModel KeyDetailsModel)
{
if (KeyDetailsModel != null && ModelState.IsValid)
{
return View(KeyDetailsModel);
}
else
{
return View(KeyDetailsModel);
}
}
Finally, the cshtml page is:
#model EdinburghNapier.EAWebLayer.Admin.Models.KeyDetailsModel
#{
ViewBag.Title = "KeyDetails";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Key Details</h2>
#using (Html.BeginForm("KeyDetails", "Key", FormMethod.Post))
{
<fieldset>
<legend>Key</legend>
<div class="display-label">
#Html.DisplayNameFor(model => model.Key.APIKey)
</div>
<div class="display-field">
#Html.EditorFor(model => model.Key.APIKey)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.Key.SiteURL)
</div>
<div class="display-field">
#Html.EditorFor(model => model.Key.SiteURL)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.Key.Active)
</div>
<div class="display-field">
#Html.EditorFor(model => model.Key.Active)
</div>
<div class="display-field">
<ul>
#foreach (var f in Model.Fields)
{
<li>
<input type="checkbox"
name="Key.KeyFields" value="#f.ID" id="Field_#f.ID"
checked="#f.IsSelected"/>
<label for="#f.ID">#f.Title</label>
</li>
}
</ul>
</div>
<div class="display-field">
<ul>
#foreach (var f in Model.Services)
{
<li>
<input type="checkbox"
name="Key.KeyServices" value="#f.ID" id="Service_#f.ID"
checked="#f.IsSelected"/>
<label for="#f.ID">#f.Name</label>
</li>
}
</ul>
</div>
</fieldset>
#Html.HiddenFor(model => model.Key.ID)
<input type="submit" value="Save"/>
}
What is happening when I submit the form is that the 'KeyFields' and 'KeyServices' properties of the 'Key' class in the 'KeyDetailsModel' are always 0. The edit screen doesn not seem to be able to bind the check box lists to the properties.
One consequence of this is that the status of the ModelState is always false when I submit.
I've been bashing my head against a wall over this for a couple of days now, hope someone can help! I appreciate that I may have supplied too much code and too little explanation - please let me know if that's the case!
You are binding your checkboxes in incorrect way, you should read a bit more about it, you can check some article like this:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
Your code should be changed, so that each checkbox had an indexer in its name:
<div class="display-field">
<ul>
#for (var i = 0; i < Model.Fields.Count(); i++)
{
<li>
<input type="checkbox"
name="#Html.NameFor(x=>x.Fields[i])" value="#Model.Fields[i].ID" id="#Html.IdFor(x=>x.Fields[i])"
checked="#Model.Fields[i].IsSelected" />
<label for="#Html.NameFor(x=>x.Fields[i])">#Model.Fields[i].Title</label>
</li>
}
</ul>
</div>
<div class="display-field">
<ul>
#for (var i = 0; i < Model.Services.Count(); i++)
{
<li>
<input type="checkbox"
name="#Html.NameFor(x=>x.Services[i])" value="#Model.Services[i].ID" id="#Html.IdFor(x=>x.Services[i])"
checked="#Model.Services[i].IsSelected" />
<label for="#Html.NameFor(x=>x.Services[i])">#Model.Services[i].Name</label>
</li>
}
</ul>
</div>
You will also have to make your Services and Fields properties an array here:
public class KeyDetailsModel
{
public Key Key { get; set; }
public Field[] Fields { get; set; }
public Service[] Services { get; set; }
}
Alternately, you can just use #Html.CheckBoxFor(x=>x.Fields[i].IsSelected inside the cycle, stick to Mvc Helpers, they really make the difference.

Complex ViewModel - View cannot Automap values from model Advice Needed..

I have a complex View. It has data from 4 Models. The models are all static and work as expected. I have created a ViewModel to attempt to show just the data needed for this view. It is made up of Competitors and some complex Classes and Events they participate in.
I have made a complex ViewModel. When I walk through the Controller, I can see all three parts being constructed from the ViewModel. Its all there including data. When I try to map the values using Intellesense in the View, it has no way of knowing this data, or has no mapping from the complex ViewModel. Am I doing this right? I have tried several ways to map these values to the View. I think I need to initialize or map the values to the Models derived from, I just cannot figure out how.
Please advise on how to map these values, data elements to the view.
ViewModel:
Compeditor is an from an actual model direct to the DB
The rest of the data is gathered from multiple tables and passed to view from controller
namespace eManager.Web2.Models
{
public class CompDetailPlus
{
public CompDetailPlus()
{
this.Compeditor = new Compeditor();
}
public virtual Compeditor Compeditor { get; set; }
public virtual IEnumerable<InEventClass> InEventClass { get; set; }
public virtual IEnumerable<AllEventClasses> AllEventClasses { get; set; }
}
public class Compeditor
{
[Key]
public virtual int CompeditorId { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string MiddleInt { get; set; }
public virtual string StreetAddress { get; set; }
public virtual string City { get; set; }
public virtual string State { get; set; }
public virtual string PostalCode { get; set; }
public virtual string EmailAddress { get; set; }
public virtual string HomePhone { get; set; }
public virtual string CellPhone { get; set; }
public virtual double Height { get; set; }
public virtual double Weight { get; set; }
public virtual int Age { get; set; }
public virtual int Event_CompId { get; set; }
}
public class InEventClass
{
public virtual int EventClassID { get; set; }
public virtual string ClassName { get; set; }
public virtual bool IsSelected { get; set; }
}
//duplicate to simplify how the second list is pulled and then combined with first list
public class AllEventClasses
{
public virtual int EventClassID { get; set; }
public virtual string ClassName { get; set; }
public virtual bool IsSelected { get; set; }
}
}
Controller:
public ActionResult CompeditorDetail(int CompeditorId)
{
//Pull the Competitor detail for the ID passed in
var comp = _db.Compeditors.Single(c => c.CompeditorId == CompeditorId);
//Pull a list of Event-Classes the competitor is already signed up for on current event
var nlist = (from o in _db.Compeditors
join o2 in _db.Event_Class_Compeditors_s on o.CompeditorId equals CompeditorId
where o.CompeditorId.Equals(CompeditorId)
join o3 in _db.Event_Classes on o2.EventClassID equals o3.EventClassID
where o2.EventClassID.Equals(o3.EventClassID)
join o4 in _db.Class_Definitions on o3.ClassID equals o4.Class_Definition_ID
where o3.ClassID.Equals(o4.Class_Definition_ID)
select new InEventClass()
{
ClassName = o4.Class_Name,
EventClassID = o2.EventClassID,
IsSelected = true
}).ToList();
//pull a complete list of Event Classes that are avaiaible
var totallist = (from o in _db.Event_Classes
join o2 in _db.Event_Classes on o.ClassID equals o2.ClassID
where o.ClassID.Equals(o2.ClassID)
join o3 in _db.Class_Definitions on o2.ClassID equals o3.Class_Definition_ID
where o2.ClassID.Equals(o3.Class_Definition_ID)
join o4 in _db.Events on o.EventID equals o4.EventID
where o.EventID.Equals(o4.EventID)
where o4.CurrentEvent.Equals(true)
select new AllEventClasses()
{
ClassName = o3.Class_Name,
EventClassID = o2.EventClassID,
IsSelected = false
}).ToList();
var whatsleft = totallist.Where(eachtotalclass => !(nlist.Any(eachClassIHave => eachClassIHave.EventClassID == eachtotalclass.EventClassID))).ToList();
var model = new CompDetailPlus { AllEventClasses = whatsleft, Compeditor = comp, InEventClass = nlist };
return View(model);
}
View:
(Has to show the Competitor detail and a compound list of Event_Classes they are in)
In the view, I cannot see the values for any data.. all error on run and no good for display.
#model IEnumerable<eManager.Web2.Models.CompDetailPlus>
#{
ViewBag.Title = "Competitor's Detail";
}
<h2>#ViewBag.Title</h2>
<fieldset>
<legend>Compeditor</legend>
<table border="1" >
<tr>
<td>
<div class="display-field">
#Html.HiddenFor(model => model.Compeditor.CompeditorId)
</div>
<b>First Name</b>
<div class="display-field">
#Html.DisplayFor(model => model.Compeditor.FirstName)
</div>
</td>
<td>
<b>Last Name</b>
<div class="display-field">
#Html.DisplayFor(model => model.Compeditor.LastName)
</div>
</td>
#using (Html.BeginForm("CompeditorDetail", "Compeditor", FormMethod.Post))
{
foreach (var item in Model)
{
<input type="checkbox" name="MyID" value="#item.AllEventClasses.IsSelected"/> #item.InEventClass.ClassName <br />
<input type="hidden" name="CompeditorID" value="#item.InEventClass.CompeditorId" />
}
}
</td>
Your View accepts a model of IEnumerable eManager.Web2.Models.CompDetailPlus which would be fine, but your controller is sending a single eManager.Web2.Models.CompDetailPlus object.
Try changing this in your View
#model IEnumerable<eManager.Web2.Models.CompDetailPlus>
to this:
#model eManager.Web2.Models.CompDetailPlus
And change the bottom part of your view so that it's iterating through Enumerable compaosite items inside your model.

Categories