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'));
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)
Let's say I have a view-models like this:
public class AbonentViewModel
{
public int? Id { get; set; }
public string ClientName { get; set; }
public List<AbonentCertificate> Certificates { get; set; }
}
public class AbonentCertificate
{
public string Name { get; set; }
public string FileName { get; set; }
}
I want to write a tag helper which could show list of Certificates using custom html like this:
<multi-list asp-for="Certificates">
<div class="list-item">
<input asp-for="Name" />
<input asp-for="FileName" />
</div>
</multi-list>
Html inside th multi-list element would be repeated for each certificate object with appropriate values set.
Is there any way to do this?
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.
I am starting my first ASP.NET MVC project, so I have one simple question.
I have following code:
foreach(var question in Model.GeneralQuestions)
{
<div class = "well">
<h3>
<strong>#question.QuestionString</strong>
</h3>
#foreach (var answer in question.PossibleAnswers)
{
#Html.RadioButtonFor(model => question.QuestionString, answer.Answer)
#Html.Label(answer.Answer)
<br />
}
</div>
}
All questions in Model.GeneralQuestions are unique, so radio buttons should be divided into groups by name attribute (for each question one group of radio buttons). But this code produces only one group, so when I answer second question first one becomes deselected.
What do I need to change?
EDIT
My model looks like:
public class StudentViewModel
{
public Student Student { get; set; }
public List<Question> GeneralQuestions { get; set; }
public List<SubjectQuestions> SubjectQuestions { get; set; }
}
public class Student
{
public int StudentID { get; set; }
public string Index { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public virtual ICollection<Subject> Subjects { get; set; }
}
public class Question
{
public int QuestionID { get; set; }
public string QuestionString { get; set; }
public bool IsAssociatedWithSubject { get; set; }
public virtual ICollection<PossibleAnswer> PossibleAnswers { get; set; }
public virtual ICollection<Results> Results { get; set; }
}
public class SubjectQuestions
{
public Subject Subject { get; set; }
public List<Question> Questions { get; set; }
}
public class Results
{
public int ResultsID { get; set; }
public int QuestionID { get; set; }
public int? SubjectID { get; set; }
public int PossibleAnswerID { get; set; }
public virtual Question Question { get; set; }
public virtual PossibleAnswer PossibleAnswer { get; set; }
public virtual Subject Subject { get; set; }
}
In one instance of StudentViewModel I save one student and all questions that he should answer (both general and related to subjects he is studying) and pass it to view. In view I put all questions in single form and they are all type of radio. So, can anyone help me with grouping of radio buttons and posting back this form correctly?
There are a number of problems with your code including generating duplicate id's (invalid html), generating duplicate name attributes (which is why you're creating only one group, but more importantly this will prevent you from binding to the model when you post back) and you're not actually binding to a valid property anyway.
You will need to create view models to represent what you want to display and edit and generate the radio buttons in a for loop (or using an EditorTemplate) so they are correctly named with indexers.
View models
public class QuestionVM
{
public int ID { get; set; } // for binding
public string Text { get; set; }
[Required]
public int? SelectedAnswer { get; set; } // for binding
public IEnumerable<AnswerVM> PossibleAnswers { get; set; }
}
public class SubjectVM
{
public int? ID { get; set; }
[DisplayFormat(NullDisplayText = "General")]
public string Name { get; set; }
public List<QuestionVM> Questions { get; set; }
}
public class AnswerVM
{
public int ID { get; set; }
public string Text { get; set; }
}
public class StudentVM
{
public int ID { get; set; }
public string Name { get; set; }
// plus any other properties of student that you want to display in the view
public List<SubjectVM> Subjects { get; set; }
}
View
#model YourAssembly.StudentVM
#using(Html.BeginForm())
{
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.Name)
for(int i = 0; i < Model.Subjects.Count; i++)
{
#Html.HiddenFor(m => m.Subjects[i].ID)
#Html.DisplayFor(m => m.Subjects[i].Name) // will display "General" if no name
for (int j = 0; j < Model.Subjects[i].Questions.Count; j++)
{
#Html.HiddenFor(m => m.Subjects[i].Questions[j].ID)
#Html.DisplayFor(m => m.Subjects[i].Questions[j].Text)
foreach(var answer in Model.Subjects[i].Questions[j].PossibleAnswers )
{
<div>
#Html.RadioButtonFor(m => m.Subjects[i].Questions[j].SelectedAnswer, answer.ID, new { id = answer.ID})
<label for="#answer.ID">#answer.Text</label>
</div>
}
#Html.ValidationMessageFor(m => m.Subjects[i].Questions[j].SelectedAnswer)
}
}
<input type="submit" value="save" />
}
Controller
public ActionResult Edit(int ID)
{
StudentVM model = new StudentVM();
// populate your view model with values from the database
return View(model);
}
[HttpPost]
public ActionResult Edit(StudentVM model)
{
// save and redirect
}
Note I am a little confused by the database structure implied by your models (for example why do you need separate models for Question and SubjectQuestion when a null value for SubjectID identifies it as a "General" question). I suggest you start by just hard-coding some values in the GET method to see how it works and posts back.
StudentVM model = new StudentVM();
model.ID = 1;
model.Name = "bambiinela";
model.Subjects = new List<SubjectVM>()
{
new SubjectVM()
{
Questions = new List<QuestionVM>()
{
new QuestionVM()
{
ID = 1,
Text = "Question 1",
SelectedAnswer = ?, // set this if you want to preselect an option
PossibleAnswers = new List<AnswerVM>()
{
new AnswerVM()
{
ID = 1,
Text = "Answer A"
},
new AnswerVM()
{
ID = 1,
Text = "Answer B"
}
}
},
new QuestionVM()
{
ID = 2,
Text = "Question 2",
PossibleAnswers = new List<AnswerVM>()
{
// similar to above
}
}
}
},
new SubjectVM()
{
ID = 1,
Name = "Math",
Questions = new List<QuestionVM>()
{
// similar to above
}
}
};
When you post, the model is populated with the ID of the selected answer for each question in each subject. Note the use of DisplayFor() for some properties. These won't post back so you would need to repopulate these properties if you return the view (e.g. ModelState is not valid). Alternatively you can generate a read-only textbox or add a hidden input for those properties. I also suggest you inspect the HTML that is generated, in particular the name attributes which will look something like
<input type="radio" name="Subjects[0].Questions[0].SelectedAnswer" ...
to give you an understanding of how collections are bound to your model on post back
The trick is to use an expression (first parameter to Html.RadioButtonFor) which contains a value that changes per group of radio-buttons. In your case, it would be an index in the list of questions.
Here is some sample code:
#for (int i = 0; i < Model.GeneralQuestions.Count; i++)
{
var question = Model.GeneralQuestions[i];
#Html.Label(question.QuestionString)
<br />
foreach (var answer in question.PossibleAnswers)
{
#Html.RadioButtonFor(model =>
Model.GeneralQuestions[i].SelectedAnswerId, answer.Id)
#Html.Label(answer.Answer)
<br />
}
}
This produces the following HTML:
<label for="Q1">Q1</label>
<br />
<input id="GeneralQuestions_0__SelectedAnswerId"
name="GeneralQuestions[0].SelectedAnswerId" type="radio" value="1" />
<label for="A01">A01</label>
<br />
<input id="GeneralQuestions_0__SelectedAnswerId"
name="GeneralQuestions[0].SelectedAnswerId" type="radio" value="2" />
<label for="A02">A02</label>
<br />
<label for="Q2">Q2</label>
<br />
<input id="GeneralQuestions_1__SelectedAnswerId"
name="GeneralQuestions[1].SelectedAnswerId" type="radio" value="11" />
<label for="A11">A11</label>
<br />
<input id="GeneralQuestions_1__SelectedAnswerId"
name="GeneralQuestions[1].SelectedAnswerId" type="radio" value="12" />
<label for="A12">A12</label>
<br />
And for sake of completeness, here is a reduced version of the models used:
public class StudentViewModel
{
public List<Question> GeneralQuestions { get; set; }
}
public class Question
{
public int QuestionId { get; set; }
public string QuestionString { get; set; }
public ICollection<PossibleAnswer> PossibleAnswers { get; set; }
public int SelectedAnswerId { get; set; }
}
public class PossibleAnswer
{
public int Id { get; set; }
public string Answer { get; set; }
}
and here is the code from the action method:
return View(new StudentViewModel
{
GeneralQuestions =
new List<Question>
{
new Question
{
QuestionString = "Q1",
PossibleAnswers =
new[]
{
new PossibleAnswer {Id = 1, Answer = "A01"},
new PossibleAnswer {Id = 2, Answer = "A02"}
}
},
new Question
{
QuestionString = "Q2",
PossibleAnswers =
new[]
{
new PossibleAnswer {Id = 11, Answer = "A11"},
new PossibleAnswer {Id = 12, Answer = "A12"}
}
},
}
});