Upload and display multiple attachments
DESCRIPTION
Upload and display multiple attachments in RadGrid using a GridTemplateColumn with RadAsyncUpload.
The built-in GridAttachmentColumn only uploads and displays one attachment at the time, see Grid - Column Types.
However, a solution to upload and display multiple attachments can be implemented additionally.
SOLUTION
For this scenario, you will need to have the Manual CRUD Operations implemented for RadGrid.
Moving on, adjust the Grid markup as follows:
- Add a GridTemplateColumn to RadGrid - This will contain the custom Controls to Upload and display the attachments
- Add an asp:PlaceHolder to the Column's ItemTemplate - A WebForms Control that will act as placeholder for the attachments' HyperLinks we add programmatically
- Add a RadAsyncUpload Control to the EditItemTemplate - This is required to Upload the files to the server
Example TemplateColumn definition
<telerik:GridTemplateColumn HeaderText="Template Column with Attachments" DataField="Attachments">
<ItemTemplate>
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
</ItemTemplate>
<EditItemTemplate>
<telerik:RadAsyncUpload ID="RadAsyncUpload1" runat="server" MultipleFileSelection="Automatic" OnFileUploaded="RadAsyncUpload1_FileUploaded">
</telerik:RadAsyncUpload>
</EditItemTemplate>
</telerik:GridTemplateColumn>
THE MAGIC
When inserting or updating, access the AsyncUpload in the Grid's Insert/Edit form and loop through the Uploaded Files collecting their names. Using the collection of file names, create a string containing all the file names separated by a coma.
Example
// Access the AsyncUpload control
RadAsyncUpload asyncUpload = editedItem.FindControl("RadAsyncUpload1") as RadAsyncUpload;
// Get the Uploaded files
UploadedFileCollection uploadedFiles = asyncUpload.UploadedFiles;
// Create a new list of strings
List<string> fileNamesList = new List<string>();
// Loop through each uploaded file
foreach (UploadedFile fileToUpload in uploadedFiles)
{
// add the filename to the list of strings
fileNamesList.Add(GetNewFileName(fileToUpload.FileName, orderId));
}
// Finally, concatenate all values separated by a coma and update the database field with the new string
newValues["Attachments"] = string.Join(",", fileNamesList.ToArray()); // Result: File1,File2,File3, etc
When displaying, fetch the data from the database, split the string by the coma to get a collection of file names. After that, loop through each filename and create a HyperLink for it. That HyperLink will be added to the PlaceHolder's Controls collection.
Example
protected void RadGrid1_ItemDataBound(object sender, GridItemEventArgs e)
{
// ItemTemplate
if (e.Item is GridDataItem)
{
GridDataItem item = (GridDataItem)e.Item;
// Get the data key value
int orderId = (int)item.GetDataKeyValue("OrderID");
// find the related row in the database
DataRow foundItem = SessionDataSource.Select(String.Format("OrderID = '{0}'", orderId)).FirstOrDefault();
// get the list of file paths by splitting the values separated by coma
List<string> filePaths = foundItem["Attachments"].ToString().Split(',').Select(path => path.Trim())
.Where(path => !string.IsNullOrEmpty(path)).ToList();
if (filePaths.Count > 0)
{
// access the placeholder
PlaceHolder ph1 = item.FindControl("PlaceHolder1") as PlaceHolder;
// loop through each path
foreach (string filePath in filePaths)
{
// Check if the file exists on the specific path
if (File.Exists(Server.MapPath("~/Attachments/" + filePath)))
{
// if so create Hyperlinks pointing to the Path
HyperLink link = new HyperLink();
link.NavigateUrl = "/Attachments/" + filePath;
link.Text = filePath;
link.Target = "_blank";
// add the hyperlink to the PlaceHolder
ph1.Controls.Add(link);
}
else
{
// Notify user the file is missing from server
ph1.Controls.Add(new Literal() { Text = filePath + " (missing)" });
}
// add line breaks to display each record in a new line
ph1.Controls.Add(new Literal() { Text = "</br>" });
}
}
}
}
Last, but not least, handling the actual file upload. We want to customize the destination the files will be uploaded to as well as the names we want to use for the uploaded files.
// Upload the Files to a Specific Directory
protected void RadAsyncUpload1_FileUploaded(object sender, FileUploadedEventArgs e)
{
RadAsyncUpload asyncUpload = (RadAsyncUpload)sender;
// NamingContainer is the Grid's EditForm since the AsyncUpload is located inside the EditItemTemplate
GridEditFormItem editFormItem = asyncUpload.NamingContainer as GridEditFormItem;
int orderId = 1;
// If Inserting there is no existing records for which we can extract the DataKeyValue
if (editFormItem is GridEditFormInsertItem)
{
// This example demonstrates in-memory editing to avoid setting up a database.
// In case of databases a new primary key value will be generated for inserted record
DataRow[] allValues = SessionDataSource.Select("OrderID = MAX(OrderID)");
if (allValues.Length > 0)
{
orderId = int.Parse(allValues[0]["OrderID"].ToString()) + 1;
}
}
else
{
// For existing records, we can access the DataKeyValue
orderId = (int)editFormItem.GetDataKeyValue("OrderID");
}
// Set the path where the Files will be uploaded to
string newFilePath = string.Format("{0}/{1}", Server.MapPath("~/Attachments"), GetNewFileName(e.File.FileName, orderId));
e.File.SaveAs(newFilePath); // Save the File to the new path/directory
}
// Helper function to format the File name.
private string GetNewFileName(string fileName, int orderId)
{
// Include OrderID in the Filename
return string.Format("OrderID_{0}_File_{1}", orderId, fileName);
}
Complete Code
Add the following code snippets to a WebForms page and you'll be ready to run it.
ASPX markup
<asp:Label ID="Label1" runat="server" Text="Action:"></asp:Label>
<telerik:RadGrid ID="RadGrid1" runat="server" AllowPaging="True" Width="1000px"
AutoGenerateEditColumn="true"
AutoGenerateDeleteColumn="true"
OnNeedDataSource="RadGrid1_NeedDataSource"
OnInsertCommand="RadGrid1_InsertCommand"
OnUpdateCommand="RadGrid1_UpdateCommand"
OnDeleteCommand="RadGrid1_DeleteCommand"
OnItemDataBound="RadGrid1_ItemDataBound">
<MasterTableView AutoGenerateColumns="False" DataKeyNames="OrderID" CommandItemDisplay="Top" InsertItemDisplay="Top" InsertItemPageIndexAction="ShowItemOnLastPage">
<Columns>
<telerik:GridBoundColumn DataField="OrderID" DataType="System.Int32"
FilterControlAltText="Filter OrderID column" HeaderText="OrderID"
ReadOnly="True" SortExpression="OrderID" UniqueName="OrderID">
</telerik:GridBoundColumn>
<telerik:GridDateTimeColumn DataField="OrderDate" DataType="System.DateTime"
FilterControlAltText="Filter OrderDate column" HeaderText="OrderDate"
SortExpression="OrderDate" UniqueName="OrderDate">
</telerik:GridDateTimeColumn>
<telerik:GridNumericColumn DataField="Freight" DataType="System.Decimal"
FilterControlAltText="Filter Freight column" HeaderText="Freight"
SortExpression="Freight" UniqueName="Freight">
</telerik:GridNumericColumn>
<telerik:GridBoundColumn DataField="ShipName"
FilterControlAltText="Filter ShipName column" HeaderText="ShipName"
SortExpression="ShipName" UniqueName="ShipName">
</telerik:GridBoundColumn>
<telerik:GridBoundColumn DataField="ShipCountry"
FilterControlAltText="Filter ShipCountry column" HeaderText="ShipCountry"
SortExpression="ShipCountry" UniqueName="ShipCountry">
</telerik:GridBoundColumn>
<telerik:GridTemplateColumn HeaderText="Template Column with Attachments" DataField="Attachments">
<ItemTemplate>
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
</ItemTemplate>
<EditItemTemplate>
<telerik:RadAsyncUpload ID="RadAsyncUpload1" runat="server" MultipleFileSelection="Automatic" OnFileUploaded="RadAsyncUpload1_FileUploaded">
</telerik:RadAsyncUpload>
</EditItemTemplate>
</telerik:GridTemplateColumn>
</Columns>
</MasterTableView>
</telerik:RadGrid>
C#/VB CodeBehind
#region Properties for CRUD Operations
public DataTable SessionDataSource
{
get
{
string sessionKey = "SessionDataSource";
if (Session[sessionKey] == null || !IsPostBack)
{
Session[sessionKey] = OrdersTable();
}
return (DataTable)Session[sessionKey];
}
}
#endregion
#region RadGrid Events for CRUD Operations
// CREATE (Add New Record)
protected void RadGrid1_InsertCommand(object sender, GridCommandEventArgs e)
{
GridEditableItem editedItem = e.Item as GridEditableItem;
DataRow newRow = SessionDataSource.NewRow();
//As this example demonstrates only in-memory editing, a new primary key value should be generated
//This should not be applied when updating directly the database
DataRow[] allValues = SessionDataSource.Select("OrderID = MAX(OrderID)");
int orderId = 1;
if (allValues.Length > 0)
{
orderId = int.Parse(allValues[0]["OrderID"].ToString()) + 1;
}
// OrderID is either 1 (if no records in the Datasource) or an incremented ID (if records exists)
newRow["OrderID"] = orderId;
//Set new values
Hashtable newValues = new Hashtable();
//The GridTableView will fill the values from all editable columns in the hash
e.Item.OwnerTableView.ExtractValuesFromItem(newValues, editedItem);
// Access the AsyncUpload control
RadAsyncUpload asyncUpload = editedItem.FindControl("RadAsyncUpload1") as RadAsyncUpload;
// Get the Uploaded files
UploadedFileCollection uploadedFiles = asyncUpload.UploadedFiles;
// Create a new list of strings
List<string> fileNamesList = new List<string>();
// Loop through each uploaded file
foreach (UploadedFile fileToUpload in uploadedFiles)
{
// add the filename to the list of strings
fileNamesList.Add(GetNewFileName(fileToUpload.FileName, orderId));
}
// Finally, concatenate all values separated by a coma and update the database field with the new string
newValues["Attachments"] = string.Join(",", fileNamesList.ToArray());
// Result: File1,File2,File3, etc
try
{
foreach (DictionaryEntry entry in newValues)
{
newRow[(string)entry.Key] = entry.Value;
}
}
catch (Exception ex)
{
Label1.Text += string.Format("<br />Unable to insert into Orders. Reason: {0}", ex.Message);
e.Canceled = true;
return;
}
SessionDataSource.Rows.Add(newRow);
//Code for updating the database ca go here...
Label1.Text += string.Format("<br />Order {0} inserted", newRow["OrderID"]);
}
// READ (data binding)
protected void RadGrid1_NeedDataSource(object sender, GridNeedDataSourceEventArgs e)
{
(sender as RadGrid).DataSource = SessionDataSource;
}
// UPDATE
protected void RadGrid1_UpdateCommand(object sender, GridCommandEventArgs e)
{
// EditForm item
GridEditableItem editedItem = e.Item as GridEditableItem;
// Get the Data key value
int orderId = (int)editedItem.GetDataKeyValue("OrderID");
//Locate the changed row in the DataSource
DataRow[] changedRows = SessionDataSource.Select(string.Format("OrderID = {0}", editedItem.GetDataKeyValue("OrderID")));
if (changedRows.Length != 1)
{
this.Label1.Text += "Unable to locate the Order for updating.";
e.Canceled = true;
return;
}
//Create a Hashtable
Hashtable newValues = new Hashtable();
// Extract values of current edited item
e.Item.OwnerTableView.ExtractValuesFromItem(newValues, editedItem);
// Access the AsyncUpload control
RadAsyncUpload asyncUpload = editedItem.FindControl("RadAsyncUpload1") as RadAsyncUpload;
// Get the Uploaded files
UploadedFileCollection uploadedFiles = asyncUpload.UploadedFiles;
// Create a new list of strings
List<string> fileNamesList = new List<string>();
// Loop through each uploaded file
foreach (UploadedFile fileToUpload in uploadedFiles)
{
// add the filename to the list of strings
fileNamesList.Add(GetNewFileName(fileToUpload.FileName, orderId));
}
// Finally, concatenate all values separated by a coma and update the database field with the new string
newValues["Attachments"] = string.Join(",", fileNamesList.ToArray()); // Output: File1,File2,File3, etc
changedRows[0].BeginEdit();
try
{
foreach (DictionaryEntry entry in newValues)
{
changedRows[0][(string)entry.Key] = entry.Value;
}
changedRows[0].EndEdit();
}
catch (Exception ex)
{
changedRows[0].CancelEdit();
Label1.Text += string.Format("Unable to update Orders. Reason: {0}", ex.Message);
e.Canceled = true;
return;
}
}
// DELETE
protected void RadGrid1_DeleteCommand(object sender, GridCommandEventArgs e)
{
GridDataItem dataItem = e.Item as GridDataItem;
string ID = dataItem.GetDataKeyValue("OrderID").ToString();
if (SessionDataSource.Rows.Find(ID) != null)
{
SessionDataSource.Rows.Find(ID).Delete();
}
}
#endregion
#region DataSource
private DataTable OrdersTable()
{
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("OrderID", typeof(int)));
dt.Columns.Add(new DataColumn("OrderDate", typeof(DateTime)));
dt.Columns.Add(new DataColumn("Freight", typeof(decimal)));
dt.Columns.Add(new DataColumn("ShipName", typeof(string)));
dt.Columns.Add(new DataColumn("ShipCountry", typeof(string)));
dt.Columns.Add(new DataColumn("Attachments", typeof(string)));
dt.PrimaryKey = new DataColumn[] { dt.Columns["OrderID"] };
for (int i = 0; i < 3; i++)
{
int index = i + 1;
DataRow row = dt.NewRow();
row["OrderID"] = index;
row["OrderDate"] = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0).AddHours(index);
row["Freight"] = index * 0.1 + index * 0.01;
row["ShipName"] = "Name " + index;
row["ShipCountry"] = "Country " + index;
dt.Rows.Add(row);
}
return dt;
}
#endregion
protected string GetLinks(string filePaths)
{
string[] paths = filePaths.Split(',');
List<string> links = new List<string>();
foreach (string path in paths)
{
links.Add(string.Format("<a href='{0}'>{1}</a>", "link", "filename"));
}
return string.Empty;
}
protected void RadGrid1_ItemDataBound(object sender, GridItemEventArgs e)
{
// ItemTemplate
if (e.Item is GridDataItem)
{
GridDataItem item = (GridDataItem)e.Item;
// Get the data key value
int orderId = (int)item.GetDataKeyValue("OrderID");
// find the related row in the database
DataRow foundItem = SessionDataSource.Select(String.Format("OrderID = '{0}'", orderId)).FirstOrDefault();
// get the list of file paths by splitting the values separated by coma
List<string> filePaths = foundItem["Attachments"].ToString().Split(',').Select(path => path.Trim())
.Where(path => !string.IsNullOrEmpty(path)).ToList();
if (filePaths.Count > 0)
{
// access the placeholder
PlaceHolder ph1 = item.FindControl("PlaceHolder1") as PlaceHolder;
// loop through each path
foreach (string filePath in filePaths)
{
// Check if the file exists on the specific path
if (File.Exists(Server.MapPath("~/Attachments/" + filePath)))
{
// if so create Hyperlinks pointing to the Path
HyperLink link = new HyperLink();
link.NavigateUrl = "/Attachments/" + filePath;
link.Text = filePath;
link.Target = "_blank";
// add the hyperlink to the PlaceHolder
ph1.Controls.Add(link);
}
else
{
// Notify user the file is missing from server
ph1.Controls.Add(new Literal() { Text = filePath + " (missing)" });
}
// add line breaks to display each record in a new line
ph1.Controls.Add(new Literal() { Text = "</br>" });
}
}
}
}
// Upload the Files to a Specific Directory
protected void RadAsyncUpload1_FileUploaded(object sender, FileUploadedEventArgs e)
{
RadAsyncUpload asyncUpload = (RadAsyncUpload)sender;
// NamingContainer is the Grid's EditForm since the AsyncUpload is located inside the EditItemTemplate
GridEditFormItem editFormItem = asyncUpload.NamingContainer as GridEditFormItem;
int orderId = 1;
// If Inserting there is no existing records for which we can extract the DataKeyValue
if (editFormItem is GridEditFormInsertItem)
{
// This example demonstrates in-memory editing to avoid setting up a database.
// In case of databases a new primary key value will be generated for inserted record
DataRow[] allValues = SessionDataSource.Select("OrderID = MAX(OrderID)");
if (allValues.Length > 0)
{
orderId = int.Parse(allValues[0]["OrderID"].ToString()) + 1;
}
}
else
{
// For existing records, we can access the DataKeyValue
orderId = (int)editFormItem.GetDataKeyValue("OrderID");
}
// Set the path where the Files will be uploaded to
string newFilePath = string.Format("{0}/{1}", Server.MapPath("~/Attachments"), GetNewFileName(e.File.FileName, orderId));
e.File.SaveAs(newFilePath); // Save the File to the new path/directory
}
// Helper function to format the File name.
private string GetNewFileName(string fileName, int orderId)
{
// Include OrderID in the Filename
return string.Format("OrderID_{0}_File_{1}", orderId, fileName);
}