Home     Products      Support      Corporate     Sign In 
Support Knowledge Base, Article 672
Product
General
Title
Repeated Value Binding to ASP.NET List Controls
Solution

Repeated Value Binding to ASP.NET List Controls

by Pete Ehli

ASP.NET provides a powerful and rich set of server side controls, but after you write your server tag, <asp:dropdownlist id="MyDropDownList" runat ="server"> for instance, how do you bind your control to your intended data source, and what method do you use to perform your data binding? Well if you have ever done any Visual Basic or Access development, then you probably have some experience using data binding to bind controls to a data collection like a recordset for example. The binding in ASP.NET is similar, but in ASP.NET using server side Web Controls, all the data binding takes place on the server and the result is HTTP sent to the client or browser in HTML 3.5, unless otherwise specified. This means we can target a wide range of browsers running on a wide range of platforms using some pretty cool ASP.NET controls.

In this article we will look at data binding using four types of list controls: a DropDownList, a RadioButtonList, a DataList, and a DataGrid. To bind any of the list controls you would use a collection such as an ArrayList, HashTable, DataReader, and DataSet. We have left out binding using a DataView, but you can easily change the 'DataSet Binding Example' shown here to use the DataView class, which is defined in the System.Data namespace. We will show the DataSet binding example last, so let's hold off on any explanation until we get there.

Basically there are two types of binding, "single value binding" and "repeated value binding". Single value binding refers to just that, binding a single value to a control such as an ASP:TextBox, ASP:HyperLink, ASP:Image, ASP:LinkButton and so on. This article is only concerned with repeated value binding using list controls that use one of the aforementioned collections as the data source. List controls can bind only to collections that support the IEnumerable, ICollection, or IListSource interface, so all list controls basic behavior is predefined for you with these interfaces.

When you bind a control you can individually bind single controls or all the controls on the page. DataBind() is a method of the Page class and also all server controls. Calling Page.DataBind() or DataBind() will call all of the data binding expressions on the page to be executed. Typically you would perform all your data binding in the Page_Load method, as we do in our examples included here.

Quick Note: I am a big advocate of using the code behind file to implement the design strategy of loose coupling, strong cohesion, and information hiding, not to mention avoiding rats nest or spaghetti code which results in the use of inline code. Imagine having a web form with 20 or more controls with at least 10 or more requiring some complicated script logic. If you write server tags, HTML, and script logic all within the .aspx page, what you have is a huge mess! The examples here are simple, and use inline code for explanation and space purposes. You can easily take the script out of the page and place it in the code-behind Page_Load method where it belongs. I suggest that if you are new to using these controls and binding them, that you first get the examples to run as is, and then implement the code-behind page.

There are eight ASP.NET controls designed for repeated value server side binding. They are the HTML select, ListBox, DropDownList, CheckBoxList, RadioButtonList, Repeater, DataList, and DataGrid. All of these controls have Properties, Methods and Events that allow us to manage the binding process. Their Properties are: DataTextField, DataValueField, DataTextFormatString, and DataMember. The Methods are: DataBind, and FindControl. The Events are: DataBinding, and SelectedIndexChanged. In the examples provided you will see the use of all of the properties and methods except the FindControl method. Events are a different story and we are mainly concerned with how to bind our controls to data sources, so we will leave Events for you to investigate on your own after you see how to perform some simple binding through the provided examples.

Let's look at the first example using an ArrayList which is a class of the System.Collections namespace. In the Page_Load method we first declare our ArrayList and load the ArrayList with string objects of fictitious programming books. The databind process in this example is straightforward and pretty simple. There are two ways to map data to our controls. One is to use the control's properties depending on what control we are using to bind our data to, or we can use templates if the control supports them. Let's look at our example DataList control that supports templates. The DataList control uses a template referenced by the tag <itemtemplate>, that is the DataList control below uses a template defined within the control, which tells the control how to bind our data. Binding using templates takes place using the syntax <%# TheNameOfTheDataSource %>. Using this syntax you can bind to data sources, properties of your page, properties of another control, collections, expressions, and also results returned from called methods. The only three ASP.NET list controls that support templates are the, Repeater, DataList, and DataGrid. What is going on in our DataList control is that the row or list item is mapped to our data using a DataItem object within the controls Container to designate which field or column we want to display. Since we only have one column to display, which is our fictions books, that is what we get. In the next example using the HashTable, we will define two columns or fields to be displayed.

ArrayList Binding Example

<!doctype html public "-//w3c//dtd html 4.0 transitional//en" >
<html>

<head>
  <title>ArrayList Binding</title>
</head>

<body>
<form runat="server">
<p align=center><b><font size="5"> ArrayList
  Binding</font></b></p>

<p align=left>
<table height=603 cellspacing=1 cellpadding=0 width=605 border=0>
  <tr>
    <td>
      <p align=center><b>DropDownList Control:
        MyDropDownList</b></p>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><asp:dropdownlist id="MyDropDownList" runat="server">
      </asp:dropdownlist></p>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><b>RadioButtonList
        Control:  MyRadioButtonList</b></p>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center>
      <asp:RadioButtonList id="MyRadioButtonList" runat="server">
      </asp:RadioButtonList></p>
    </td>
  </tr>
  <tr>
    <td>
      <font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><b>DataGrid Control: 
        MyDataGrid</b></p>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center>
      <asp:datagrid id="MyDataGrid" runat="server"></asp:datagrid>
      </p>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><b>DataList Control: 
        MyDataList</b></p>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center>
      <asp:datalist id="MyDataList" runat="server">
      <itemtemplate>
      <%# Container.DataItem %>
      </itemtemplate>
      </asp:datalist></p>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <p align=left><font face="Tahoma" size="2">Author: Pete
        Ehli</font></p>
    </td>
  </tr>
</table>
</p>
</form>

<script language="C#" runat="server">
  private void Page_Load(object sender, System.EventArgs e)
    {
      //Create an ArrayList of values to bind to
      ArrayList MyArrayList = new ArrayList();
      MyArrayList.Add("Turbo C#");
      MyArrayList.Add("C# Programming 101");
      MyArrayList.Add("DOS Basic Training");
      MyArrayList.Add("C# Patterns with ASP.NET");
      MyArrayList.Add("ADO.NET in Action");
      MyArrayList.Add("Intermediate ASP.NET");

      //Bind the controls to the data source MyArrayList
      MyDropDownList.DataSource = MyArrayList;
      MyRadioButtonList.DataSource = MyArrayList;
      MyDataGrid.DataSource = MyArrayList;
      MyDataList.DataSource = MyArrayList;

      //Bind all controls on the page
      Page.DataBind();
    }
</script>
</body>

</html>

In the 'HashTable Binding Example' below we have added prices for our books. Using a HashTable provides a simple solution to displaying two values to be displayed by our controls instead of one. Each of the HashTable references has a Key and a Value. The key is the name of our fictitious books and the value is the book prices. If you are familiar with HashTables you know that they are a Dictionary of sorts. If you compare a real life dictionary to a HashTable, the Key is the word in the dictionary and the definition is the value. Using a HashTable we locate our data using the Key, we do this by the statement MyDropDownList1.DataValueField ="Key"; We then tell the control what data to display MyDropDownList1.DataTextField = "Key"; and then format the data if we wish MyDropDownList2.DataTextFormatString = "{0:E}"; which in the case of our second DropDownList control is scientific notation. If we look at our DataList control you will see that we are using the DataBinder.Eval method of the DataBinder class for displaying our data. We use the Eval method of the DataBinder class in our example because it is capable of displaying two values. The DataBinder.Eval method takes three arguments used for locating, displaying, and formatting our data, with the third argument being optional. One important point here is that DataBinder.Eval is a static method that uses late-bound reflection to bind data. It therefore incures a "performance hit", but in my opinion this hit is negligible when taken into account its functionality and ease by which it makes binding possible. Let's now consider binding the DataGrid control using a HashTable. The DataGrid control using an ArrayList, DataReader, DataView, and DataSet as its data source is smart enough to figure out what columns or fields to display, but not using the HashTable. As you see we need to use the <asp:boundcolumn> tag within the control definition to specify to the DataGrid what columns or fields, and what header information to display. Also, we format the second column as currency values.

HashTable Binding Example

<!doctype html public "-//w3c//dtd html 4.0 transitional//en" >
<html>

<head>
  <title>HashTable Binding</title>
</head>

<body>
<form runat="server">
<p align=center><b><font size="5"> HashTable
  Binding</font></b></p>
<p align=left>
<table height=603 cellspacing=1 cellpadding=0 width=605 border=0>
  <tr>
    <td>
      <p align=center><b>DropDownList Control: 
        MyDropDownList1</b></p>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><asp:dropdownlist
      id="MyDropDownList1"
      runat="server">
      </asp:dropdownlist></p>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>xx</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><b>DropDownList Control: 
        MyDropDownList2</b></p>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><asp:dropdownlist
      id="MyDropDownList2"
      runat="server">
      </asp:dropdownlist></p>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><b>RadioButtonList Control: 
        MyRadioButtonList</b></p>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center>
      <asp:RadioButtonList id="MyRadioButtonList"
      runat="server">
      </asp:RadioButtonList>
      </p>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><b>DataGrid Control: 
        MyDataGrid</b></p>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center>
      <asp:datagrid id="MyDataGrid"
        autogeneratecolumns="False"
        runat="server">

        <columns>
          <asp:boundcolumn headertext="Key"
          datafield="Key">
          </asp:boundcolumn>

          <asp:boundcolumn headertext="Value"
          datafield="Value" dataformatstring="{0:C}">
          </asp:boundcolumn>
        </columns>

      </asp:datagrid></p>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center><b>DataList Control: 
        MyDataList</b></p>
    </td>
  </tr>
  <tr>
    <td>
      <p align=center>
      <asp:datalist id="MyDataList" runat="server">
      <itemtemplate>
      <%# DataBinder.Eval(Container.DataItem, "Key") %>  
      <%# DataBinder.Eval(Container.DataItem, "Value", "{0:C}") %>
      </itemtemplate>
      </asp:datalist></p>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td><font color=#ffffff>x</font>
    </td>
  </tr>
  <tr>
    <td>
      <p align=left><font face="Tahoma" size="2">Author: Pete
        Ehli</font></p>
    </td>
  </tr>
</table>
</p>

<script language="C#" runat="server">
  private void Page_Load()
    {
      //Create a HashTable
      Hashtable MyHashTable = new Hashtable(5);

      //Populate MyHashTable with key and value data
      MyHashTable.Add("Turbo C#", 39.99);
      MyHashTable.Add("C# Programming 101", 29.49);
      MyHashTable.Add("DOS Basics Training", 30.00);
      MyHashTable.Add("C# Patterns with ASP.NET", 49.49);
      MyHashTable.Add("ADO.NET in Action", 35.99);
      MyHashTable.Add("Intermediate ASP.NET", 69.99);

      //Define the DataSource for the control
      MyDropDownList1.DataSource = MyHashTable;

      //Tell the control where to find our data
      MyDropDownList1.DataValueField ="Key";

      //Tell the control what data to display
      MyDropDownList1.DataTextField = "Key";

      //Define the DataSource for the control
      MyDropDownList2.DataSource = MyHashTable;

      //Tell the control where to find our data
      MyDropDownList2.DataValueField = "Key";

      //Tell the control what data to display
      MyDropDownList2.DataTextField = "Value";

      //Format the displayed data
      MyDropDownList2.DataTextFormatString = "{0:E}";

      //Define the DataSource for the control
      MyRadioButtonList.DataSource = MyHashTable;

      //Tell the control where to find our data
      MyRadioButtonList.DataValueField = "Key";

      //Tell the control what data to display
      MyRadioButtonList.DataTextField = "Value";

      //Format the displayed data
      MyRadioButtonList.DataTextFormatString = "Current Book Price {0:C}";

      //Define the DataSource for the control
      MyDataGrid.DataSource = MyHashTable;

      //Define the DataSource for the control
      MyDataList.DataSource = MyHashTable;

      //Bind all controls on the page
      Page.DataBind();
    }
</script>
</form>
</body>

</html>

In the 'DataReader Binding Example' below, we use only a DataGrid control to show data binding to a DataReader. Using a DataReader is the preferred choice for data binding using a collection, but this of course depends on what you are tring to accomplish with your list controls. Using a DataReader is a Forward only, Read only means by which to access our data, so we can use it only once to literally suck our data out of our database, and then populate our DataGrid control. A DataReader is a very efficient means by which to access our data so we have very little overhead using one unlike a DataSet. For this and the DataSet example we are uing the Northwind SQL Server database which resides in the MSDE (Microsoft SQL Server Desktop Engine) included in the .Net Framework SDK samples. We also have included a "striped out" Web.config file which you should place in the same directory with this example in your IIS web server. The Web.config file included here is pretty bare, and is used only for example purposes, but will function fine as is to run our example. If you look at the Web.config file, you will notice the name of the MSDE instance is 'NetSDK' - to access the database, use the server name: (local)\NetSDK. If your machine's name is MyMachine then your MSDE sever name is MyMachine\NetSDK, but using this name will not work to connect to the MSDE server. You must use (local)\NetSDK to be able to connect to your MSDE server and access the included Northwind database. In this example we are binding to just the control itself. Since this is an article about data binding, not data access, we will not go into great detail about how data access is done here. The script in the examples is well commented so that should give you a good idea about what is going on as far as performing our data access to bind to our DataGrid control.

DataReader Binding Example

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>
<!doctype html public "-//w3c//dtd html 4.0 transitional//en" >
<html>

<head>
  <title>DataReader Binding</title>
</head>

<body >
<form runat="server">
<p align=center><b><font size="5">DataReader Binding</font></b></p>
<p align=center> </p>
<p align=center><b>DataGrid Control:  MyDataGrid</b></p>
<p align=center><asp:datagrid id="MyDataGrid" runat="server">
  </asp:datagrid><br></p>
</form>

<script language="C#" runat="server">
  private void Page_Load(object sender, System.EventArgs e)
    {
      //Create a variable to hold the connection object
      SqlConnection MySqlConn = null;

      try
      {
        //Assign a connection string from the Web.config file
        string MySqlConnString = (string)
          ConfigurationSettings.AppSettings["SqlConnString"];

        //Create a connection object
        MySqlConn = new SqlConnection(MySqlConnString);

        //Open the connection
        MySqlConn.Open();

         //Build the string Select statement
        string Select = "SELECT * FROM Orders, Customers " +
        "WHERE Orders.CustomerID = Customers.CustomerID and ShipVia = 1";

        //Create a command object
        SqlCommand SqlCmd = new SqlCommand(Select, MySqlConn);

        //Define a DataReader object using our
        //command object's ExecuteReader method
        SqlDataReader MyDataReader = SqlCmd.ExecuteReader();

        //Tell our DataGrid what it's data source is
        MyDataGrid.DataSource = MyDataReader;

        //Bind our DataGrid to the specified data source
        MyDataGrid.DataBind();
      }

    catch(Exception ex)
      {
        throw ex;
      }

    finally
      {
        if(MySqlConn.State == ConnectionState.Open)
          {
            MySqlConn.Close();
          }
      }
    }
</script>

<p align=left><font face="Tahoma" size="2">Author: Pete
  Ehli</font>
</body>

</html>

Web.config File for the "DataReader Binding Example"

<configuration>
<appSettings>
<add key="SqlConnString" value="server=(local)\NetSDK;
  uid=QSUser;pwd=QSPassword;database=Northwind" />
</appSettings>
<system.web>
</system.web>
</configuration>

In the 'DataSet Binding Example' below, data binding is shown binding two DataGrids to one DataSet. We are using the System.Data.OleDb namespace as our means of data access to the same database, Northwind (see the above DataReader example). By using the System.Data.OleDb namespace we can access different databases such as Microsoft Access, Oracle and so on, but you would just need to change the connection string in the Web.config file included below this example. Again since this is an article about data binding, not data access, we will not go into great detail about how data access is done here. The script in the examples is well commented so that should give you a good idea about what is going on as far performing our data access to bind to our controls. Again, you need to place the Web.cofig file included below in the same directory you place the example in your IIS web server. What needs to be noted here is we are creating a DataSet that contains two tables that have a defined one-to-many relationship within the DataSet, that is one Order to many Order Details. This is what is referred as a Master Detail. MyDataGrid1 is the Master and MyDataGrid2 is the Detail. The tables that reside in our DataSet are significantly smaller due to the data we pull out of the Northwind database, using our SQL Select statements with the SQL comparison key word 'LIKE'. At the end of the script block we bind one DataGrid to one table in our DataSet. DataSets are disconnected objects where we can define simple to complex DataTable relationships, where we could add, change, or delete data contained in our DataSet, and then reflect these changes in our database.

DataSet Binding Example

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<%@ Import Namespace="System.Configuration" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>

<head>
  <TITLE>DataSet Binding</TITLE>
</head>

<body>
<form runat="server">
<p align=center><b><font size=5>DataSet Binding</font></b></p>
<p align=center><b><font size=5></font></b> </p>
<p align=center>DataGrid Control:  MyDataGrid<b><font size=5></font></b></p>
<p align=center><asp:datagrid id=MyDataGrid1 runat="server"></asp:datagrid><br></p>
<p align=center><asp:datagrid id=MyDataGrid2 runat="server"></asp:datagrid><br></p>

</form>
<script language="C#" runat="server">
  private void Page_Load(object sender, System.EventArgs e)
    {
      //Create a string for comparison reasons
      string TheOrderID = "1025%";

      //Build the string select statements
      string Orders = "SELECT * FROM Orders WHERE " +
      OrderID LIKE'" + TheOrderID + "'";

      string OrderDetails = "SELECT * FROM [Order Details] WHERE " +
      "OrderID LIKE'" + TheOrderID + "'";

      //Create a new DataSet object
      DataSet MyDataSet = new DataSet();

      //Assign a connection string from the Web.config file
      string MyOleConnString = (string)
        ConfigurationSettings.AppSettings["OleConnString"];

      //Create our connection object
      OleDbConnection MyOleDbConn = new OleDbConnection(MyOleConnString);

      //Create our command object
      OleDbCommand MyOleDbCmd = new OleDbCommand();

      //Set the command object properties
      MyOleDbCmd.Connection = MyOleDbConn;
      MyOleDbCmd.CommandType = CommandType.Text;
      MyOleDbCmd.CommandText = Orders;

      //Create our adapter oject
      OleDbDataAdapter MyOleDataAdapter = new OleDbDataAdapter();

      //Set the SelectCommand property using or command object
      MyOleDataAdapter.SelectCommand = MyOleDbCmd;

      //Get the data from the "Orders" table in the database and
      //put it into a table named "MyOrders" into the MyDataSet object
      MyOleDataAdapter.Fill(MyDataSet, "MyOrders");

      //Reassign the command oject CommandText property
      MyOleDbCmd.CommandText = OrderDetails;

      //Get the data from the "Order Details" table in the database and
      //put it into a table named "MyOrderDetails"
      //into the MyDataSet object
      MyOleDataAdapter.Fill(MyDataSet, "MyOrderDetails");

      //Create a relation in the DataSet object named MyDataSetRelation
      DataRelation MyRelation = new DataRelation("MyDataSetRelation",
      MyDataSet.Tables["MyOrders"].Columns["OrderID"],
      MyDataSet.Tables["MyOrderDetails"].Columns["OrderID"]);

      //Add the relation to the MyDataSet object
      MyDataSet.Relations.Add(MyRelation);

      //Specify to the DataGrids what their data source is
      MyDataGrid1.DataSource = MyDataSet;
      MyDataGrid2.DataSource = MyDataSet;

      //Tell the DataGrids to set their DataMember
      //propety to the the table named in the DataSet object
      MyDataGrid1.DataMember = "MyOrders";
      MyDataGrid2.DataMember = "MyOrderDetails";

      //Bind our DataGrids to their specified data source
      MyDataGrid1.DataBind();
      MyDataGrid2.DataBind();
    }
</script>

<p align=left><font face="Tahoma" size="2">Author: Pete
  Ehli</font>
</body>

</html>

Web.config for the "DataSet Binding Example"

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--modify connection string for your server -->
<add key="OleConnString" value="provider=SQLOLEDB.1;
  data source=(local)\NetSDK;initial catalog=Northwind;
  uid=QSUser;pwd=QSPassword" />
</appSettings>
<system.web>
</system.web>
</configuration>

In conclusion, repeated data binding in ASP.NET using list controls can be much more complicated and robust than what has been shown here. However, you should now have a good understanding and firm grounding on how repeated binding takes place, and what methods and data sources are available for you to implement repeated binding in your ASP.NET list controls.

Pete Ehli holds an Associate of Applied Science degree in Computer Information Systems. Having a firm foundation in Java, he has decided to transfer his skills to C#, and has been working with Visual Studio.NET since late Beta 1. He is Vice President of the Portland Area .NET User Group (PADNUG) http://www.3leaf.com/padnug/. Besides having a passion for the C# language, Pete is an avid weightlifter, bodybuilder, and health nut. He can be reached at csharpexpert@hotmail.com.

Attachments
Attachments/KB672_binding.zip
Created : 7/11/2003 4:03:36 PM (last modified : 7/11/2003 4:03:33 PM)
Rate this article!
Comments