Blazzter
Blazzter

Reputation: 57

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");
      }

Upvotes: 0

Views: 695

Answers (2)

Yiyi You
Yiyi You

Reputation: 18199

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

Upvotes: 1

Mike Brind
Mike Brind

Reputation: 30110

Only one of the handlers fires on each request, depending on the HTTP verb that was used to make the request. OnGet is executed when the page is requested using HTTP Get method. OnPost is executed by the POST method.

The web is stateless. That means that variables initialised on one request are not available to another request. If you want to work with the collection in the OnPost method, you need to instantiate it again in the OnPost handler.

More details on my site here: https://www.learnrazorpages.com/razor-pages/handler-methods

Upvotes: 0

Related Questions