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

How do I pass the current state of the view model from the view to the controller in ASP.NET Core 6?


I have a view model composed of several different models that I am trying to partially populate when the view first opens and fully populate with other actions in my controller.

I want to be able to pass the current state of the view model as a parameter to multiple actions in the same controller, but when I try this, the view model is getting sent back as null.

Here's the pertinent code.

EventViewModel:

public class EventViewModel
{
     public Event? Event { get; set; }
     public Client? Client { get; set; }
     public Employee? SelectedEmployee { get; set; }
     public DateTime StartTime { get; set; }
     public DateTime EndTime { get; set; }
     public List<Employee>? EmployeeList { get; set; }
     public List<EmployeeSchedule>? EmployeeSchedules { get; set; }
}

EventController:

//Get: /Event/Add
public IActionResult Add()
{
    EventViewModel viewModel = new EventViewModel();
    viewModel.Schedules = new List<EmployeeSchedule>();

    if (_db.Employees.Any())
    {
        viewModel.EmployeeList = _db.Employees.ToList();
    }
    else
    {
        viewModel.EmployeeList = new List<Employee>();
    }

    return View(viewModel);

    /*returns ViewModel containing Non-null Employees and Schedules lists and Add View loads 
      with correct values */
}

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Add(EventViewModel viewModel)  //all ViewModel properties are null here
{
    if (ModelState.IsValid)
    {
        _db.Events.Add(viewModel.Event);  //viewModel."" possible null reference warning
        ...
        _db.SaveChanges();
        return RedirectToAction("Index");
    }
    else
    {
        return View(viewModel);
    }
}

/*This method is meant to add selected employees from the employee table to the Schedule 
 table with schedule times appended from picker. I am sure this is wrong, but I can't fix it 
 until I can get the ViewModel back*/

public void ScheduleEmployee(EventViewModel viewModel) //all ViewModel properties null here
{
    viewModel.EmployeeSchedule.Id = viewModel.SelectedEmployee.Id;
    viewModel.EmployeeSchedule.Name = viewModel.SelectedEmployee.Name;
    viewModel.Schedules.Add(viewModel.EmployeeSchedule); //how to return this to view?
}

Event/Add.cshtml:

@model MyProject.ViewModels.EventViewModel
...
<!--how I am binding form input to ViewModel-->

<label asp-for="Event.Location"></label>
<input asp-for="Event.Location" class="form-control" />
<span asp-validation-for="Event.Location" class="text-danger"></span>
...

<!--button that I need to return ViewModel to Controller Add Post-->
 <button type="submit" class="btn btn-primary"> Save</button>
...

<!--button that needs to return ViewModel to Controller to add employee and schedule times 
  to current state of EventViewModel.Schedules list-->
<a asp-controller="Event" asp-action="ScheduleEmployee" class="btn btn-secondary">
    Add To Schedule
</a>

Solution

  • HTTP -- that is the actual transport protocol between the server and the client -- is "stateless" because it remembers nothing between invocations.

    So you can't save the current state by http. In my opinion, You want to use different actions to populate the same table of data, So here two simple demos.

    The First method: Use Session. In this method, before you wanna access to another action, You can click save button, This button will submit the form and save the value in session. So when you access to another action, Project will get current sate of model from session.

    Model

    public class TestModel
        {
            public string? Name { get; set; }
            public int? Age { get; set; }
            public int? AA { get; set; }
    
            //this property is used to set the key of session
            public string? SeessionId { get; set; }
        }
    

    Get method

    public IActionResult Add()
            {
                TestModel model = new TestModel();
                
                //set the value of SessionId when load the  view first time
               
                model.SeessionId = Guid.NewGuid().ToString();
                return View(model);
            }
    

    View

    @model TestModel
    
    <form method="post">
        <div class="form-group">
            <label asp-for="Name"></label>
            <input asp-for="Name" class="form-control" />
            <span asp-validation-for="Name" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Age"></label>
            <input asp-for="Age" class="form-control" />
            <span asp-validation-for="Age" class="text-danger"></span>
        </div>
    
    
        <input type="hidden" asp-for="SeessionId" />
        <input type="hidden" asp-for="AA" />
    
        when you want to access other action, You need to click this save button to save current sate by session
        <button asp-action="SetSession">Save</button>
    
        <button type="submit"  asp-action="Add" class="btn btn-primary"> Submit</button>
    </form>
    
    <a asp-controller="Home" asp-action="AA" asp-route-SessionId="@Model.SeessionId" class="btn btn-secondary">
        Add To Age
    </a>
    

    SetSession method

    [HttpPost]
            public IActionResult SetSession(TestModel viewModel)
            {
                //set the session by viewModel.SeessionId
                HttpContext.Session.SetString(viewModel.SeessionId, JsonConvert.SerializeObject(viewModel));
                return View("Add", viewModel);
            }
    

    AA Method

    public IActionResult AA(string SessionId) 
            {
                TestModel result = JsonConvert.DeserializeObject<TestModel>(HttpContext.Session.GetString(SessionId));
                if (result == null)
                {
                    return RedirectToPage("Error");
                }  
    
                //set the value of AA
                result.AA = result.Age + 1;
    
                //return to the view with model
                return View("Add",result);
            }
    

    The Second method, Just use button to submit current sate of model, After some more operations, return to the current page, Update the data of TestModel. because each time the data in the current table is submitted, so it is indirectly implemented to save the current state.

    @model TestModel
    
    <form method="post">
        <div class="form-group">
            <label asp-for="Name"></label>
            <input asp-for="Name" class="form-control" />
            <span asp-validation-for="Name" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Age"></label>
            <input asp-for="Age" class="form-control" />
            <span asp-validation-for="Age" class="text-danger"></span>
        </div>
    
    
       .........
    
       
    <!--use these buttons to submit form to different actions -->
        <button asp-controller="xxx" asp-action="ScheduleEmployee">Add To Schedule</button>
        <button asp-controller="xxx" asp-action="xxx">Add To xxxx</button>
    
        <button type="submit" asp-action="Add" class="btn btn-primary"> Submit</button>
    </form>
    

    Action

            [HttpPost]
            public IActionResult ScheduleEmployee(TestModel model)
            {
                //......
    
                return View("Add", model);
            }