How to create object that contains a list of object in a single form? - asp.net-mvc

public class Basket
{
public int Id { get; set; }
public string Sharp { get; set; }
public string Material { get; set; }
public List<Fruit> Fruits { get; set; }
}
public class Fruit
{
public int Id { get; set; }
public string Color { get; set; }
public string Taste { get; set; }
}
With the above example, how could I create both Basket and Fruit in the same asp-form without using any JavaScript?
<form method="post" asp-controller="Basket" asp-action="Create">
<input asp-for="Material" />
<input asp-for="Sharp" />
#*I would like to also create custom amounts of new Fruit in this form.*#
<input type="submit" value="Submit" />
</form>
If my razor form is defined as the above example, how could I create custom amounts of Fruit and create Basket at the same form? It is possible to avoid using JavaScript in this case?

It is possible to avoid using JavaScript in this case?
Based on your scenario and current architecture what you need to do is, there should be a table where you would be adding your fruit object as it's a List<Fruit> Fruit kind of. As per your given code, your output should be as below:
So, I would say, Javascript would make it easier. If you would like to avoid javascript it wouldn't be impossible but would be costly and complex.
how could I create custom amounts of Fruit and create Basket at the
same form?
You could follow the below steps to achieve what you are trying to implement.
View:
#model DotNet6MVCWebApp.Models.Basket
<form method="post" asp-controller="Yonny" asp-action="Create">
<div class="form-group">
<label asp-for="Material" class="col-md-2 form-label"></label>
<input asp-for="Material" class="col-md-6 form-control" />
<span asp-validation-for="Material" class="form-span-error"></span>
</div>
<div class="form-group" style="padding-bottom:20px">
<label asp-for="Sharp" class="col-md-2 form-label"></label>
<input asp-for="Sharp" class="col-md-6 form-control" />
<span asp-validation-for="Sharp" class="form-span-error"></span>
</div>
#*I would like to also create custom amounts of new Fruit in this form.*#
<div style="padding-bottom:20px">
<button type="button" class="btn btn-primary" onclick="AddRow()">Add Fruit</button>
</div>
<div id="dataTable">
<table>
<thead>
<tr>
<th>Id</th>
<th>Color</th>
<th>Taste</th>
</tr>
</thead>
<tbody id="FruitList" data-count="0">
</tbody>
</table>
</div>
<input type="submit" class="btn btn-success" value="Submit" />
</form>
#section Scripts {
<script>
/*
. Hidding table on load
*/
document.getElementById('dataTable').style.display ='none';
function AddRow()
{
var countVal = parseInt($('#FruitList').attr('data-count'));
var html = '';
html += '<tr>';
html += '<td><input type="text" name="Fruits[' + countVal + '].Id" class="form-control"/></td>';
html += '<td><input type="text" name="Fruits[' + countVal + '].Color" class="form-control"/></td>';
html += '<td><input type="text" name="Fruits[' + countVal + '].Taste" class="form-control"/></td>';
html += '</tr>';
$('#FruitList').append(html);
countVal += 1;
$('#FruitList').attr('data-count', countVal);
/*
. Showing table when adding item into
*/
document.getElementById('dataTable').style.display ='block';
}
</script>
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("Id,Material,Sharp,Fruits")] DotNet6MVCWebApp.Models.Basket basket)
{
if (ModelState.IsValid)
{
//Save Basket
_context.Add(basket);
await _context.SaveChangesAsync();
//Add Fruits List
foreach (var item in basket.Fruits)
{
_context.Add(item);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Create));
}
return View(basket);
}
Note:
If you somehow got null data while sending request to controller make sure your binding property that is Bind("Id,Material,Sharp,Fruits") are same as name="Fruits[' + countVal + '].Id" inside the javascript function
Output:

Related

Asp.net core form always returns null

I'm trying to build one view that includes all (Create, Edit, Delete, and Index) in one View which is Index.
The problem is with Editing. Always returns null to the controller as shown in the gif.
I have Model and ViewModel as follows.
The Model BootstrapCategory
public class BootstrapCategory
{
[Key]
public Guid Id { get; set; }
[MaxLength(20)]
[Required]
public string Category { get; set; }
}
The ViewModel VMBPCategoris
public class VMBPCategoris
{
public List<BootstrapCategory> bootstrapCategories { get; set; }
public BootstrapCategory bootstrapCategory { get; set; }
}
The View
Note: Edit not by the usual button in the table it instead by another
button as shown in the gif
#model VMBPCategoris
#foreach (var item in Model.bootstrapCategories)
{
<tr>
<td>
<form asp-action="Edit" method="post">
<input type="hidden" asp-for="#item.Id" />
<div class="#item.Id d-none">
<div class="input-group">
<input id="btnGroupEdit" type="submit" value="Save" class="input-group-text btn btn-primary" />
<input asp-for="#item.Category" class="form-control" aria-label="Input group example" aria-describedby="btnGroupEdit">
</div>
<span asp-validation-for="#item.Category" class="text-danger"></span>
</div>
</form>
<div class="#item.Id">
#Html.DisplayFor(modelItem => item.Category)
</div>
</td>
<td>
Edit |
<a asp-action="Details" asp-route-id="#item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="#item.Id">Delete</a>
</td>
</tr>
}
The Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit([Bind("Id,Category")] BootstrapCategory bootstrapCategory)
{
_context.Update(bootstrapCategory);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
//return View(vMBPCategoris);
}
The view model class VMBPCategoris needs to have its members properties assigned to instances in a constructor:
public class VMBPCategoris
{
public List<BootstrapCategory> bootstrapCategories { get; set; }
public BootstrapCategory bootstrapCategory { get; set; }
public VMBPCategoris()
{
bootstrapCategories = new List<BootstrapCategory>();
bootstrapCategory = new BootstrapCategory();
}
}
You can give a name to your input tag.Change your form like below.
<form asp-action="Edit" method="post">
<input type="hidden" asp-for="#item.Id" name="Id"/>
<div class="#item.Id d-none">
<div class="input-group">
<input id="btnGroupEdit" type="submit" value="Save" class="input-group-text btn btn-primary" />
<input asp-for="#item.Category" name="Category" class="form-control" aria-label="Input group example" aria-describedby="btnGroupEdit">
</div>
<span asp-validation-for="#item.Category" class="text-danger"></span>
</div>
</form>
I made an easy solution by changing a little bit with the ViewModel and using the value attribute to get value from a model and using asp-for to set a value to another model.
It's all clarified in that Stackoverflow post

How Can I Get ViewData in PartialView in Razor Pages

I'm Using Razor Pages For my App, in one part of my app I've used a partial view here is my codes;
public class Permission
{
[Key]
public int PermissionId { get; set; }
public string PermissionTitle { get; set; }
public int? ParentID { get; set; }
}
public class IndexModel : PageModel
{
public PartialViewResult OnGetCreateRole()
{
var ListPermission = permissionService.AllPermission();
return new PartialViewResult()
{
ViewName = "_PCreateRole", // partial's name
ViewData = new ViewDataDictionary<List<Permission>>(ViewData,
ListPermission)
};
}
}
ViewData is a List of Permission class and i've sent ViewData to partial but i dont know how to get ViewData, also my partial use another model, below is my partial:
#model ToMVC.DataLayer.Entities.User.Role
<div class="row">
<div class="col-md-12">
<form asp-page="CreateRole" method="post">
<div class="form-group">
<label class="control-label">Title</label>
<input asp-for="RoleTitle" class="form-control"/>
<p><span class="text-danger" asp-validation-for="RoleTitle"></span></p>
</div>
<div class="form-group">
<input type="submit" value="submit" class="btn btn-primary" />
</div>
//this part needs ViewData
#foreach (var item in ViewData)
{
}
</form>
</div>
</div>
I want to use ViewData in Foreach loop.
A better solution than ViewData would be to simply make a new ViewModel class containing all the information you need for a view.
public class UserRoleAndPermissions{
public UserRoleAndPermissions(){
Permissions = new List<Permissions>();
}
public List<Permission> Permissions {get;set;}
public ToMVC.DataLayer.Entities.User.Role Role {get;set;}
}
And your view
//check your namespace here - this is just an example
#model ToMVC.DataLayer.UserRoleAndPermissions
<div class="row">
<div class="col-md-12">
<form asp-page="CreateRole" method="post">
<div class="form-group">
<label class="control-label">Title</label>
<input asp-for="RoleTitle" class="form-control"/>
<p><span class="text-danger" asp-validation-for="RoleTitle"></span></p>
</div>
<div class="form-group">
<input type="submit" value="submit" class="btn btn-primary" />
</div>
#foreach (var item in Model.Permissions)
{
}
</form>
</div>
</div>

.NET Core MVC Form returning wrong model value

I'm having a weird issue with a form in my View not returning the model's correct Id property value. You'll noticed in the code below I have the script section logging the model's Id. Doing this shows the correct Id on the console, but when the Id is passed to the Controller's action method it is always 0, which is incorrect.
Here's the view:
#model EotE_Encounter.Models.Encounter
<div>
<h4>#Model.Name</h4>
<div>
<form asp-action="CreateCharacter" asp-controller="Encounter" data-bind="" data-ajax="true" data-ajax-mode="replace" data-ajax-update="#character-container">
<input id="encounterId" type="hidden" value="#Model.Id" />
<button class="btn btn-default" type="submit">Create Character</button>
</form>
</div>
<hr />
<div>
<ul>
#{
if(Model.CharactersInEncounter != null)
{
foreach(Character character in Model.CharactersInEncounter)
{
<li>#character.Name</li>
}
}
}
</ul>
</div>
</div>
<script>
console.log(#Model.Id);
</script>
Related Action Method:
public ActionResult CreateCharacter(int encounterID)
{
return RedirectToAction("CreateCharacter", "Character", encounterID);
}
And the Encounter model:
public class Encounter
{
//these first three properties may not be used just yet.
public int Id { get; set; }
public string Name { get; set; }
public byte Round { get; set; }
public List<Character> CharactersInEncounter { get; set; }
[StringLength(2000)]
public string Notes { get; set; }
}
Only form elements with a name attribute will have their values passed when submitting a form. So, add the name attribute to your hidden element. Id and the name are not the same.
#model EotE_Encounter.Models.Encounter
<div>
<h4>#Model.Name</h4>
<div>
<form asp-action="CreateCharacter" asp-controller="Encounter" data-bind="" data-ajax="true" data-ajax-mode="replace" data-ajax-update="#character-container">
<input id="encounterId" name="encounterID" type="hidden" value="#Model.Id" />
<button class="btn btn-default" type="submit">Create Character</button>
</form>
</div>
<hr />
<div>
<ul>
#{
if(Model.CharactersInEncounter != null)
{
foreach(Character character in Modelsel.CharactersInEncounter)
{
<li>#character.Name</li>
}
}
}
</ul>
</div>
</div>
<script>
console.log(#Model.Id);
</script>
Notice the name attribute of the <input id="encounterId" name="encounterID" type="hidden" value="#Model.Id" /> element. It has to be the same name as the action parameter (int encounterID). If it's not the same then the parameter binding will not work.

How to apply MVC validation attribute dynamically on <input type='Text'>

I have a ViewModel with [Required] and [MaxLength(4)] attribute
public class Student
{
[MaxLength(4)]
[Required]
public string Name { get; set; }
}
Inside my view I have
#model List<WebApplication2.Models.Student>
<div class="row">
<div class="col-md-4">
#{
foreach(var item in #Model)
{
#Html.TextBoxFor(model=>item.Name)
#Html.ValidationMessageFor(model => item.Name)
}
}
</div>
<div class="col-md-4">
#{
foreach (var item in #Model)
{
<input type="text" value="#item" />
}
}
</div>
When I use #Html helpers to render text boxes it applied data-validation rules to textbox. I couldn't figure out how I can apply that to normal Html <input type = 'text' /> Is there any way I can do that dynamically without using #Html helpers?

Binding Complex Types in MVC5

I'm a newbie developer trying to develope a web application with asp .net mvc 5 for personel usage. The website kind of a quiz maker that I can insert Russian words with meanings and prepare a quiz by using these words.
When I trying to code the quiz page and post the data the the action method I faced some problem that I couldn't get around. I iterated through the Model, read the data and wrote them to the page. Now, what I want to do is, when I post the form, I want to get each question string and selected answer (maybe in this format: imgur.com/QETnafx). Therefore, I can easily check the answer string whether it is true or not.
I checked the following tutorials out:
Model Binding To A List by Phil Haack and ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries by Scott Hanselman
I hope I explained the situation clearly. If you need more information I can happily provide.
ViewModel
public class QuizInitializationModel
{
public List<Question> Questions { get; set; }
}
public class QuestionString
{
public int Id { get; set; }
public string WordString { get; set; }
}
public class Question
{
public QuestionString QuestionString { get; set; }
public List<AnswerItem> Answers { get; set; }
}
public class AnswerItem
{
public int Id { get; set; }
public string WordString { get; set; }
}
VIEW
#using (Html.BeginForm("Begin", "Quiz", FormMethod.Post))
{
<table class="table table-striped table-condensed table-bordered">
#for (int i = 0; i < Model.Questions.Count; i++)
{
<tr>
<td>
#Html.Label(Model.Questions[i].QuestionString.WordString)
</td>
<td>
#for (int item = 0; item < Model.Questions[0].Answers.Count; item++)
{
#Html.Label(Model.Questions[i].Answers[item].WordString)#:
#Html.RadioButton("array" + "[" + #i + "]" + "." + Model.Questions[i].QuestionString.WordString, Model.Questions[i].Answers[item].Id)<br />
}
</td>
</tr>
}
</table>
<input type="submit" value="Send" class="btn btn-primary" />
}
OUTPUT
<form action="/Admin/Quiz/Begin" method="post">
<table class="table table-striped table-condensed table-bordered">
<tr>
<td>
<label for="">вулкан</label>
</td>
<td>
<label for="trade">trade</label>
<input id="array_0________" name="array[0].вулкан" type="radio" value="18" /><br />
<label for="volcano">volcano</label>
<input id="array_0________" name="array[0].вулкан" type="radio" value="24" /><br />
<label for="talk__conversation">talk, conversation</label>
<input id="array_0________" name="array[0].вулкан" type="radio" value="15" /><br />
<label for="time">time</label>
<input id="array_0________" name="array[0].вулкан" type="radio" value="13" /><br />
<label for="income">income</label>
<input id="array_0________" name="array[0].вулкан" type="radio" value="21" /><br />
</td>
</tr>
<tr>
<td>
<label for="">мама</label>
</td>
<td>
<label for="universe">universe</label>
<input id="array_1______" name="array[1].мама" type="radio" value="25" /><br />
<label for="peace">peace</label>
<input id="array_1______" name="array[1].мама" type="radio" value="2" /><br />
<label for="value">value</label>
<input id="array_1______" name="array[1].мама" type="radio" value="20" /><br />
<label for="mom__mama">mom, mama</label>
<input id="array_1______" name="array[1].мама" type="radio" value="17" /><br />
<label for="industry">industry</label>
<input id="array_1______" name="array[1].мама" type="radio" value="19" /><br />
</td>
</tr>
</table>
And how can i fix the ids of the labels like "array_1______" ?
They appeared when I added this code "array" + "[" + #i + "]" + "." to the RadioButton control for the purpose of assign an index for each answer.
Html helpers replace invalid characters with an underscore (a period is actually not invalid, but would cause problems with jquery so its also replaced an underscore). However id attribute is not the problem, although you are generating duplicates which is invalid html.
Your manually generating the name attribute for the radio buttons which have no relationship to any property in your model so wont be bound when you post back. Your model needs to include a property you can bind the selected answer to. Modify the Question model to
public class Question
{
public QuestionString QuestionString { get; set; }
public List<AnswerItem> Answers { get; set; }
public int SelectedAnswer { get; set; } // add this
}
and modify the view to
#using (Html.BeginForm()) // note parameter not necessary if your posting to the same controller/action
{
#for (int i = 0; i < Model.Questions.Count; i++)
{
...
#Html.HiddenFor(m => m.Questions[i].QuestionString.Id)
<h2>#Model.Questions[i].QuestionString.WordString)</h2>
...
#foreach(var answer in Model.Questions[i].Answers)
{
var id = string.Format("{0}-{1}", #i, answer.Id);
#Html.Label(id, answer.WordString)
#Html.RadioButtonFor(m => m.Questions[i].SelectedAnswer, answer.ID, new { id = id })
}
....
}
<input type="submit" value="Send" class="btn btn-primary" />
}
The radio buttons are now bound to the SelectedAnswer property and when you post back, the value will be the ID of the selected AnswerItem
Note also:
A hidden input has been added for the ID of the question so the
question can be identified on post back
The question text is in a heading tag (could be another tag), but a
label is not appropriate - a label is an element associated with a
control (for setting focus to the control) but you don't have an
associated control
A unique id is created in the foreach loop so you can give each
radio button a unique id (so the html is valid) and associate the
label (the answer text) with the button

Resources