Search code examples
c#razorasp.net-core-mvc

Having a problem with ASP.NET Core MVC passing value from view to controller


I have a view that is designed to delete from the database.

The view is populated correctly. When I click the Delete button, the ID on the view is not passed to the controller. When I have a breakpoint in the controller has received a null value from the view.

In the view when I removed the hidden from this <input asp-for="Form.Id" hidden /> the Id is visible. Yet when I click on the Delete button, the Id passed is null.

My model:

namespace PricingParameters.Models
{
    public class Form
    {
        [Key]
        public int Id { get; set; }

        [Required]
        [Display(Name = "Branch")]
        public int BranchId { get; set; } = 0;

        [ValidateNeverAttribute]
        public Branch Branch { get; set; }

        [Required]
        public int FormTypeId { get; set; } = 0;

        [ValidateNeverAttribute]
        public FormType FormType { get; set; }

        [Required]
        [Display(Name = "Form Cost")]
        [DisplayFormat(DataFormatString = "{0:N2}", ApplyFormatInEditMode = true)]
        public double Value { get; set; } = 0;

        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Modified Date")]
        public DateTime ModifedDateTime { get; set; } = DateTime.Now;

        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Created Date")]
        public DateTime CreatedDateTime { get; set; } = DateTime.Now;
    }
}

My view model:

namespace PricingParameters.Models.ViewModels
{
    public class FormVM
    {
        public required Form Form { get; set; }
        [ValidateNever]
        public required IEnumerable<SelectListItem> BranchList { get; set; }
        [ValidateNever]
        public required IEnumerable<SelectListItem> FormTypeList { get; set; }
    }
}

My view:

@model PricingParameters.Models.ViewModels.FormVM

@{
    ViewData["Title"] = "Form Cost Delete";
}

<form method="post" asp-action="Delete">
    <input asp-for="Form.Id" hidden />
    <div class="border p-3 mt-4">
        <div class="row pb-2">
            <h2 class="text-primary">Delete Form Cost</h2>
            <hr />
        </div>
        <div class="mb-3">
            <label asp-for="Form.BranchId"></label>
            <select asp-for="Form.BranchId" disabled asp-items="@Model.BranchList" class="form-select">
                <option disabled selected>--Branch--</option>
            </select>
        </div>
        <div class="mb-3">
            <label asp-for="Form.FormTypeId"></label>
            <select asp-for="Form.FormTypeId" disabled asp-items="@Model.FormTypeList" class="form-select">
                <option disabled selected>--Form Type--</option>
            </select>
        </div>
        <div class="mb-3">
            <label asp-for="Form.Value"></label>
            <input asp-for="Form.Value" disabled class="form-control" />
        </div>
        <button type="submit" class="btn btn-primary" style="width:150px">Delete</button>
        <a asp-controller="Form" asp-action="Index" class="btn btn-secondary" style="width:150px">
            Back to List
        </a>
    </div>
</form>

The Delete action in my FormController:

// POST
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public IActionResult DeletePOST(int? id) 
{ 
    var obj = _unitOfWork.Form.GetFirstOrDefault(u => u.Id == id);

    if (obj == null)
    {
        TempData["error"] = "Form Cost not deleted";
        return RedirectToAction(nameof(Index));
    }

    _unitOfWork.Form.Remove(obj);
    _unitOfWork.Save();

    TempData["success"] = "Form Cost deleted successfully";

    return RedirectToAction(nameof(Index));
}

This code is almost a character-by-character from another model that works.


Solution

  • The <input asp-for="Form.Id" hidden /> tag helper used in the question above will generate the following HTML:

    <input hidden type="number" id="Form_Id" name="Form.Id" data-val-required="The Id field is required."  ...
    

    As we can see the generated name is name="Form.Id". This is why the binding doesn't work properly, because of on the server side the expected name is defined as id.

    To fix this problem define the tag name explicitly:

    <input asp-for="Form.Id" name="id" hidden />