Home > Back-end >  .NET EF 4.7.2 MVC Create method converts child form submission
.NET EF 4.7.2 MVC Create method converts child form submission

Time:11-05

I'm trying to implement a create functionality for my PC Components ASP.NET MVC application using EF 4.7.2 and inheritance to handle all derived classes in a single method.

The problem is submitting the Component_CreateCPU.cshtml form converts the derived class of CPU to its base class Component in the /Components/Create action.

I tested instantiating a new CPU object in Index() and passing it into the Create() method and it retained it's derived class.

Is there any way to submit the view form and ensure the derived class is being passed in?

Model classes:

public class Component : Interfaces.IComponent
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    [DisplayName("Name")]
    public string Name { get; set; }
    [DisplayName("Description")]
    public string Description { get; set; }
    [DisplayName("Price")]
    public decimal Price { get; set; }

    public Manufacturer Manufacturer { get; set; }
}

public class CPU : Component
{
    [DisplayName("Core Count")]
    public int CoreCount { get; set; }
    [DisplayName("Core Clock")]
    public string CoreClock { get; set; }
}

Create partial view

_Component_CreateCPU.cshtml:

@model PCDB.Models.Components.CPU

@using (Html.BeginForm("Create", "Components", FormMethod.Post)) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>CPU</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Price, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Price, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Price, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.CoreCount, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.CoreCount, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.CoreCount, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.CoreClock, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.CoreClock, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.CoreClock, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

ComponentsController:

public class ComponentsController : Controller
{
    private readonly IComponentRepository<Component> _componentRepository;
    
    public ComponentsController()
    {
        _componentRepository = new ComponentsRepository<Component>();

    }

    public ActionResult Index()
    {
        return View(_componentRepository.GetAll());
    }

    [Authorize(Roles = "Admin")]
    public ActionResult Create()
    {
        return View(new ComponentCreateViewModel());
    }

    [Authorize(Roles = "Admin")]
    [HttpPost]
    public ActionResult Create(Component component)
    {
        if (ModelState.IsValid)
        {
            _componentRepository.Insert(component);
            _componentRepository.Save();
        }

        return Content("Success");
    }
}

CodePudding user response:

When you post a form to the controller, the browser isn't serializing an entity, it is merely passing the fields which are married up against the properties of the object your controller method accepts, or the field names of parameters in that controller method.

So in your case your controller is expecting a base class Component, and that is all it will receive, not an instance of a CPU or other subclass. (Though it may be worth testing what happens if Component is made abstract) My suggestion would be to implement methods per sub-class. If there are bits that are applicable at the component level, pass those through after receiving the subclass to a common method:

    [Authorize(Roles = "Admin")]
    [HttpPost]
    public ActionResult CreateCPU(CPU cpu)
    {
        if (ModelState.IsValid)
        {
            _componentRepository.Insert(cpu);
            _componentRepository.Save();
        }

        return Content("Success");
    }

ComponentRepository can still accept Insert(Component) provided it can ultimately ensure the correct DbSet is referenced, or that DbContext.Entity<T> will resolve the passed in CPU vs. other components.

  • Related