It took me a few minutes to figure this out and Google was surprisingly unhelpful so hopefully this will be useful to others.

When databinding objects, only properties are available. To my dismay, simply making class fields public isn’t enough. Even though the field is right there, publicly accessible, the databinder acts as if it’s not.

Consider a class, Payment:

public class Payment
{
  public decimal Amount;
 
  Payment()
  {
  }
}

We might use this like so:

<asp:gridview runat="server" id="dgPayments">
  <Columns>
    <asp:TemplateColumn HeaderText="Amount">
      <ItemTemplate>
        <%# DataBinder.Eval(Container.DataItem, "Amount") %>
      </ItemTemplate>
    </asp:TemplateColumn>
  </Columns>
</asp:gridview>
protected void Page_Load(object sender, System.EventArgs e)
{
  List<Payment> Payments = new List<Payment>();
 
  // Code here to fill Payments with your collection of Payment objects.
 
  dgPayments.DataSource = Payments;
  dgPayments.DataBind();
}

On databinding, this throws an error claiming that there’s no Amount available to bind.

Modify the class to encapsulate the field as a property like so:

public class Payment
{
  private decimal _amount;
 
  public decimal Amount
  {
    get { return _amount; }
    set { _amount = value; }
  }
 
  Payment()
  {
  }
}

Now, it works.