I need some help to solve this issue, I don't know how to figure out.
Basically I want to add dynamic fields on form but I can't bind the values on Submit Post.
The list is always null, don't have values.
Basically I have a view model with some fieds and one list.
My PlanoAcaoViewModel:
public class PlanoAcaoViewModel
{
public int IdPlanoAcao { get; set; }
public int Numero { get; set; }
public DateTime DataEncerramento { get; set; }
public List<PlanoEvidenciaIForm> ListaPlanoEvidenciaIForm { get; set; }
public class PlanoEvidenciaIForm
{
public int IdPlanoAcaoEvidencia { get; set; }
public IFormFile Arquivo { get; set; }
public string Nota { get; set; }
public DateTime Data { get; set; }
}
}
My Controller:
[HttpPost]
public async Task<IActionResult> Create(PlanoAcaoViewModel model)
{
var num = model.Numero; // It's ok, return the right number
var data = model.DataEncerramento ; // It's ok, return the right date
foreach (var it in model.ListaPlanoEvidenciaIForm)
{
// model.ListaPlanoEvidenciaIForm is NULL
}
}
My cshtml:
#model ProjetoGestor.ViewModel.PlanoAcaoViewModel
<form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="row">
<div class="col-auto">
<label asp-for="Numero" class="control-label"></label>
<input asp-for="Numero" class="form-control" disabled type="text" />
</div>
<div class="col-auto">
<label asp-for="DataEncerramento" class="control-label"></label>
<input asp-for="DataEncerramento" class="form-control" type="date" placeholder="dd/mm/yyyy" />
<span asp-validation-for="DataEncerramento" class="text-danger"></span>
</div>
</div>
<div class="row">
#* Dynamic Fields *#
<input asp-for="ListaPlanoEvidenciaIForm[0].Arquivo" class="form-control" type="file" />
<input asp-for="ListaPlanoEvidenciaIForm[0].Nota" class="form-control" />
</div>
<div class="row col-auto">
<div class="align-center">
<a class="btn btn-primary btn-xl" asp-action="Index">Voltar</a> |
<input type="submit" value="Criar" class="btn btn-primary btn-success btn-xl" />
</div>
</div>
</form>
And the dynamic fields
I already tried a lot of ways but without success:
// In this way the model.ListaPlanoEvidenciaIForm is NULL
<input asp-for="ListaPlanoEvidenciaIForm[0].Arquivo" class="form-control" type="file" />
<input asp-for="ListaPlanoEvidenciaIForm[0].Nota" class="form-control" />
// In this way don't do it the POST (don't call the method create post)
<input name="ListaPlanoEvidenciaIForm[0].Arquivo" class="form-control" type="file" />
<input name="ListaPlanoEvidenciaIForm[0].Nota" class="form-control" />
// In this way the model.ListaPlanoEvidenciaIForm have length > 0 but all values inside list are null
<input name="ListaPlanoEvidenciaIForm" class="form-control" type="file" />
<input name="ListaPlanoEvidenciaIForm" class="form-control" />
In this way also don't call the method creat post:
From https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1;
When uploading files using model binding and IFormFile, the action
method can accept:
A single IFormFile.
Any of the following collections that represent several files:
IFormFileCollection
IEnumerable<IFormFile>
List<IFormFile>
There's no mention of a list of objects, each containing an IFormFile. So you may be hitting a limitation of MVC binding. I'd suggest you try to split the list of files from the other values;
public List<PlanoEvidenciaIForm> ListaPlanoEvidenciaIForm { get; set; }
public List<IFormFile> Arquivos { get; set; }
public class PlanoEvidenciaIForm
{
public int IdPlanoAcaoEvidencia { get; set; }
public string Nota { get; set; }
public DateTime Data { get; set; }
}
<!-- The IFormFile doesn't use [ ] -->
<input asp-for="Arquivos" class="form-control" type="file" />
<input asp-for="ListaPlanoEvidenciaIForm[0].Nota" class="form-control" />
Related
I have two models, User and Employee. I have to make a registration form, where I'll be taking inputs for both these models. I know how to do this with a single model but I cannot figure out how to do it for multiple models. I have read some suggestions here where they've suggested using a ViewModel but I cannot configure it properly.
User:
public class UserModel
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
Employee:
public class EmployeeModel
{
public int Id { get; set; }
public string Name { get; set; }
public int Phone { get; set; }
public int UserId { get; set; }
public int LocationId { get; set; }
}
ViewModel:
public class UserEmployee
{
public UserModel user { get; set; }
public EmployeeModel emp { get; set; }
}
My controllers:
[HttpGet]
public ActionResult RegisterEmployee()
{
var model = new UserEmployee();
model.user = new UserModel();
model.emp = new EmployeeModel();
return View(model);
}
[HttpPost]
public ActionResult RegisterEmployee(UserEmployee useremp)
{
return View();
}
Form:
#model ZeroHungerProject.Models.UserEmployee
///////
<form action="" method="post">
<div class="form-group">
<label>User Name</label>
<input type="text" name="#Model.user.UserName" class="form-control" placeholder="User Name">
</div>
<div class="form-group">
<label>Email address</label>
<input type="email" name="#Model.user.Email" class="form-control" placeholder="Email">
</div>
<div class="form-group">
<label>Phone</label>
<input type="number" name="#Model.emp.Phone" class="form-control" placeholder="Phone">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="#Model.user.Password" class="form-control" placeholder="Password">
</div>
<div class="form-group">
<label>Confirm Password</label>
<input type="password" class="form-control" placeholder="Confirm Password">
</div>
<button type="submit" class="btn btn-primary btn-flat m-b-30 m-t-30">Register</button>
I tried using the viewmodel to post the inputs. But no data is passing from the form.
Remove the prefix "#Model." from the name attribute.
<form action="" method="post">
<div class="form-group">
<label>User Name</label>
<input type="text" name="user.UserName" class="form-control" placeholder="User Name">
</div>
<div class="form-group">
<label>Email address</label>
<input type="email" name="user.Email" class="form-control" placeholder="Email">
</div>
<div class="form-group">
<label>Phone</label>
<input type="number" name="emp.Phone" class="form-control" placeholder="Phone">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="user.Password" class="form-control" placeholder="Password">
</div>
<div class="form-group">
<label>Confirm Password</label>
<input type="password" class="form-control" placeholder="Confirm Password">
</div>
<button type="submit" class="btn btn-primary btn-flat m-b-30 m-t-30">Register</button>
</form>
And to pass ConfirmPassword, you need to provide the name attribute to the <input> element for ConfirmPassword and add the ConfirmPassword property in the UserModel class.
<input type="password" name="user.ConfirmPassword" class="form-control" placeholder="Confirm Password">
public class UserModel
{
...
public string ConfirmPassword { get; set; }
}
Controller
public async Task<IActionResult> Create(IFormFile? StaffPhoto, CollectionViewModel collectionModel)
{
if (StaffPhoto != null){...} // issue is StaffPhoto value is null
}
View Model
namespace Website.Models
{
public class CollectionViewModel
{
public Staff staff { get; set; }
public Contact contact { get; set; }
}
}
Entity Model
public class Staff
{
public int StaffId { get; set; }
[DisplayName("First Name")]
[Required]
public string StaffFirstName { get; set; }
[DisplayName("Last Name")]
[Required]
public string StaffLastName { get; set; }
[DisplayName("Photo")]
public string? StaffPhoto { get; set; }
}
View
#model CollectionViewModel
<form asp-action="Create" enctype="multipart/form-data" method="post" class="row g-3 mt-0">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="col">
<label asp-for="staff.StaffFirstName" class="form-label"></label>
<input asp-for="staff.StaffFirstName" class="form-control" />
<span asp-validation-for="staff.StaffFirstName" class="text-danger"></span>
</div>
<div class="col">
<label asp-for="staff.StaffLastName" class="form-label"></label>
<input asp-for="staff.StaffLastName" class="form-control" />
<span asp-validation-for="staff.StaffLastName" class="text-danger"></span>
</div>
<div class="col-md-3">
<label asp-for="staff.StaffPhoto" class="form-label"></label>
<input asp-for="staff.StaffPhoto" type="file" accept="image/*" class="form-control" />
<span asp-validation-for="staff.StaffPhoto" class="text-danger"></span>
#{if (ViewBag.fileUploadErrorMessage != null)
{
<span class="text-danger">#ViewBag.fileUploadErrorMessage</span>
}
}
</div>
<div class="col">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="Create" class="btn btn-secondary">Reset All</a>
</div>
</form>
You should add IFormFile in model.
public class CollectionViewModel
{
public Staff staff { get; set; }
public IFormFile StaffPhoto { get; set; }
public Contact contact { get; set; }
}
set StaffPhoto to asp-for in view.
<input asp-for="StaffPhoto" type="file" accept="image/*" class="form-control" />
I have a question about model binding in ASP.NET Core Razor pages. I've spent a lot of time reading the Microsoft documentation, Stackoverflow posts and learnrazorpages.com, but haven't quite found the answer I'm looking for. So below is a stripped down version of three classes I have. As you can see, both Client and ReferralPerson contain Person objects and Client also contains a ReferralPerson object. On my razor page (the data entry form), I intentionally do not show all of the fields for the Person object that is part of the ReferralPerson. There are also other properties of the ReferralPerson object that are not shown such as NumberOfReferrals. If a user recalls a record, modifies some of the data and then saves (Post), only those properties that I have included inside of the element end up being populated in the model in the OnPost (OnPostSave). I understand why this is. My question is do I have to include every single property, individually on the page in order for those properties to be populated in the OnPost event? If you look at my Razor page code below, you will see that I add a number of hidden fields at the top of the element. Do I need to do that for every single property that isn't visible or included somewhere else inside that form? Ideally I would just be able to do something like the following to include the entire object versus having hidden fields for every single property. I just want to make sure there isn't something I'm not aware of in terms of how this can or should be handled.
<input type="hidden" asp-for="Client.ReferralPerson" />
Here are the C# classes (model)
public class Person
{
public int ID { get; set; }
[Required, StringLength(50)]
public string FirstName { get; set; } = "N/A";
[Required, StringLength(50)]
public string LastName { get; set; } = "N/A";
[StringLength(30)]
public string PhoneNumberPrimary { get; set; }
[StringLength(30)]
public string PhoneNumberSecondary { get; set; }
[StringLength(50)]
public string Address { get; set; }
[StringLength(50)]
public string EmailAddress { get; set; }
}
public class Client
{
public int ID { get; set; }
public Person Person { get; set; }
public DateTime InitalContactDate { get; set; }
[Required, StringLength(100)]
public string ReasonForVisit { get; set; }
public ReferralPerson ReferralPerson { get; set; }
//other properties removed for brevity
}
public class ReferralPerson
{
public int ID { get; set; }
public Person Person { get; set; }
[StringLength(100)]
public string BusinessName { get; set; }
public int NumberOfReferrals { get; set; }
}
Here's a simplified version of my Razor page:
<form class="needs-validation p-2" novalidate="" method="post">
<input type="hidden" asp-for="Client.ID" />
<input type="hidden" asp-for="Client.Person.ID" />
<input type="hidden" asp-for="Client.ContactType.ID" />
<input type="hidden" asp-for="Client.ReferralPerson.ID" />
<input type="hidden" asp-for="Client.ReferralPerson.Person.ID" />
<div class="row mb-3">
<div class="col-sm-6">
<label asp-for="Client.Person.FirstName" class="form-label">First name</label>
<input type="text" class="form-control" asp-for="Client.Person.FirstName">
<span asp-validation-for="Client.Person.FirstName" class="text-danger font-weight-bold"></span>
</div>
<div class="col-sm-6">
<label asp-for="Client.Person.LastName" class="form-label">Last name</label>
<input type="text" class="form-control" asp-for="Client.Person.LastName">
<span asp-validation-for="Client.Person.LastName" class="text-danger font-weight-bold"></span>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-6">
<label for="type" class="form-label stacked-justify-left">Date</label>
<input type="date" class="form-control" asp-for="Client.InitalContactDate">
<span asp-validation-for="Client.InitalContactDate" class="text-danger font-weight-bold"></span>
</div>
<div class="col-6">
<label for="email" class="form-label">Email <span class="text-muted">(Optional)</span></label>
<input type="email" class="form-control" placeholder="you#example.com" asp-for="Client.Person.EmailAddress">
</div>
</div>
<div class="row mb-3">
<div class="col-6">
<label for="primaryPhone" class="form-label">Phone # (Primary)</label>
<input type="text" class="form-control" placeholder="Phone Number" asp-for="Client.Person.PhoneNumberPrimary">
</div>
<div class="col-6">
<label for="secodaryPhone" class="form-label">Phone # (Secondary)</label>
<input type="text" class="form-control" placeholder="Phone Number" asp-for="Client.Person.PhoneNumberSecondary">
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label for="address" class="form-label">Address</label>
<input type="text" class="form-control" placeholder="1234 Main St" asp-for="Client.Person.Address">
</div>
</div>
<div class="row mb-3" id="ReferralPersonDetails" style="display:none">
<div class="col-sm-6">
<label asp-for="Client.ReferralPerson.Person.FirstName" class="form-label">Referral By First name</label>
<input type="text" class="form-control" asp-for="Client.ReferralPerson.Person.FirstName">
</div>
<div class="col-sm-6">
<label asp-for="Client.ReferralPerson.Person.LastName" class="form-label">Referral By Last name</label>
<input type="text" class="form-control" asp-for="Client.ReferralPerson.Person.LastName">
</div>
</div>
<hr class="my-4">
<div class="row mb-3 ml-1 mr-1">
<hr class="my-4">
<button type="submit" class="w-100 btn btn-primary btn-lg" asp-page-handler="Save">Save</button>
</div>
</form>
Model binding is based on the name of properties in model, If you don't use
<input asp-for="xxx">
to bind the specified property, After submitting the form, These properties will not be populated in post method. So if you don't bind all properties, those that are not bound will not be populated in the post method.
But in your data form, I think you just want to let the user modify some properties. So in my opinion, You can create a viewmodel to include the properties whitch you want to change, In this method you don't need to bind all the properties in the form. Then in your controller, You just need to replace the original data with the data in the viewmodel.
You don't need to do the same with all id fields. I guess you are trying to edit the information of Client and ReferralPerson, adding the .Person.ID fields is not necessary because surely your database already has information about Person in each Client or ReferralPerson, get it out in controller process this action then edit it with data from the form.
I have the following model, which needs to be created via a page:
public class Exercise
{
public Guid Id{ get; set; }
public string Name { get; set; }
public string Description{ get; set; }
public ICollection<ExerciseCategory> Categories
...
public ICollection<Link> Links { get; set; }
}
public class ExerciseCategory
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class Link
{
public string Name { get; set; }
public string Value { get; set; }
}
I have a view that lets me create this object, but I miss a form control for the property Exercise.Links:
<form id="profile-form" method="post">
<div class="row">
<div class="col-12 p-sm-2 p-md-3 p-lg-5 bg-custom-white rounded-4">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-floating mt-2 mb-2">
<input asp-for="Input.Name" class="form-control" />
<label asp-for="Input.Name" class="form-label"></label>
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-floating mt-2 mb-2">
<input asp-for="Input.Description" class="form-control" />
<label asp-for="Input.Description" class="form-label"></label>
<span asp-validation-for="Input.Description" class="text-danger"></span>
</div>
<div class="form-floating mt-2 mb-2">
<select asp-for="Input.CategoryIds"
asp-items="Model.Input.Categories" class="form-control"></select>
<label asp-for="Input.Categories" class="form-label"></label>
</div>
<button id="create-exercise-button" type="submit" class="w-100 btn btn-lg btn-primary">Create</button>
</div>
</div>
</form>
The property Exercise.Categories is easy as Category is an entity which is created separately, so I just have a dropdown listing the existing categories.
Link is just a value object that is not persisted on its own. Therefore, the user should be able to create/remove/edit Link from the Exercise create/edit page.
I have tried the following:
#for (var i = 0; i < Model.Input.Links.Count; i++)
{
<div>
<input asp-for="Input.Links[i].Name">
<input asp-for="Input.Links[i].Value">
</div>
}
Which results in the following HTML:
<div>
<input type="text" id="Input_Links_0__Name" name="Input.Links[0].Name" value="some link">
<input type="text" id="Input_Links_0__Value" name="Input.Links[0].Value" value="https://somelink.com">
</div>
<div>
<input type="text" id="Input_Links_1__Name" name="Input.Links[1].Name" value="some link1">
<input type="text" id="Input_Links_1__Value" name="Input.Links[1].Value" value="https://somelink1.com">
</div>
But this doesn't populate the Input.Links property when posting back.
I started thinking that the problem is the fact that my list is a list of Link rather than a list of primitive types. In order to test this, I added a test List<string> to input model called Links1 and tried to populate that list as I did before:
<div class="form-floating mt-2 mb-2">
<input asp-for="Input.Links1[i]" data-val="true" class="form-control" />
<label asp-for="Input.Links1[i]" class="form-label"></label>
</div>
This works. I get the populated list back in the controller after submitting the form, which confirms the problem is the List<Link>
How can I add a form control for populating the property Exercise.Link (which is a List<Link>)?
After reading this answer to another question, I figured out what the problem was:
The class Link, on which the property Exercise.Links depends, has no setter for its Link.Name and Link.Value properties, so the binder cannot populate them when you submit the form.
This is due to Link being a domain model. The solution in this case was to create a presentation layer model that has public {get; set;} and use that on the form instead of the domain model.
So, to answer the question properly, this is how you could write a form control to edit a List<Link>:
#for (var i = 0; i < Model.Input.Links.Count; i++)
{
<div>
<input asp-for="Input.Links[i].Name">
<input asp-for="Input.Links[i].Value">
</div>
}
I am beginning to wonder if what I am about to ask is even possible.. But it SHOULD be.
The MS examples are woefully short of what I am after so maybe EF isn't designed the way I thought. (BTW Scaffold for CRUD for each individual table works great. But it only does table level creation. I want something that manages the relation as an object.)
public class ContactPhone
{
public ContactPhone(ContactPhone contactPhone)
{
Id = contactPhone.Id;
ContactId = contactPhone.ContactId;
ContactPhoneTypeId = contactPhone.ContactPhoneTypeId;
Number = contactPhone.Number;
}
//private ContactPhone contactPhone;
public ContactPhone()
{
}
//public ContactPhone(ContactPhone contactPhone)
//{
// this.contactPhone = contactPhone;
//}
public int Id { get; set; }
public int ContactId { get; set; }
public virtual Contact Contact { get; set; }
public int ContactPhoneTypeId { get; set; }
public virtual ContactPhoneType ContactPhoneType { get; set; }
public string Number { get; set; }
}
public class Contact
{
public Contact()
{
Phones = new List<ContactPhone>();
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int LocationAddressId { get; set; }
public LocationAddress LocationAddress { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual SCPI_site_User User { get; set; }
//public ICollection
public List<ContactPhone> Phones { get; set; }
}
I know this is wrong but it kinda works...my assumption was that my Contact was persistent and passed back and forth between the Razor page, however that doesn't seem to be the case.
public class CreateModel : PhoneTypesPageModel
{
private readonly SCPI_Site.Areas.SCPI_SiteContext _context;
public CreateModel(SCPI_Site.Areas.SCPI_SiteContext context)
{
_context = context;
}
[TempData]
public string Message { get; set; }
[BindProperty]
public Contact Contact { get; set; }
[BindProperty]
public ContactPhone ContactPhone { get; set; }
//public bool HasPhones => Contact.Phones.Count > 0;
public IActionResult OnGet()
{
PopulatePhoneTypeDropDownList(_context);
int userID = User.Identity.GetUserId<int>();
if (Contact == null)
{
Contact = new Contact();
}
if (ContactPhone == null)
{
Contact = new Contact();
}
Contact.UserId = userID;
ViewData["UserId"] = userID;
return Page();
}
public async Task<IActionResult> OnPostAsync(string submit)
{
int userID = User.Identity.GetUserId<int>();
Contact.UserId = userID;
switch (submit)
{
case "AddPhone":
// (failed attempt) _context.ContactPhone.Add(ContactPhone);
Contact.Phones.Add(new ContactPhone(ContactPhone));
ContactPhone.Number = "";
return Page();
default:
//if (!ModelState.IsValid)
//{
// return Page();
//}
_context.Contact.Add(Contact);
await _context.SaveChangesAsync();
Message = "Contact Created!";
return RedirectToPage("./Index");
}
}
}
}
HTML:
#page
#model SCPI_Site.Areas.ContactModel.Pages.Contacts.CreateModel
#{
//Layout = "~/Views/Shared/_AdminLayout.cshtml";
ViewData["Title"] = "Create";
}
<div class="col">
<h4>Contact</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Contact.UserId" />
<div class="row">
<div class="form-group col-md-6">
<label asp-for="Contact.FirstName" class="control-label"></label>
<input asp-for="Contact.FirstName" class="form-control" />
<span asp-validation-for="Contact.FirstName" class="text-danger"></span>
</div>
<div class="form-group col-md-6">
<label asp-for="Contact.LastName" class="control-label"></label>
<input asp-for="Contact.LastName" class="form-control" />
<span asp-validation-for="Contact.LastName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Contact.Email" class="control-label"></label>
<input asp-for="Contact.Email" class="form-control" />
<span asp-validation-for="Contact.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Contact.LocationAddress.Address" class="control-label"></label>
<input asp-for="Contact.LocationAddress.Address" class="form-control" />
<span asp-validation-for="Contact.LocationAddress.Address" class="text-danger"></span>
</div>
<div class="row">
<div class="form-group col-md-4">
<label asp-for="Contact.LocationAddress.City" class="control-label"></label>
<input asp-for="Contact.LocationAddress.City" class="form-control" />
<span asp-validation-for="Contact.LocationAddress.City" class="text-danger"></span>
</div>
<div class="form-group col-md-4">
<label asp-for="Contact.LocationAddress.State" class="control-label"></label>
<input asp-for="Contact.LocationAddress.State" class="form-control" />
<span asp-validation-for="Contact.LocationAddress.State" class="text-danger"></span>
</div>
<div class="form-group col-md-4">
<label asp-for="Contact.LocationAddress.PostalCode" class="control-label"></label>
<input asp-for="Contact.LocationAddress.PostalCode" class="form-control" />
<span asp-validation-for="Contact.LocationAddress.PostalCode" class="text-danger"></span>
</div>
</div>
#if (Model.Contact.Phones != null)
{
<hr />
<table class="table">
<thead>
<tr>
<th>
Type
#*#Html.DisplayNameFor(model => model.Contact.Phones[0].ContactPhoneType)*#
</th>
<th>
Number
#*#Html.DisplayNameFor(model => model.Contact.Phones[0].Number)*#
</th>
<th></th>
</tr>
</thead>
<tbody>
#if (Model.Contact.Phones != null)
{
#foreach (var item in Model.Contact.Phones)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ContactPhoneType.Id)
</td>
<td>
#Html.DisplayFor(modelItem => item.Number)
</td>
<td>
<a asp-page="../ContactPhones/Edit" asp-route-id="#item.Id">Edit</a> |
<a asp-page="../ContactPhones/Details" asp-route-id="#item.Id">Details</a> |
<a asp-page="../ContactPhones/Delete" asp-route-id="#item.Id">Delete</a>
</td>
</tr>
}
}
</tbody>
</table>
}
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
<div id="pay-invoice" class="card">
<div class="card-body">
<div class="card-title">
<input type="hidden" id="x_card_num" name="x_card_num" value="">
#*<partial name="../ContactPhones/Index" model=#Model />*#
#*First name:
<input type="text" name="A" value="<%= ViewData[" A"] %>" />
<br />
Last name:
<input type="text" name="B" value="<%= ViewData[" B"] %>" />
<br />
<input type="submit" value="Insert" />
<button type="submit" name="submit" value="add"><span class="glyphicon glyphicon-plus"></span>Add another</button>*#
<input type="hidden" asp-for="ContactPhone.ContactId" />
<div class="row">
<div class="form-group col-md-7">
<label asp-for="ContactPhone.Number" class="control-label"></label>
<input asp-for="ContactPhone.Number" class="form-control" />
<span asp-validation-for="ContactPhone.Number" class="text-danger"></span>
</div>
<div class="form-group col-md-5">
<label asp-for="ContactPhone.ContactPhoneType" class="control-label"></label>
<select asp-for="ContactPhone.ContactPhoneTypeId" class="form-control"
asp-items="#Model.PhoneTypeSL">
#*<option value="">-- Select Type --</option>*#
</select>
<span asp-validation-for="ContactPhone.ContactPhoneTypeId" class="text-danger" />
</div>
</div>
<button type="submit" name="submit" value="AddPhone" class="btn btn-primary"><span class="fa fa-plus"> </span> Add Phone Number</button>
#*<a asp-area="ContactsModel" asp-page="/ContactPhones/Create"> Add Phone Number</a>*#
</div>
</div>
</div>
<p>
#* <a asp-page="Create">Create New</a>*#
</p>
#*#Html.Hidden("Contact.UserId") This breaks the validation*#
#*<div class="form-group">
<label asp-for="Contact.UserId" class="control-label"></label>
<select asp-for="Contact.UserId" class="form-control" asp-items="ViewBag.UserId"></select>
</div>*#
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
Create:
Given this little model, how do I create a razor page that will add phone records to the Contact object and capture the Contact Object data and create the entire model with one Save call. (manage the keys etc)
Now I could be way off base here but this type of scenario is what I would expect from an ORM. Frankly I'm not sure why anyone would build the EF and not provide this type of functionality. But likely I am missing something and I have poured over the MS docs many times searching for clues.
But it works that way in Django ;)
If there is even one example of this type of operation out there that isn't the same customer one dimensional example I would appreciate it.
Thanks
CB
If you want to bind items to a property that's a collection of some kind, you need to add indexers to the name of the form fields or the expression that you pass to the asp-for attribute of a taghelper, beginning with 0.
Here's a simpler model:
public class Order
{
public int OrderId { get; set; }
public string Customer { get; set; }
public List<OrderItem> Items { get; set; }
}
public class OrderItem
{
public int OrderItemId { get; set; }
public string Item { get; set; }
public decimal Price { get; set; }
}
Here's the PageModel:
public class CreateModel : PageModel
{
[BindProperty]
public Order Order { get; set; }
public void OnPost()
{
}
}
Here is the form:
#page
#model CreateModel
#{
}
<form method="post">
<input asp-for="Order.Customer" /><br />
<input asp-for="Order.Items[0].Item" /><br/>
<input asp-for="Order.Items[0].Price" />
<input type="submit"/>
</form>
That is all you need. The values from the form will automatically be bound to the Order property marked with the [BindProperty] attribute. If you pass that to EF, it will create and save an order, and then use the ID to create and save an OrderItem. If you want to allow the user to create more items, just increment the indexer by 1 for each item:
<form method="post">
<input asp-for="Order.Customer" /><br />
#for (var i = 0; i < 5; i++)
{
<input asp-for="Order.Items[i].Item" /><br/>
<input asp-for="Order.Items[i].Price" />
}
<input type="submit"/>
</form>
Ref: https://www.learnrazorpages.com/razor-pages/model-binding#binding-complex-collections