Search code examples
c#asp.net-corerazorrazor-pages

IList binding Razor Pages


Trying to bind IList so that I can update the fields that I have. However, ending up with an empty list with OnPostAsync. Not sure what I'm missing.

My code looks as follows:

[BindProperty]
public IList<Runbook_Serverlist_Plus> RunbookServerListPlus { get; set; }

This list is filled with the OnGetAsync function using the below code:

public async Task OnGetAsync()
{
   var listRunbookServerList = await _context.Runbook_Serverlist.ToListAsync();
   RunbookServerListPlus = listRunbookServerList.Select(item => new Runbook_Serverlist_Plus(item)).ToList();
}

So from my model I first get the IList<Runbook_Serverlist> and then I create a new list for Runbook_Serverlist_Plus. Idea is to have a sort of a base class of Runbook_Serverlist, then put a Runbook_Serverlist_Plus on top, in which I can set specific fields which I can later use to update some fields.

See code below:

  public class Runbook_Serverlist_Plus
  {
    [BindProperty]
    public Runbook_Serverlist Bc {get; set; }

    public Runbook_Serverlist_Plus(Runbook_Serverlist bsBc)
    {
      Bc = bsBc;
      CheckCompleted = false;
    }
    
    public bool CheckCompleted { get; set; }
  }

In my cshmtl the code is as follows:

<form method="post">
<table class="table">
  <thead>
  <tr>
    <th>
      @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.Servername)
    </th>
    <th>
      @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.Status)
    </th>
    <th>
      @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.Cluster)
    </th>
    <th>
      @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.StatusChange)
    </th>
    <th>
      @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.PreRun)
    </th>
    <th>
      @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.Ordering)
    </th>
    <th></th>
  </tr>
  </thead>
  <tbody>
  @for (var i = 0; i < Model.RunbookServerListPlus.Count; i++)
  {
    var item = Model.RunbookServerListPlus[i];
    <tr>
      <td>
        @Html.DisplayFor(modelItem => item.Bc.Servername)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Bc.Status)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Bc.Cluster)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Bc.StatusChange)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Bc.PreRun)
      </td>
      <td>
        @Html.DisplayFor(modelItem => item.Bc.Ordering)
      </td>
      <td>
        <input hidden asp-for="@item.Bc.Servername" class="form-control" />
        <input asp-for="@item.CheckCompleted"  class="form-control"/>
      </td>
    </tr>
  }
  </tbody>
</table>
  <div class="form-group">
    <input type="submit" value="Update" class="btn btn-primary" />
  </div>
</form>

So I have a checkbox for the CheckCompleted item and when doing a post, I would like to see all the checkboxes which are marked. When I do a submit/post, I would expect the RunbookServerListPlus to be filled. However, this one is empty all the time.

What am I missing?

Post function is for now nothing else then:

      public async Task<IActionResult> OnPostAsync()
      {
        if (!ModelState.IsValid)
        {
          return Page();
        }

        return RedirectToPage("./Index");
      }

Solution

  • Firstly,you can add hidden inputs to bind model data,form post will pass inputs' value.And .net core bind model with name attribute.If you want to bind data with list,you need to set name as list[index].xxx.

    And If you only want to pass Runbook_Serverlist_Plus when CheckCompleted is checked,you can use js to change the name of hidden inputs to right format before form post.Here is a demo.

    Models:

    public class Runbook_Serverlist_Plus
        {
            [BindProperty]
            public Runbook_Serverlist Bc { get; set; }
            public Runbook_Serverlist_Plus(Runbook_Serverlist bsBc)
            {
                Bc = bsBc;
                CheckCompleted = false;
            }
            public Runbook_Serverlist_Plus()
            {
            }
            public bool CheckCompleted { get; set; }
        }
        public class Runbook_Serverlist
        {
            public string Servername { get; set; }
            public string Status { get; set; }
            public string Cluster { get; set; }
            public string StatusChange { get; set; }
            public string PreRun { get; set; }
            public string Ordering { get; set; }
    
    
        }
    

    cshtml:

    <form method="post">
        <table class="table">
            <thead>
                <tr>
                    <th>
                        @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.Servername)
                    </th>
                    <th>
                        @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.Status)
                    </th>
                    <th>
                        @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.Cluster)
                    </th>
                    <th>
                        @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.StatusChange)
                    </th>
                    <th>
                        @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.PreRun)
                    </th>
                    <th>
                        @Html.DisplayNameFor(model => model.RunbookServerListPlus[0].Bc.Ordering)
                    </th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                @for (var i = 0; i < Model.RunbookServerListPlus.Count; i++)
                {
                    var item = Model.RunbookServerListPlus[i];
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Bc.Servername)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Bc.Status)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Bc.Cluster)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Bc.StatusChange)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Bc.PreRun)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Bc.Ordering)
                        </td>
                        <td>
                            <input asp-for="@item.CheckCompleted" class="form-control" name="RunbookServerListPlus[index].CheckCompleted" />
                            <input hidden asp-for="@item.Bc.Servername" class="form-control" name="RunbookServerListPlus[index].Bc.Servername" />
                            <input hidden asp-for="@item.Bc.Status" class="form-control" name="RunbookServerListPlus[index].Bc.Status" />
                            <input hidden asp-for="@item.Bc.Cluster" class="form-control" name="RunbookServerListPlus[index].Bc.Cluster" />
                            <input hidden asp-for="@item.Bc.StatusChange" class="form-control" name="RunbookServerListPlus[index].Bc.StatusChange" />
                            <input hidden asp-for="@item.Bc.PreRun" class="form-control" name="RunbookServerListPlus[index].Bc.PreRun" />
                            <input hidden asp-for="@item.Bc.Ordering" class="form-control" name="RunbookServerListPlus[index].Bc.Ordering" />
    
                        </td>
                    </tr>
                }
            </tbody>
        </table>
        <div class="form-group">
            <input type="submit" value="Update" class="btn btn-primary" />
        </div>
    </form>
    <script>
        $("form").submit(function () {
            var index = 0;
            $("tbody tr").each(function () {
                var lasttd = $(this).find('td:last-child');
                
                if (lasttd.find("input")[0].checked) {
                    lasttd.find("input").each(function () {
                        $(this).attr("name", $(this).attr("name").replace("index",index));
                    })
                    index++;
                }
            })
        })
    </script>
    

    cshtml.cs(I use fake data to test):

    [BindProperty]
            public List<Runbook_Serverlist_Plus> RunbookServerListPlus { get; set; }
            public void OnGet()
            {
                RunbookServerListPlus = new List<Runbook_Serverlist_Plus> {
                    new Runbook_Serverlist_Plus { Bc = new Runbook_Serverlist { Cluster = "c1", Ordering = "1", PreRun="p1", Servername="sname1", Status="status1", StatusChange="statuschange1" } },
                    new Runbook_Serverlist_Plus { Bc = new Runbook_Serverlist { Cluster = "c2", Ordering = "2", PreRun="p2", Servername="sname2", Status="status2", StatusChange="statuschange2" } },
                    new Runbook_Serverlist_Plus { Bc = new Runbook_Serverlist { Cluster = "c3", Ordering = "3", PreRun="p3", Servername="sname3", Status="status3", StatusChange="statuschange3" } }
    
                };
            }
            public async Task<IActionResult> OnPostAsync()
            {
                if (!ModelState.IsValid)
                {
                    return Page();
                }
    
                return RedirectToPage("./Index");
            }
    

    result: enter image description here

    Update(Pass a whole list):

    Firstly,remove js in the first demo.

    And then change td like this:

    <td>
                        <input asp-for="@item.CheckCompleted" class="form-control" name="RunbookServerListPlus[@i].CheckCompleted" />
                        <input hidden asp-for="@item.Bc.Servername" class="form-control" name="RunbookServerListPlus[@i].Bc.Servername" />
                        <input hidden asp-for="@item.Bc.Status" class="form-control" name="RunbookServerListPlus[@i].Bc.Status" />
                        <input hidden asp-for="@item.Bc.Cluster" class="form-control" name="RunbookServerListPlus[@i].Bc.Cluster" />
                        <input hidden asp-for="@item.Bc.StatusChange" class="form-control" name="RunbookServerListPlus[@i].Bc.StatusChange" />
                        <input hidden asp-for="@item.Bc.PreRun" class="form-control" name="RunbookServerListPlus[@i].Bc.PreRun" />
                        <input hidden asp-for="@item.Bc.Ordering" class="form-control" name="RunbookServerListPlus[@i].Bc.Ordering" />
    
                    </td>
    

    result: enter image description here

    Check data passed to handler: enter image description here

    enter image description here