CBERBER
CBERBER

Reputation: 43

How Do I make a Razor Form for a complex EF Model

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

Upvotes: 2

Views: 644

Answers (1)

Mike Brind
Mike Brind

Reputation: 30075

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

Upvotes: 1

Related Questions