Search code examples
c#asp.net.netentity-framework-coreasp.net-core-mvc

Trouble when implementing Models in an ASP.NET MVC architecture


To give you some context.

I have been trying to create a simple ASP.NET MVC application using razor pages.

Now I have understood the idea of a view model and how they are used in order to display data in a Razor page.

Thing is.. Now I want to use a Razor page to create values that are related among each other.

Let me dig deeper into it. Right now I have 2 classes: Personas (people) and Hijos (children). The idea is simple.

People can have many children but children are children of only one parent, in this a persona.

I have created a display and another page that displays the sons of that person that's all said and done.

Problem is when I try to create a children and hook it to a parent.

These are the relevant classes:

public class Persona
{
    public int Id { get; set; }
    public string Nombre { get; set; } = null!;
    public string Apellido { get; set; } = null!;
    public string Email { get; set; } = null!;
    public string Telefono { get; set; } = null!;
    
    //Hijo
    public List<Hijo>? Hijos { get; set; }
}

public class Hijo
{
    public int Id { get; set; }
    public string Nombre { get; set; }
    public DateOnly FechaNacimiento { get; set; } = DateOnly.FromDateTime(DateTime.Now);
    
    //Navigation Properties
    public int PersonaId { get; set; }
    public Persona Persona { get; set; } = null!;
}

I have created a view model that goes like this:

public class CrearHijoViewModel
{
    public string Nombre { get; set; }

    public DateOnly FechaNacimiento { get; set; }
    
    public List<Persona> Padres { get; set; }
}

It basically holds the data that will be displayed in the create view.

This is the controller:

[HttpPost]
public async Task<IActionResult> Create(Hijo hijo)
{
    Console.WriteLine(hijo);

    if (ModelState.IsValid)
    {
        _context.Add(hijo);
        await  _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }

    var padres = await _context.Personas.ToListAsync();

    var newCrearHijoModel = new CrearHijoViewModel
        {
            Nombre = hijo.Nombre,
            FechaNacimiento = hijo.FechaNacimiento,
            Padres = padres
        };

    return View(newCrearHijoModel);
}

And this is the form:

<form asp-controller="Hijos" asp-action="Create" method="post">
    <div>
        <label>Nombre</label>
        <input type="text" asp-for="Nombre">
    </div>
    <div>
        <label>Fecha de Nacimiento</label>
        <input type="date" asp-for="FechaNacimiento">
    </div>
    <select class="form-control" asp-for="Padres">
        @foreach (var padre in Model.Padres)
        {
            <option value="@padre.Id">@padre.Nombre @padre.Apellido</option> 
        }
    </select>
    <input type="submit" value="Crear Hijo">
</form>

As you can see its a fairly simple.. idea. I am not sure about my implementation.

Trouble arises when I try to submit my data. It gets rejected. I am not sure if its because I am receiving a Hijo model whereas the Razor page deals with a CrearHijoViewModel.

I understand this could be a problem but if it is.... How do I fix it? Should I create a new Hijo object and input that into the context? Is it my view model or my Razor page wrong?

Any help or guidance is highly appreciated!


Solution

  • The issue we are facing is created by Model Binding. In the Post method, we are ready to receive Hijo but in the form, we are posting Nombre Padres which is not matching the properties defined in Hijo. There are 2 approaches for the issue, 1 change our Controller action to receive CrearHijoViewModel, 2 change our form to submit Hijo.

    But if we want to use approach 2, we can't use tag-helper like asp-for="Padres" as this is bind to the model of the View which is CrearHijoViewModel. We can use a form similar to below to submit a Hijo and since then we don't need to change the Controller action.

    <form asp-controller="Hijos" asp-action="Create" method="post">
        <div>
            <label>Nombre</label>
            <input type="text" asp-for="Nombre">
            @* asp-for="Nombre" will help generate element attribute id="Nombre" name="Nombre" *@
            @* since Model Hijos has property Nombre too, we can still use asp-for="Nombre" *@
        </div>
        <div>
            <lable>Persona Nombre </lable>
            <input type="text" name="Persona.Nombre">
        </div>
        <div>
            <lable>Persona Email </lable>
            <input type="text" name="Persona.Email">
        </div>
        <input type="submit" value="create child">
    </form>
    

    I created a sample in my side and I saw it could work well.

    enter image description here