I have a ViewModel named CreateRoleViewModel which contains the items shown below. On the view, I'm iterating through a list of permissions and displaying them. What I'm trying to accomplish is to allow a user to create a new role and add permissions to that role. I've been able to successfully pass all data to the controller except the permissions. Hopefully I'm not confusing anyone. I've tried to use a for loop (which did't produce correct results) and a foreach loop, which did produce the correct results (meaning displayed all permissions currently available). I realize I'm going to also, most likely need to update my domain model for Permissions to include columns for View, Modify etc.
CreateViewModel
public class CreateRoleViewModel
{
[Required]
public string RoleName { get; set; }
public string RoleDescription { get; set; }
public List<Permissions> Permissions { get; set; }
public bool AllowViewAccess { get; set; }
public bool AllowModifyAccess { get; set; }
public CreateRoleViewModel()
{
Permissions = new List<Permissions>();
}
}
PermissionsController
[Route("Administration/Permissions/CreateRole")]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateRole(CreateRoleViewModel newRole)
{
try
{
var test = newRole;
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
View
<form method="post" asp-action="CreateRole">
<div class="form-group col-6" style="padding-left:0">
<label asp-for="RoleName"></label>
<input asp-for="RoleName" class="form-control" />
<span asp-validation-for="RoleName" class="text-danger"></span>
</div>
<div class="form-group col-6" style="padding-left:0">
<label asp-for="RoleDescription"></label>
<input asp-for="RoleDescription" class="form-control" />
</div>
<div class="form-group ">
<h4>Permissions</h4>
<div class="card">
<div class="card-header">
<input type="checkbox" class="form-check-input p-2" id="selectAll">
<label class="form-check-label" for="selectAll">Select All<span style="font-weight:bold;font-style:italic;"> (This will allow read/write access to ALL selected items.)</span></label>
</div>
</div>
<!--Permissions should go here-->
<div class="card-body border">
#foreach (var item in Model.Permissions)
{
<div class="row" style="border-radius:3px;border-top:2px solid gray;border-bottom:2px solid gray;padding:15px;">
<div class="col-8">
<input type="checkbox" class="form-check-input p-2" id="select">
<label class="form-check-label" for="select" style="font-weight:bold;">#item.PermissionName</label>
<p style="color:gray;">#item.PermissionDescription</p>
</div>
<div class="col-2">
<input type="checkbox" class="form-check-input p-2" id="readOnly" asp-for="AllowViewAccess">
<label class="form-check-label" for="readOnly">View</label>
</div>
<div class="col-2">
<input type="checkbox" class="form-check-input p-2" id="readWrite" asp-for="AllowModifyAccess">
<label class="form-check-label" for="readWrite">Modify</label>
</div>
</div>
}
</div>
<div asp-validation-summary="All" class="text-info"></div>
<button style="background-color: #6987D5;color:white;" class="btn">Save</button>
<button class="btn" style="background-color:lightgray;border:1px solid darkgray">Cancel</button>
</div>
</form>
From your description and the view code, I think you should put the "AllowViewAccess " and "AllowModifyAccess " in the Permissions class, because the role have separate "View" or "Modify" access to different permissions. Based on your codes, I made an example:
Model:
public class CreateRoleViewModel
{
[Required]
public string RoleName { get; set; }
public string RoleDescription { get; set; }
public List<Permissions> Permissions { get; set; }
public CreateRoleViewModel()
{
Permissions = new List<Permissions>();
}
}
public class Permissions
{
public string PermissionName { get; set; }
public string PermissionDescription { get; set; }
public bool AllowViewAccess { get; set; }
public bool AllowModifyAccess { get; set; }
}
View:
#model CreateRoleViewModel
#{
var i = 0;
}
<form method="post" asp-action="CreateRole">
<div class="form-group col-6" style="padding-left:0">
<label asp-for="RoleName"></label>
<input asp-for="RoleName" class="form-control" />
<span asp-validation-for="RoleName" class="text-danger"></span>
</div>
<div class="form-group col-6" style="padding-left:0">
<label asp-for="RoleDescription"></label>
<input asp-for="RoleDescription" class="form-control" />
</div>
<div class="form-group ">
<h4>Permissions</h4>
<div class="card">
<div class="card-header">
<input type="checkbox" class="form-check-input p-2" id="selectAll">
<label class="form-check-label" for="selectAll">Select All<span style="font-weight:bold;font-style:italic;"> (This will allow read/write access to ALL selected items.)</span></label>
</div>
</div>
<!--Permissions should go here-->
<div class="card-body border">
#foreach (var item in Model.Permissions)
{
<div class="row" style="border-radius:3px;border-top:2px solid gray;border-bottom:2px solid gray;padding:15px;">
<div class="col-8">
<input type="checkbox" class="form-check-input p-2" id="select">
<input type="hidden" asp-for="#Model.Permissions[i].PermissionName">
<label class="form-check-label" for="select" style="font-weight:bold;">#item.PermissionName</label>
<p style="color:gray;">#item.PermissionDescription</p>
<input type="hidden" asp-for="#Model.Permissions[i].PermissionDescription">
</div>
<div class="col-2">
<input type="checkbox" class="form-check-input p-2" id="readOnly" asp-for="#Model.Permissions[i].AllowViewAccess">
<label class="form-check-label" for="readOnly">View</label>
</div>
<div class="col-2">
<input type="checkbox" class="form-check-input p-2" id="readWrite" asp-for="#Model.Permissions[i].AllowModifyAccess">
<label class="form-check-label" for="readWrite">Modify</label>
</div>
</div>
i++;
}
</div>
<div asp-validation-summary="All" class="text-info"></div>
<button style="background-color: #6987D5;color:white;" class="btn">Save</button>
<button class="btn" style="background-color:lightgray;border:1px solid darkgray">Cancel</button>
</div>
</form>
Controller:
public IActionResult Index()
{
CreateRoleViewModel createRoleViewModel = new CreateRoleViewModel();
return View(createRoleViewModel);
}
//[Route("Administration/Permissions/CreateRole")]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateRole(CreateRoleViewModel newRole)
{
try
{
var test = newRole;
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
Result:
Related
I send a request to the web service and the response that I get is a list of names and values. I loop them in the view and render a form, to allow users to fill the fields and submit it. But the viewmodel always returns null in post action.
this is my View:
<form method="post">
<div class="row register-form">
#foreach (var item in Model.FlowOutputModel)
{
<div class="col-md-6">
<div class="form-group">
<label for="#item.CharacteristicValue">
#item.CharacteristicName:
</label>
<input asp-for="#item.CharacteristicValue" type="text"
class="form-control"
placeholder="#item.CharacteristicName *" value="" required/>
</div>
</div>
}
<div class="col-md-12">
<input type="submit" class="btnRegister" value="Submit"/>
</div>
</div>
</form>
my model as followin:
public class DecisionInputModel
{
public string FlowName { get; set; }
public List<FlowOutputModel> FlowOutputModel { get; set; }
}
public class FlowOutputModel
{
public string CharacteristicName { get; set; }
public string CharacteristicValue { get; set; }
}
My Controller
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(DecisionInputModel decisionInput)
{
if (ModelState.IsValid)
{
_service.GetResults(decisionInput);
}
}
Any suggestions?
replace loop foreach by for and add action to form
<form method="post" action="#Url.Action("Index", "Home")">
<input asp-for="#Model.FlowName" type="text" class="form-control" />
// or maybe even better
#Html.TextBoxFor(model => model.FlowName, new { #class = "form-control" })
<div class="row register-form">
#for(int i=0; i < Model.FlowOutputModel.Count; i++)
{
<div class="col-md-6">
<div class="form-group">
<label for="#Model.FlowOutputModel[i].CharacteristicValue">
#Model.FlowOutputModel[i].CharacteristicName:
</label>
<input asp-for="#Model.FlowOutputModel[i].CharacteristicValue" type="text"
class="form-control"
placeholder="#Model.FlowOutputModel[i].CharacteristicName *" value="" required/>
</div>
</div>
}
<div class="col-md-12">
<input type="submit" class="btnRegister" value="Submit"/>
</div>
</div>
</form>
You have to add the "name" attribute to the input:
<input name="FlowOutputModel[#Model.FlowOutputModel.IndexOf(item)].CharacteristicValue" type="text" class="form-control" placeholder="#item.CharacteristicName *" value="" required />
My HttpPost Edit task is not giving me my list of EventMembers. I put a watch on the GET for my Edit task and it reads my EventMembers just fine. but when i get my POST Edit my EventMembers only shows System.Collections.Generic.List`1[System.String] in my watch window, as well as my input box in my view. Whats happening?
Model:
public class Event
{
[Required]
public int EventId { get; set; }
[ForeignKey("UserId")]
public virtual SchedulerUser SchedulerUser { get; set; }
[MaxLength(50)]
public string EventCreator { get; set; }
public List<string> EventMembers { get; set; }
[Required]
[MaxLength(100)]
public string Subject { get; set; }
[MaxLength(400)]
public string Description { get; set; }
[Required]
public DateTime StartTime { get; set; }
public DateTime? EndTime { get; set; }
[Required]
public bool IsFullDay { get; set; }
[Required]
public bool AcceptOrDecline { get; set; }
}
Controller:
// GET: Events/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var #event = await _context.Events.FindAsync(id);
if (#event == null)
{
return NotFound();
}
return View(#event);
}
// POST: Events/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("EventId,UserId,EventCreator,EventMembers,Subject,Description,StartTime,EndTime,IsFullDay,AcceptOrDecline")] Event #event)
{
if (id != #event.EventId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
#event.SchedulerUser = await _userManager.GetUserAsync(HttpContext.User);
_context.Update(#event);
if (#event.AcceptOrDecline == false)
{
_context.Remove(#event);
}
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EventExists(#event.EventId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(#event);
}
View:
#model Scheduler.Models.Event
#{
ViewData["Title"] = "Edit";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Edit</h1>
<h4>Event</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="EventId" />
<div class="form-group">
<label asp-for="Subject" class="control-label"></label>
<input asp-for="Subject" class="form-control" />
<span asp-validation-for="Subject" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EventMembers" class="control-label"></label>
<input asp-for="EventMembers" class="form-control" />
<span asp-validation-for="EventMembers" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartTime" class="control-label">Start Time</label>
<input asp-for="StartTime" class="form-control" />
<span asp-validation-for="StartTime" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EndTime" class="control-label">End Time</label>
<input asp-for="EndTime" class="form-control" />
<span asp-validation-for="EndTime" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="IsFullDay" /> All Day
</label>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="AcceptOrDecline" /> Accepted
</label>
<p style="color:red; font-weight:bold;">Uncheck this to decline invitation. Event will be removed from your schedule.</p>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Since your EventMembers model property is an Enumerable list, the Html control which represents the property should be indexed.
<div class="form-group">
<label asp-for="EventMembers[0]" class="control-label"></label>
<input asp-for="EventMembers[0]" class="form-control" />
<span asp-validation-for="EventMembers[0]" class="text-danger"></span>
</div>
<div class="form-group">
...
<input asp-for="EventMembers[1]" class="form-control" />
...
</div>
The best practice is to generate the control inside a loop.
#for (int i = 0; i < Model.EventMembers.Count; i++)
{
<div class="form-group">
...
<input asp-for="EventMembers[i]" class="form-control" />
...
</div>
}
I'm not understanding how to properly bind a single item from a collection. In my project, a student can have multiple plans with these models:
public class Student
{
public Student()
{
Plans = new HashSet<Plan>();
}
public int StudentId { get; set; }
public string Designee { get; set; }
public string DesigneePhone { get; set; }
public virtual ICollection<Plan> Plans { get; set; }
}
public partial class Plan
{
public int PlanId { get; set; }
public int StudentId { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public virtual Student Student { get; set; }
}
I'm listing each plan with its own Save button on the page. I thought I was using the asp-for helper correctly according to this Learn Razor Pages post.
#page "{studentId:int}"
#model CollectionTest.PlansModel
<form asp-page-handler="SaveMain" method="post">
<div class="row mt-3">
<input type="hidden" asp-for="Student.StudentId" />
<div class="col">
<label asp-for="Student.Designee"></label>
<input asp-for="Student.Designee" class="form-control" />
</div>
<div class="col">
<label asp-for="Student.DesigneePhone"></label>
<input asp-for="Student.DesigneePhone" class="form-control" />
</div>
<div class="col-auto align-self-end">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</div>
</form>
<hr />
#for (int i = 0; i < Model.Student.Plans.Count(); i++)
{
var planId = Model.Student.Plans.ToList()[i].PlanId.ToString();
<div id="#("card_Plan" + planId)" class="card">
<div class="card-header">
<h5>Plan ##planId</h5>
</div>
<div class="card-body">
<form id="#("form_Plan" + planId)" asp-page-handler="SavePlan" method="post">
<input type="hidden" asp-for="Student.Plans.ToList()[i].PlanId" />
<div class="row">
<div class="col">
<label asp-for="Student.Plans.ToList()[i].StartDate"></label>
<input asp-for="Student.Plans.ToList()[i].StartDate" class="form-control" />
<span asp-validation-for="Student.Plans.ToList()[i].StartDate"></span>
</div>
<div class="col">
<label asp-for="Student.Plans.ToList()[i].EndDate"></label>
<input asp-for="Student.Plans.ToList()[i].EndDate" class="form-control" />
<span asp-validation-for="Student.Plans.ToList()[i].EndDate"></span>
</div>
</div>
</form>
</div>
<div class="card-footer">
<div class="float-right">
<button type="submit" class="btn btn-primary" form="#("form_Plan" + planId)">Save</button>
</div>
</div>
</div>
}
But it seems like nothing is being bound in the SavePlan page handler:
[BindProperty]
public Student Student { get; set; }
.
.
.
public async Task<IActionResult> OnPostSavePlan(int planId)
{
if (!ModelState.IsValid)
{
return Page();
}
/******************************
*
* Student.Plans is empty and planId is zero
*
*******************************/
Plan plan = await _planContext.Plan.FirstAsync(p => p.PlanId == planId);
_planContext.Attach(plan).State = EntityState.Modified;
try
{
await _planContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!PlanExists(plan.PlanId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Plans");
}
What am I doing wrong?
According to your code, because what you pass in the razor page is [0].PlanId, but what you receive in the OnPostSavePlan method is planId. If you only need to receive planId, you need to change the form.
<div class="card-body">
<form id="#("form_Plan" + planId)" asp-page-handler="SavePlan" method="post">
<input type="hidden" asp-for="#planId" />
From your requirement,it seems you want to pass a list of plans. So you need to modify your code in the following steps:
Plans.cshtml:
<form id="form_Plan" asp-page-handler="SavePlan" method="post">
#for (int i = 0; i < Model.Student.Plans.Count(); i++)
{
var planId = Model.Student.Plans.ToList()[i].PlanId.ToString();
<div id="#("card_Plan" + planId)" class="card">
<div class="card-header">
<h5>Plan ##planId</h5>
</div>
<div class="card-body">
<input type="hidden" asp-for="Student.Plans.ToList()[i].PlanId" />
<div class="row">
<div class="col">
<label asp-for="Student.Plans.ToList()[i].StartDate"></label>
<input asp-for="Student.Plans.ToList()[i].StartDate" class="form-control" />
<span asp-validation-for="Student.Plans.ToList()[i].StartDate"></span>
</div>
<div class="col">
<label asp-for="Student.Plans.ToList()[i].EndDate"></label>
<input asp-for="Student.Plans.ToList()[i].EndDate" class="form-control" />
<span asp-validation-for="Student.Plans.ToList()[i].EndDate"></span>
</div>
</div>
</div>
</div>
}
<div class="card-footer">
<div class="float-right">
<button type="submit" class="btn btn-primary" form="form_Plan">Save</button>
</div>
</div>
</form>
Plans.cshtml.cs:
public class PlansModel : PageModel
{
public IActionResult OnGet()
{
//for easy testing,I add the data manually
Student = new Student()
{
Plans = new List<Plan>()
{
new Plan(){ PlanId=1, StartDate=DateTime.Parse("2019-8-7")},
new Plan(){ PlanId=2, StartDate=DateTime.Parse("2019-4-7")},
new Plan(){ PlanId=3, StartDate=DateTime.Parse("2019-6-7")}
}
};
return Page();
}
[BindProperty]
public Student Student { get; set; }
public async Task<IActionResult> OnPostSavePlan(List<Plan> plans)
{
//...
}
}
Result:
Update:
Plans.cshtml:
#foreach (var plan in Model.Student.Plans)
{
var planId = plan.PlanId.ToString();
<div id="#("card_Plan" + planId)" class="card">
<div class="card-header">
<h5>Plan ##planId</h5>
</div>
<div class="card-body">
<form id="#("form_Plan" + planId)" asp-page-handler="SavePlan" method="post">
<input type="hidden" asp-for="#plan.PlanId" />
<div class="row">
<div class="col">
<label asp-for="#plan.StartDate"></label>
<input asp-for="#plan.StartDate" class="form-control" />
<span asp-validation-for="#plan.StartDate"></span>
</div>
<div class="col">
<label asp-for="#plan.EndDate"></label>
<input asp-for="#plan.EndDate" class="form-control" />
<span asp-validation-for="#plan.EndDate"></span>
</div>
</div>
</form>
</div>
<div class="card-footer">
<div class="float-right">
<button type="submit" class="btn btn-primary" form="#("form_Plan" + planId)">Save</button>
</div>
</div>
</div>
}
Plans.cshtml.cs:
public async Task<IActionResult> OnPostSavePlan(Plan plan)
{
//...
}
Result:
I'm trying to add a "Create" to one of my Controllers (LeagueController), so I created a Create View, which uses my League object.
However, when I go to submit the form, it does not redirect back to the Index view, as I want it to, nor does it enter the log entry to say that I created a league.
League Class
public class League : BaseEntity
{
[Required]
[DataType(DataType.Text)]
public string LeagueName { get; set; }
[Required]
[DataType(DataType.Text)]
public string LeagueInitials { get; set; }
[DataType(DataType.Text)]
public string LeagueURL { get; set; }
[DataType(DataType.DateTime)]
public DateTime Founded { get; set; }
[InverseProperty("League")]
public ICollection<Team> Teams { get; set; }
[ForeignKey("LeagueID")]
public ICollection<LeagueOwners> LeagueOwners { get; set; }
}
LeaguesController Class
public class LeaguesController : Controller
{
private MyDBContext context;
private ILogger logger;
public LeaguesController(MyDBContext context, ILogger logger)
{
this.context = context;
this.logger = logger;
}
public IActionResult Index()
{
this.logger.LogInformation("Reached League Index");
return View();
}
[Route("Create")]
public IActionResult Create()
{
this.logger.LogInformation("Creating a league");
return View();
}
[HttpPost]
public IActionResult Create(League league)
{
this.logger.LogInformation("Create button clicked!");
return this.RedirectToAction("Index");
}
}
Create.cshtml
#model MySite.Core.Entities.League
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create</h2>
<h4>League</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="LeagueName" class="control-label"></label>
<input asp-for="LeagueName" class="form-control" />
<span asp-validation-for="LeagueName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LeagueInitials" class="control-label"></label>
<input asp-for="LeagueInitials" class="form-control" />
<span asp-validation-for="LeagueInitials" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LeagueURL" class="control-label"></label>
<input asp-for="LeagueURL" class="form-control" />
<span asp-validation-for="LeagueURL" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Founded" class="control-label"></label>
<input asp-for="Founded" class="form-control" />
<span asp-validation-for="Founded" class="text-danger"></span>
</div>
<div class="form-group" hidden="hidden">
<label asp-for="Created" class="control-label"></label>
<input asp-for="Created" class="form-control" value="#DateTime.Now" />
<span asp-validation-for="Created" class="text-danger"></span>
</div>
<div class="form-group" hidden="hidden">
<label asp-for="Modified" class="control-label"></label>
<input asp-for="Modified" class="form-control" value="#DateTime.Now" />
<span asp-validation-for="Modified" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
The first way is that you could delete [Route("Create")]which is on your get method.
The second way is that you could add [Route] attribute to your post method like below:
[Route("Create")]
public IActionResult Create()
{
this.logger.LogInformation("Creating a league");
return View();
}
[Route("Create")]
[HttpPost]
public IActionResult Create(League league)
{
this.logger.LogInformation("Create button clicked!");
return this.RedirectToAction("Index");
}
In your form <form asp-action="Create" method="post"> add a tag to point to the correct controller asp-controller="Leagues"
Do not enforce the route, that's just anti-pattern.
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