Search code examples
c#asp.net-core-mvc.net-6.0

ASP.NET Core 6 MVC / C# : model sent to controller is null


Here is the markup of my form:

<form asp-controller="Group" asp-action="Create" class="new-group-form">
    <div class="input-section">
        <label asp-for="NewGroup.Name">Name</label>
        <input asp-for="NewGroup.Name" type="text">
    </div>

    <input class="invited-members" asp-for="NewGroup.Members" type="hidden" value="">

    <div class="possible-members">
        @foreach (var friend in Model.Friends)
        {
             <div class="member">
                 <p>@friend.FriendEmail</p>
                 <button id="@friend.FriendEmail" type="button" class="member__action invite">Invite</button>
             </div>
        }
    </div>

    <div class="buttons">
        <button type="submit" class="create">Create</button>
        <button type="button" class="cancel">Cancel</button>
    </div>
</form>

Then this is the code of the controller:

[Route("group/new-group")]
[IgnoreAntiforgeryToken]
[HttpPost]
public IActionResult Create([FromForm]NewGroupViewModel newGroupViewModel)
{
    var memberEmails = newGroupViewModel.Members.Split().ToArray();
    var ownerEmail = User.FindFirstValue(ClaimTypes.Email);

    if (ModelState.IsValid && memberEmails.Length >= 3)
    {
        var newGroup = _db.Groups.Add(new Group
        {
            Name = newGroupViewModel.Name,
            OwnerId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier))
        });

        _db.SaveChanges();

        foreach (var member in memberEmails)
        {
            _db.GroupParticipants.Add(new GroupParticipant
            {
                UserId = _db.Users.First(x => x.Email == member).Id,
                GroupId = newGroup.Entity.Id,
                IsOwner = (member == ownerEmail)
            });
        }

        _db.SaveChanges();
    }
    
    return RedirectToAction("Index", "Boards");
}

As you can see I already used tag FromBody, which does not seem to be helpful enough.

Here is the code of my model class:

public class NewGroupViewModel
{
    [Required]
    [MinLength(5)]
    [MaxLength(20)]
    public string Name { get; set; }
    public string Members { get; set; }
}

The problem here is that newGroupViewModel values are null, and that leads to an exception at line

var memberEmails = newGroupViewModel.Members.Split().ToArray();

and the rest of the controller's code is kinda useless for this topic.

Due to the fact that null value can not be split. By the way inputs values are correct. But the values can't be successfully be delivered to the controller's method.

As you can see I've tried using FromForm tag, but it didn't help. Also I tried to reformat minor stuff as swapping some positioning of asp tags in inputs & labels (I've faced this problem before and that's how it was solved, but it doesn't work this time)


Solution

  • Asp.Net Core bind value by property names. In your View code, You use asp-for="NewGroup.Name" tag helper in input tag, This tag helper will generate name="NewGroup.Name" in html automatically. So when you submit the from, the property names are NewGroup.Name and NewGroup.Members.

    enter image description here

    In your backend, you use NewGroupViewModel to accept data, But the property names in NewGroupViewModel are Name and Members, They can't match with NewGroup.Name and NewGroup.Members, So NewGroupViewModel can't bind data. In your ajax code, you specify property names are name and members, This is why backend can bind value from ajax code.

    So in your view code, you just need to specify the name attribute correctly, Then backend can bind the value successfully

    <div class="input-section">
            <label asp-for="NewGroup.Name">Name</label>
            <input name="Name" type="text">
        </div>
        <input class="invited-members" name="Members" type="hidden" value="xxxx">
    

    BTW: Asp.Net Core MVC default bind value from form, So you don't need to add [FromForm] attribute in your controller action.