Orchard - save list of elements on form submit - c#

Basically i have a editor view where i have a few fields and a list, located in admin -> teacher -> edit
File.cshtml
<fieldset>
<legend>#T("Details")</legend>
#Html.LabelFor(m => m.FirstName, T("First name"))
#Html.TextBoxFor(m => m.FirstName)
#Html.LabelFor(m => m.LastName, T("Last name"))
#Html.TextBoxFor(m => m.LastName)
#Html.LabelFor(m => m.Gender, T("Gender"))
#Html.DropDownListFor(m => m.Gender, genderSelectListItems, "Select gender...")
</fieldset>
<fieldset>
#{
var i = 0;
#Html.LabelFor(m => m.Availability)
#Html.DropDownListFor(m => m.Availability, daysSelectList, "Select day to add...")
<input type="button" id="addDay" value="Add" />
<ul id="daysList">
#foreach (var day in Model.Availability)
{
<li>
#Html.TextBoxFor(m => m.Availability[i].Interval)
</li>
i++;
}
</ul>
}
</fieldset>
Whenever i submit the data, only the fields get saved. The list is never saved, even though fiddler shows that the data is sent back to the server
My models are:
TeacherPartRecord
public class TeacherPartRecord : ContentPartRecord
{
public TeacherPartRecord()
{
TeacherData = new List<TeacherDataRecord>();
}
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Gender Gender { get; set; }
public virtual IList<TeacherDataRecord> TeacherData { get; set; }
}
TeacherPart - this file is almost the same as TeacherPartRecord so i truncated it, showing only the difference
public class TeacherPart : ContentPart<TeacherPartRecord>
{
................
public IList<AvailabilityRecord> Availability
{
get { return Record.TeacherData.Select(r => r.AvailabilityRecord).ToList(); }
}
}
TeacherDataRecord
public class TeacherDataRecord
{
public virtual int Id { get; set; }
public virtual TeacherPartRecord TeacherPartRecord { get; set; }
public virtual AvailabilityRecord AvailabilityRecord { get; set; }
}
AvailabilityRecord
public class AvailabilityRecord
{
public virtual int Id { get; set; }
public virtual Day Day { get; set; }
public virtual string Interval { get; set; }
}
and TeacherPartDriver
public class TeacherPartDriver : ContentPartDriver<TeacherPart>
{
//GET
protected override DriverResult Editor(TeacherPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Teacher_Edit", () =>
shapeHelper.EditorTemplate(TemplateName: "Parts/Teacher", Model: BuildEditorViewModel(part), Prefix: Prefix));
}
//POST
protected override DriverResult Editor(TeacherPart part, IUpdateModel updater, dynamic shapeHelper)
{
var viewModel = new TeacherEditViewModel();
updater.TryUpdateModel(viewModel, Prefix, null, null);
_teacherService.UpdateTeacher(viewModel, part);
return Editor(part, shapeHelper);
}
}
BuildEditorViewModel only maps properties from TeacherPart to TeacherEditViewModel, which is identical with TeacherPart
The POST Editor method never receives the data from Availability (part.Availability is the same as previously, new data is not received)
I inserted a few entries in the database manually because i can't add a list of type Availability from the edit page, even though the other content (first name, last name, gender) is created
I would like to know how/when does the binding occur, or maybe some suggestions on how i could solve this issue.
I should mention that my c#/MVC experience is 6 months and my orchard experience is about 2 weeks so i'm still learning.
All i hope is that i stated my problem clearly and there is someone that can help.
Thank you for your time
EDIT
As i can see, the values are bound
<input id="Teacher_Availability_0__Interval" name="Teacher.Availability[0].Interval" type="text" value="14-20">
<input id="Teacher_Availability_1__Interval" name="Teacher.Availability[1].Interval" type="text" value="12-18">
and fiddler shows the data is getting sent
Fiddler
so i guess that something wrong happens right after the data is sent and before being received by the server or maybe my models are wrong

Related

Difficulties with DropDownlist in ASP.Net MVC5

I'm struggling with dropdownlist tried several methods online and all failed will show the methods that I tried.
Objective
Create a Reciept with date,reference... & country. Country is Required and should be a dropdownlist.
So the Table for Reciept("ID, Name, Date, Address, City, CountryList).
RecieptModel
public class TransactionModel
{
public int ID { get; set; }
public int Name{ get; set; }
public DateTime Date { get; set; }
public string Address { get; set;}
public string City { get; set; }
public CountryList CountryList { get; set; }
}
public class ApplicationDbContext : DbContext
{
public DbSet<RecieptModel> Reciepts { get; set;}
public DbSet<CountryList> coutryList { get; set; }
}
CountryList
public class CountryList
{
public byte Id { get; set; }
public enum Country
{
Germany,
US,
UK
}
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Name,Date,City,CountryList")] Reciepts reciepts)
{
if (ModelState.IsValid)
{
db.Reciepts.Add(reciepts);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(reciepts);
}
View
<div class="form-group">
#Html.LabelFor(model => model.CountryList, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EnumDropDownListFor(model => model.CountryList)
#Html.ValidationMessageFor(model => model.CountryList)
</div>
</div>
This failed I looked for several examples I'm trying to do it without the use of javascript. In the End I just want to learn how to implement a Dropdownlist & save it to the database allot of the methods that I tried to implement failed in MVC5.
I would sugest you to add a html extension method that implement dropdownlist for a generic enumarator. Take a look at this answer.
There is slightly change in your view where you bind the Country
this is actually you write
#Html.EnumDropDownListFor(model => model.CountryList)
Change is need to add name property in your country list (it must be ID that is your taken in your model) , so it must be like this to maintain country list value
#Html.DropDownListFor(model => model.CountryList.Id, new SelectList(model => model.CountryList)
Becouse every HTML control need unique indentifier

I want to develop a CRUD interface for a website and I keep getting an error at the Edit method

The error I get is:
The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Albums_dbo.Artists_ArtistId". The conflict occurred in database "MusicStoreData", table "dbo.Artists", column 'ArtistId'
It occurs at the HttpPost Edit action.
All I want to do is to edit a specific album. For example: there is an album called AC/DC and I want to be able to edit the Genre, the Title, the Artist, etc.
I tried changing the view and to put a dropdownlist with the current Genres and Artist in the Db, but then I am getting a referential integrity error.
This is the controller:
public ActionResult Edit(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}
[HttpPost]
public ActionResult Edit(Album album)
{
if(ModelState.IsValid)
{
//Album editAlbum = db.Albums.Find(album.AlbumId);
//album.AlbumId = editAlbum.AlbumId;
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return (RedirectToAction("Index"));
}
return View(album);
}
This is the view:
#model MvcMusicStore.Models.Album
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (#Html.BeginForm())
{
<fieldset>
#Html.HiddenFor(x => x.AlbumId)
<legend>Album</legend>
<div>
Genre<br />
#Html.TextBoxFor(x => x.Genre.Name)
</div>
<div>
Artist<br />
#Html.TextBoxFor(x => x.Artist.Name)
</div>
<div>
Title<br />
#Html.EditorFor(x => x.Title)
</div>
<div>
Price<br />
#Html.TextBoxFor(x => x.Price)
</div>
<div>
Description<br />
#Html.EditorFor(x => x.Genre.Description)
</div>
<p><input type="submit" value="Save" /></p>
</fieldset>
}
<div>#Html.ActionLink("Return to List", "Index")</div>
And these are the model classes:
namespace MvcMusicStore.Models
{
public class Album
{
public int AlbumId { get; set; }
public int GenreId { get; set; }
public int ArtistId { get; set; }
public virtual Genre Genre { get; set; }
public string Title { get; set; }
public decimal Price { get; set; }
public virtual Artist Artist { get; set; }
}
public class Genre
{
public int GenreId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Album> Albums { get; set; }
}
public class Artist
{
public int ArtistId { get; set; }
public string Name { get; set; }
}
}
And the DbContext:
namespace MvcMusicStore.Models
{
public class MusicStoreEntities : DbContext
{
public DbSet<Album> Albums { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<Artist> Artists { get; set; }
}
}
I know that I am missing something, but I really can't put my finger on it. I would be glad if there is someone willing to help me. Thanks!
You can solve this two ways:
1. Easy Way and Incomplete way - just to get rid of that error
Just add:
#Html.HiddenFor(x => x.ArtistId)
To your form. Your requests are stateless, so the server is just being sent what is contained in your form. This however would keep you from being able to change your artist with one of your textboxes (but that also won't work, which I'll touch in the second option).
2. Slightly Better Way
Right now, you are posting data to your controller that doesn't match your model. Your controller is expecting an Album type object and you're posting things like Genre.Name, which Album does not have have. As a result, your controller will default to 0 for your foreign keys for these objects (that's the problem you're facing now). I would recommend making a new class called AlbumEditViewModel that contains the text fields for GenreName and ArtistName (you probably want to remove Genre.Description from this view as I don't think you want to edit it). In your get method for this view, hydrate the ViewModel with the appropriate values for the given album, and then send that to the view. On your post method, you'll get the Album model from the database based on the Album.AlbumId and also get the Ids for Genre and Artist and then set the Ids of your Album model before saving Album.
Other Recommendations
I would probably change this to be dropdowns instead of textbox as right now it will require exact matches to get the artist ID. Alternatively, you could keep it as textboxes and have your controller check if an artist exists with such a name and if it doesn't, create a new artist in the database with that name before assigning the ID.
Let me know if you have questions.

MVC model binding to list - only works on first item in list

I have a page that contains multiple forms to edit questions for a single quiz, each question has its own list of answers. So for each question inside this quiz there is a form for which a user can edit the question (and answers), See below:
#model OLTINT.Areas.admin.ViewModels.OldQuizQAViewModel
<h1>Edit #Model.QuizTitle quiz</h1>
<hr />
<p class="breadcrumb">
#Html.ActionLink(HttpUtility.HtmlDecode("◄") + " Back to List", "Quizzes", new { id = Model.CourseID }, new { #class = "" })
</p>
#for (int j = 0; j < Model.OldQuizQuestions.Count(); j++)
{
using (Ajax.BeginForm("EditQuiz", "Course", null, new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "button"
}))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.QuizID)
#Html.HiddenFor(model => model.OldQuizQuestions[j].QuizQuestionID)
<p class="form_title">Question number #Model.OldQuizQuestions[j].Order</p>
<div class="resize_input">#Html.EditorFor(model => model.OldQuizQuestions[j].Question)</div>
<p class="form_title">#Html.LabelFor(model => model.OldQuizQuestions[j].Type)</p>
<div class="resize_input">#Html.DropDownListFor(model => model.OldQuizQuestions[j].Type, ViewBag.types, "Please choose...", new { #class = "chosen-select" })</div>
<p class="form_title">Choose correct answers</p>
Char x = 'a';
for (int i = 0; i < Model.OldQuizQuestions[j].OldQuizAnswers.Count(); i++)
{
x++;
if (i == 0)
{
x = 'a';
}
<div style="display:table; width:100%;">
<div class="divTableCell" style="padding:0 10px 10px 0; vertical-align:middle; min-width:6%;">
#Html.CheckBoxFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].Correct, new { style = "" })
#Html.LabelFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].Correct, "["+ x +"]")
</div>
<div class="divTableCell quiz_input">
#Html.HiddenFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].QuizAnsID)
#Html.EditorFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].Answer)
</div>
</div>
}
<div class="button_container">
<p id="button"></p>
#Html.ActionLink("Delete this question", "DeleteQuestion", new { id = Model.OldQuizQuestions[j].QuizQuestionID }, new { #class = "button button_red button_not_full_width" })
<input type="submit" value="Save" class="button button_orange button_not_full_width" />
</div>
<hr />
}
}
OldQuizQAViewModel:
public class OldQuizQAViewModel
{
public int CourseID { get; set; }
public int? QuizID { get; set; }
public string QuizTitle { get; set; }
public IList<OldQuizQuestions> OldQuizQuestions { get; set; }
}
OldQuizQuestions:
public class OldQuizQuestions
{
[Key]
public int QuizQuestionID { get; set; }
public int OldQuizID { get; set; }
[Required]
public string Question { get; set; }
[Required]
public int Order { get; set; }
[Required]
public int Type { get; set; }
public virtual IList<OldQuizAnswers> OldQuizAnswers { get; set; }
public virtual OldQuiz OldQuiz { get; set; }
}
OldQuizAnswers:
public class OldQuizAnswers
{
[Key]
public int QuizAnsID { get; set; }
public int QuizQuestionID { get; set; }
public string Answer { get; set; }
public int Order { get; set; }
public bool Correct { get; set; }
public bool Chosen { get; set; }
public virtual OldQuizQuestions OldQuizQuestions { get; set; }
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditQuiz(OldQuizQAViewModel model)
{
var questiondata = model.OldQuizQuestions.Single();
if (ModelState.IsValid)
{
OldQuizQuestions updatequestion = db.OldQuizQuestions
.SingleOrDefault(x => x.QuizQuestionID == questiondata.QuizQuestionID);
updatequestion.Question = questiondata.Question;
updatequestion.Type = questiondata.Type;
db.Entry(updatequestion).State = EntityState.Modified;
db.SaveChanges();
foreach (var answer in questiondata.OldQuizAnswers)
{
var updateanswer = updatequestion.OldQuizAnswers
.First(x => x.QuizAnsID == answer.QuizAnsID);
updateanswer.Answer = answer.Answer;
updateanswer.Correct = answer.Correct;
db.Entry(updateanswer).State = EntityState.Modified;
db.SaveChanges();
}
return Content("<span style='font-weight:300; font-size:1.2em; color: green; '>Saved!</span>");
}
return Content("<span class='errortext'>Please correct the marked fields!</span>");
}
Now this works fine if I want to edit the first question but when I edit anything else my controller just says null but when I check the data that's being posted everything is there (for example when i try to edit question 2):
I've had a look around on here at the many queries about model binding to a list but none have helped. Can anyone see where i'm going wrong with this?
The issue you are facing is caused by a misunderstanding of how asp.net model binding works in relation to lists. For example looking at the view model used in your controller action EditQuiz.
public class OldQuizQAViewModel
{
public int CourseID { get; set; }
public int? QuizID { get; set; }
public string QuizTitle { get; set; }
public IList<OldQuizQuestions> OldQuizQuestions { get; set; }
}
In order for the model binding to work with a IList or any other collection, asp-net expects the form data you post to have sequential indexes. The form data you are sending over POST already has a working example of model binding with collections implemented. Looking at the form data:
The highlighted properties show how to correctly bind to a collection, in that you set the values for the property Correct in the OldQuizAnswers model for each index of IList in OldQuizQAViewModel and pass these all at once in a single request.
Whereas in the same request you only pass the data for OldQuizQuestions of specific index you wish these values to be bound to in the IList collection.
This is why the fist time you post, the model binding works successfully as you are referencing the first index ([0]), whereas on the second POST you reference the second index (1) but not the first, causing the model binding to fail.
See herefor more information on how model binding works.
Have you tried adding a #Html.HiddenFor(model => model.OldQuizQuestions[j]) ?
I read this "It creates a hidden input on the form for the field (from your model) that you pass it.
It is useful for fields in your Model/ViewModel that you need to persist on the page and have passed back when another call is made but shouldn't be seen by the user."
Answer comes from https://stackoverflow.com/a/3866720/8404545
Might be the problem here

How to POST and Catch a view model with a parent object and list of child objects in EF MVC?

I want to add a family which has many members and can have many houses. I am trying to make a form and submit the object family with collection of other objects. I have tried few things but I can only get one object to pass to controller and not the collection. What can i do?
should i make member and house partial views and render them in the view ??
Is there any way of doing this with collecting everything in JavaScript and then passing a whole family object to the controller?
I am using MVC 5 with Entity Framework. I am not able to solve this problem. Any help is appreciated.
here is an example of objects
public class family
{
public int id { get; set; }
public int familyname { get; set; }
public List<member> members { get; set; }
public List<house> houses { get; set; }
}
public class member
{
public int id { get; set; }
public string name { get; set; }
public DateTime birthdate { get; set; }
//foreign key
public family Family { get; set; }
public int FamilyID { get; set; }
}
public class house
{
public int id { get; set; }
public int number { get; set; }
public string address { get; set; }
//foreign key
public family Family { get; set; }
public int FamilyID { get; set; }
}
public class FamilyViewModel
{
public family Family { get; set; }
public member Member { get; set; }
public house House { get; set;}
public List<member> Members { get; set; } //??
public List<house> Houses { get; set; } //??
}
View
#model WebApp.ViewModels.FamilyViewModel
#{
ViewBag.Title = "New";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Family</h2>
#using (Html.BeginForm("Submit", "Family"))
{
<div class="form-group">
#Html.LabelFor(m => m.Family.familyname)
#Html.TextBoxFor(m => m.Family.familyname, new { #class = "form-control"})
</div>
<div id="member">
</div>
<div id="house">
</div>
}
Controller
[HttpPost]
public ActionResult Submit(FamilyViewModel CompleteFamily)
{
//What to do here?
return View();
}
First of all please change the datatype of familyname to string, inside family class. And FamilyViewModel will be same as your family class.
Currently I'm working on a MVC project that have this type of functionality. See, in this case, first you need to save data for family. While saving, members and houses will be null.
For example, for the first time you are saving data, then suppose here family id is 1, familyname is John, members and houses will be null.
Hope you understood till this.
You already render two partial view for members and houses. Provide two buttons in the main view(that is nothing but your family view). 1 is for Add Members and another 1 is for Add Houses. So when user click on Add Members show one popup modal or anything you want, where user can submit family members. Similarly for houses.
Then while saving family members (I mean when they click on SAVE button in members popup modal), just call a jquery/ajax function and post your data to controller method including the current family Id.
See my bellow code,
//This will be inside a popup modal for members,
<div class="row with-forms">
<div class="col-md-12">
<h5>Member Name</h5>
<input class="search-field" type="text" name="name" id="memberName" />
</div>
<div class="col-md-12">
<h5>Birth Date</h5>
<input class="search-field" type="text" name="DOB" id="memberDOB" />
</div>
<div class="col-md-12">
<input class="btn btn-success" type="button" id="saveMembers" />
</div>
</div>
//My jquery/ajax code to save members data
$("#saveMembers").click(function () {
var membersData = [{
name: $("#memberName").val(),
birthdate: $("#memberDOB").val()
})
var CompleteFamily = {
id: $("#hiddenFamilyId").val(), //keep your familyId in a
//hidden field in same page
members: membersData,
//houses: houseData //similarly u can add house data here
}
$.ajax({
"url": "/yourControllerName/FamilyViewModel",
"type": "Post",
"data": CompleteFamily,
success: function (data) {
//show a message that member added to this family
}
})
Thats it. Then you can save your data in Action method. Like this you can post houses data for the same familyId in same ajax method.
In your controller Action method you can validate like this,
public ActionResult Submit(FamilyViewModel CompleteFamily)
{
if(FamilyViewModel.id == 0)
// This is a new entry to family.
// Return your new familyId in a viewBag and keep that in a hidden field
//in your view HTML
else
{
//You just need to update the family. I mean, here you are adding
//members and houses to the respective family Id.
}
}
See, dont forget to return your familyId in a viewBag or what ever you want, and keep that in a hidden field in HTML. So that only you can add members/houses to that respective Id. Like the way I'm passing data in the above ajax.
For example, your familyId in HTML like bellow,
<input type="hidden" id="hiddenFamilyId" value="#ViewBag.familyId"/>
Hope it solve your problem. Cheers...

Create a checkbox for each object

I am creating a small website which allows users to create questions and exams (and also take those exams). Facing a problem with the Create-examview. The user has to be allowed to check any Questions that should be added to the Exam.
Using the following action to create the view, which passes the Questions in a ViewBag:
public ActionResult Create()
{
QuestionaireDbContext db = new QuestionaireDbContext();
ViewBag.Questions = db.Questions;
return View();
}
In my view then, I can call ViewBag.Questions and (should be able to?) use those to create checkboxes for each Question.
I have tried using the extesionmethod for HtmlHelper, CheckBoxList, which I got through NuGet. But the Html.CheckBoxList doesn't seem to be picked up. I tried adding the using as suggested in their documents but that didn't work either.
How can I create a checkbox for each Question and allow the user to select a variaty of them?
My Exam and Question-models for reference:
public class Exam
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreationDate { get; set; }
public ICollection<Question> Questions { get; set; }
}
public class Question
{
public enum Answers
{
A,
B,
C,
D
}
public int Id { get; set; }
public string Name { get; set; }
public string AnswerA { get; set; }
public string AnswerB { get; set; }
public string AnswerC { get; set; }
public string AnswerD { get; set; }
public Answers Correct { get; set; }
}
You will need to create view models to represent what you want to bind to. One possible solution might be
public class ExamVM
{
public int ID { get; set; }
public string Name { get; set; }
public List<QuestionVM> Questions { get; set; }
}
public class QuestionVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
In you Create method, initialize and populate the ExamVM details including the collection of possible questions, then in the view
#model YourAssembly.ExamVM
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.Name)
for (int i = 0; i < Model.Questions; i++)
{
#Html.HiddenFor(m => m.Questions[i].ID)
#Html.CheckBoxFor(m => m.Questions[i].IsSelected)
#Html.DisplayFor(m => m.Questions[i].Name)
}
<input type="submit" value="Save" />
}
Post method
[HttpPost]
public ActionResult Create(ExamVM model)
{
foreach(QuestionVM q in model.Questions)
{
if (q.IsSelected)
{
// Save the value of exam.ID and question ID to the database
Create a folder called EditorTemplates in your Views/Shared folder.
create a new empty view called _QuestionEditor and add the following code.
#model Question
#Html.HiddenFor(model => model.Id)
#Html.DisplayFor(model => model.Name)
//use DisplayFor or LabelFor accordingly
#Html.CheckboxFor(model => true, Model.AnswerA)
#Html.CheckboxFor(model => true, Model.AnswerB)
#Html.CheckboxFor(model => true, Model.AnswerC)
#Html.CheckboxFor(model => true, Model.AnswerD)
now in your main view use it as follows
#foreach(var question in ViewBag.Questions){
#Html.EditorFor(item => question)
//you can specify the template name i.e `"_QuestionEditor"` as the second parameter
//if you have more than one editor template for the same type
}
This doesn't take into account how you submit your data as you haven't provided any code for that or the model that you use to get the data back into your post action

Categories

Resources