ASP.Net C# Convert HTML to PDF using iTextSharp (update)

Update, and please read this before using the code below. iTextSharp is end of life. iText's own repository now carries a DEPRECATED notice, and the library receives only selected security fixes. More importantly, iTextSharp is licensed under AGPLv3, and iText's licensing terms specifically name "serving PDFs on the fly in a web application" and "shipping iText with a closed source product" as activities that require a purchased commercial license. That is exactly what the code in this post does. If you use this in a commercial application, you either open-source your entire application under the AGPL or you buy a license from iText. There is also a known XXE vulnerability, CVE-2020-15522, affecting iTextSharp 5.x that will not be patched. The approach below still works and the post is left up for reference, but check your licensing position before you deploy it, and consider a currently maintained PDF library instead.

Here's a minor update to a post from last year how how to convert HTML to PDF using iTextSharp.

I wanted to add a way to optionally render the PDF in landscape mode, so this was what I ultimately came up with.

 public static ReturnValue ConvertHtmlToPdfAsBytes(string HtmlData, bool Landscape = false)
{
    // variables
    ReturnValue Result = new ReturnValue();

    // do some additional cleansing to handle some scenarios that are out of control with the html data
    HtmlData = HtmlData.ReplaceValue("<br>", "<br />");

    // convert html to pdf
    try
    {
        // create a stream that we can write to, in this case a MemoryStream
        using (var stream = new MemoryStream())
        {
            // create an iTextSharp Document which is an abstraction of a PDF but **NOT** a PDF
            using (var document = new Document())
            {
                // portrait vs landscape
                if (Landscape)
                {
                    document.SetPageSize(PageSize.A4.Rotate());
                }

                // create a writer that's bound to our PDF abstraction and our stream
                using (var writer = PdfWriter.GetInstance(document, stream))
                {
                    // open the document for writing
                    document.Open();

                    // read html data to StringReader
                    using (var html = new StringReader(HtmlData))
                    {
                        XMLWorkerHelper.GetInstance().ParseXHtml(writer, document, html);
                    }

                    // close document
                    document.Close();
                }
            }

            // get bytes from stream
            Result.Data = stream.ToArray();

            // success
            Result.Success = true;
        }
    }
    catch (Exception ex)
    {
        Result.Success = false;
        Result.Message = ex.Message;
    }

    // return
    return Result;
}
The ReturnValue class was simply a helper class that looks like this:

 // return value class  
 public class ReturnValue  
 {  
       // constructor  
       public ReturnValue()  
       {  
         this.Success = false;  
         this.Message = string.Empty;  
       }  
   
       // properties  
       public bool Success = false;  
       public string Message = string.Empty;  
       public Byte[] Data = null;  
 }  
We also had another method to physically create the PDF file in case you didn't want just the bytes array directly, for example:

 public static ReturnValue ConvertHtmlToPdfAsFile(string FilePath, string HtmlData)  
 {  
       // variables  
       ReturnValue Result = new ReturnValue();  
   
       try  
       {  
         // convert html to pdf and get bytes array  
         Result = ConvertHtmlToPdfAsBytes(HtmlData: HtmlData);  
   
         // check for errors  
         if (!Result.Success)  
         {  
           return Result;  
         }  
   
         // create file  
         File.WriteAllBytes(path: FilePath, bytes: Result.Data);  
   
         // result  
         Result.Success = true;  
       }  
       catch(Exception ex)  
       {  
         Result.Success = false;  
         Result.Message = ex.Message;  
       }  
   
       // return  
       return Result;  
 }  
It's important to remember that in order for this to work, you must have valid well-formed HTML; otherwise you can certainly expect for iTextSharp to throw an error. But if you have control over the HTML that you need to convert, this solution is great, and produces very nice PDF files.

It's worth noting that in our case we didn't need to pass the CSS in separately using the overloaded ParseXHtml constructor, ParseXHtml(PdfWriter writer, Document doc, Stream inp, Stream inCssFile), because we were including our CSS styles in our HTML data string instead, which for our solution was a bit cleaner.

ASP.Net C# Checking Request Header and/or Request Cookies in Global.asax Application_BeginRequest

Here is a simple example using C# to get a custom token from either the HTTP header or HTTP cookie on any page in your application. This example relies on a few custom extension methods that I regularly use, although they could be refactored out very easily.

In this particular example we're checking the Request.Headers collection first for a custom key named Token. If found we use it, if not found we check the Request.Cookies collection, and if found we use it.

Global.asax.cs
   protected void Application_BeginRequest(object sender, EventArgs e)
   {
      // variables
      string Token = string.Empty;

      // check for token in header
      if (Token.IsBlank() && Request.Headers.AllKeys.Any(k => k.IsEqual("Token")))
      {
         Token = Server.UrlDecode(Request.Headers.GetValues("Token").First());
      }

      // check for token in cookie
      if (Token.IsBlank() && Request.Cookies.AllKeys.Any(k => k.IsEqual("Token")))
      {
         Token = Server.UrlDecode(Request.Cookies.Get("Token").Value);
      }

      // if token specified try to parse it
      if (Token.HasValue())
      {
         // todo
      }

      // debug
      Response.Write("Application_BeginRequest<br />");
      Response.Write(string.Format("Token: {0}<br />", Token));
   }

The idea behind this example was to be able to let an encrypted authentication token be passed in to automatically authenticate the user for any page within the application. Having it check both the Request.Headers and the Request.Cookies collection provides some additional flexibility for the processes that pass in the token. For example, some older versions of Android code don't easily allow a custom HTTP header to be specified, but could easily set a cookie.

ASP.Net C# Retrieving a list of videos from a YouTube playlist

Recently when working on my favorites page, as I was typing up my favorite songs and linking them to YouTube, I thought it would be pretty nice to just tap in to my "Favorites" list directly in YouTube. A quick Google search lead me to this Stack Overflow article:

http://stackoverflow.com/questions/34143202/get-all-videos-from-channel-youtube-api-v3-c-sharp

So building off of a couple of the answers in this article, I've put together a functional example using C# to show how to connect to the YouTube Data API.

For this example we'll be using the following NuGet package:

Google.Apis.YouTube.v3 Client Library

If you need to generate your own YouTube Data API key, you can visit the Google Developer Console and use the API Manager.

ASPX Page
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Web.youtube._default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <!-- title -->
    <title>YouTube Demo</title>
 
    <!-- bootstrap -->
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

    <!-- styles -->
    <style>
        body { margin: 25px !important; }
        .video { margin-top: 25px; }
        .video .title { font-weight: normal; white-space: nowrap; overflow: hidden; }
        .video .thumbnail img { width: 192px; height: 144px; }
    </style>
</head>

<body>
    <form id="form1" runat="server">
        <div class="container">
            <div class="col-lg-12">
                <h1>YouTube Demo</h1>
            </div>
            
            <!-- placeholder where we will render the results -->
            <asp:PlaceHolder ID="pnlVideos" runat="server" />

            <div class="col-lg-12" style="padding-top: 10px">
                <asp:Label ID="lblMessage" Font-Bold="true" ForeColor="Red" ClientIDMode="Static" runat="server" />
            </div>
        </div>

        <!-- template to help build each video container -->
        <asp:Literal ID="lblTemplate" Visible="false" runat="server">
            <div class="video col-lg-4">
                <div class="title">
                    <a href="{Url}" target="_blank">
                        {Title}
                    </a>
                </div>

                <div class="thumbnail">
                    <a title="{ToolTip}" href="{Url}" target="_blank">
                        <img title="{ToolTip}" src="{Image}" alt="" />
                    </a>
                </div>
            </div>
        </asp:Literal>
    </form>
</body>
</html>

Code-Behind
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Web.youtube
{
    public partial class _default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                // initialize call to the youtube service
                var YouTubeService = new YouTubeService(new BaseClientService.Initializer() { ApiKey = "YourApiKey" });

                // indicate the parts we need for the request
                var ChannelListRequest = YouTubeService.Channels.List("contentDetails");

                // indicate the username for the channel you are requesting
                ChannelListRequest.ForUsername = "UserNameOfChannelYouAreRequesting";
                
                // execute the request and get the response
                var ListResponse = ChannelListRequest.Execute();

                // iterate through the results
                foreach (var channel in ListResponse.Items)
                {
                    // indicate the list you are requesting
                    // if your favorites list is unlisted you will need to explicitly set this value
                    // var ListId = channel.ContentDetails.RelatedPlaylists.Favorites;
                    var ListId = "YouTubeListId";

                    // the page token will be used if the results span multiple pages
                    var PageToken = "";

                    // iterate until there is no more data
                    while (PageToken != null)
                    {
                        // indicate the parts we need for the request
                        var PlaylistRequest = YouTubeService.PlaylistItems.List("snippet");

                        // playlist request properties
                        PlaylistRequest.PlaylistId = ListId;
                        PlaylistRequest.MaxResults = 50;
                        PlaylistRequest.PageToken = PageToken;

                        // execute the request and get the response
                        var PlaylistResponse = PlaylistRequest.Execute();

                        // iterate through the results
                        foreach (var Video in PlaylistResponse.Items)
                        {
                            // variables
                            string Template = lblTemplate.Text;
                            string VideoId = Video.Snippet.ResourceId.VideoId;
                            string Title = Video.Snippet.Title;
                            string Url = string.Format("https://www.youtube.com/watch?v={0}", VideoId);
                            string Image = Video.Snippet.Thumbnails.High.Url;

                            // replace placeholders
                            Template = Template.Replace("{VideoId}", VideoId);
                            Template = Template.Replace("{Title}", Title);
                            Template = Template.Replace("{Url}", Url);
                            Template = Template.Replace("{Image}", Image);
                            Template = Template.Replace("{ToolTip}", HttpUtility.HtmlEncode(Title));

                            // add to videos panel
                            pnlVideos.Controls.Add(new LiteralControl(Template));
                        }

                        // get the next page token
                        PageToken = PlaylistResponse.NextPageToken;
                    }
                }
            }
            catch (Exception ex)
            {
                lblMessage.Text = ex.Message;
            }
        }
    }
}

Click here to see a fully functional demo.

ASP.Net C# DownloadString vs DownloadData Encoding Examples

I recently came across an issue using the WebClient.DownloadString method in System.Net that was causing UTF8 encoded data to not come through properly, and instead was showing odd characters.

Researching the issue led me to the following Stack Overflow articles:

The accepted answers on both work well, so I've put together an example using C# to illustrate the behavior and results.

DownloadString (no encoding specified)
// variables
string Url = "https://mysafeinfo.com/api/data?list=states&format=json&alias=nm=name,ab=code,cp=capital,yr=year&select=capital&ab=GU";
  
// DownloadString (no encoding specified)
using (WebClient client = new WebClient())
{
    Console.WriteLine(client.DownloadString(Url));
}

// result
[
  {
    "capital": "Hagåtña Dededo"
  }
]

DownloadString (encoding specified)
// variables
string Url = "https://mysafeinfo.com/api/data?list=states&format=json&alias=nm=name,ab=code,cp=capital,yr=year&select=capital&ab=GU";
  
// DownloadString (encoding specified)
using (WebClient client = new WebClient())
{
    // specify encoding
    client.Encoding = System.Text.UTF8Encoding.UTF8;
   
    // output
    Console.WriteLine(client.DownloadString(Url));
}

// result
[
  {
    "capital": "Hagåtña Dededo"
  }
]

DownloadData (encoding specified)
// variables
string Url = "https://mysafeinfo.com/api/data?list=states&format=json&alias=nm=name,ab=code,cp=capital,yr=year&select=capital&ab=GU";
  
// DownloadData (encoding specified)
using (WebClient client = new WebClient())
{
    Console.WriteLine(System.Text.UTF8Encoding.UTF8.GetString(client.DownloadData(Url)));
}

// result
[
  {
    "capital": "Hagåtña Dededo"
  }
]

One note from the present day: WebClient is obsolete in current versions of .NET, and HttpClient is the supported replacement. The underlying point still holds: GetStringAsync decodes for you and can get the encoding wrong in exactly the same way, while GetByteArrayAsync hands you the raw bytes so you can decode them yourself. If you are hitting the problem described here on modern .NET, the fix is the same, one layer over.

ASP.Net C# Exporting and Importing Excel Data

Update on EPPlus licensing. When this post was written, EPPlus was LGPL and free to use anywhere. As of version 5, released in early 2020, EPPlus moved to the Polyform Noncommercial license, and its maintainers state that any use inside a commercial company requires a purchased commercial license. Versions 4.x and earlier are still LGPL. Version 5 and later also require you to set ExcelPackage.LicenseContext explicitly or they throw a LicenseException in debug builds. The code below still works, but check which version you are pulling from NuGet and what your licensing position is. If you need a permissively licensed alternative, ClosedXML and NPOI are both worth a look.

Here is a simple example using C# to export data to an Excel file and to read data from an Excel file. This example demonstrates the following:

- MySafeInfo Data API
- Json.NET (Newtonsoft.Json)
- EPPlus
- Response.BinaryWrite
- WebClient.DownloadData
- StreamReader
- Serialization
- Custom Extension Methods

Code-Behind
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Utilities;

namespace Web.excel
{
    public partial class _default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // empty
        }

        public void DownloadFile(byte[] Data, string FileType = "application/csv", string FileName = "data.csv")
        {
            // headers
            Response.BufferOutput = true;
            Response.ClearHeaders();

            // explicitly append preamble for csv files so excel recognizes utf-8 encoding
            if (FileType.ToLower().Contains("csv"))
            {
                Response.BinaryWrite(System.Text.Encoding.UTF8.GetPreamble());
            }

            // encoding
            Response.ContentEncoding = System.Text.Encoding.UTF8;

            // content type
            Response.ContentType = FileType;

            // content disposition
            Response.AddHeader("Content-Disposition", string.Format("attachment; filename=\"{0}\"", FileName));

            // data
            Response.BinaryWrite(Data);

            // end
            Response.End();
        }

        protected void btnDownload_Click(object sender, EventArgs e)
        {
            // variables
            string Result = string.Empty;
            string Url = "https://mysafeinfo.com/api/data?list=states&format=json&alias=nm=name,ab=code,cp=capital,yr=year";
            List<State> States = new List<State>();
            DataTable DT = new DataTable();
            byte[] Data = null;

            // download json data
            using (WebClient client = new WebClient())
            {
                Result = System.Text.UTF8Encoding.UTF8.GetString(client.DownloadData(Url));
            }

            // deserialize json data into a List<State>
            // Json.NET (Newtonsoft.Json)
            // https://www.nuget.org/packages/Newtonsoft.Json
            States = Newtonsoft.Json.JsonConvert.DeserializeObject<List<State>>(Result);

            // convert List<State> to a DataTable using ToDataTable extension method
            DT = States.ToDataTable();

            // convert DataTable to byte[] using ToExcel extension method 
            // EPPlus
            // https://www.nuget.org/packages/EPPlus
            Data = DT.ToExcel(WorksheetName: "States");

            // download excel file
            DownloadFile(Data: Data, FileType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", FileName: "states.xlsx");
        }

        protected void btnUpload_Click(object sender, EventArgs e)
        {
            // variables
            DataTable DT = new DataTable();
            List<State> States = new List<State>();

            // read excel file in to a StreamReader
            using (StreamReader sr = new StreamReader(Server.MapPath("states.xlsx")))
            {
                // read the underlying stream and convert to a DataTable using ToDataTable extension method
                DT = sr.BaseStream.ToDataTable(HasHeader: true);
            }

            // convert DataTable to a List<State> using ToList extension method
            States = DT.ToList<State>().ToList();

            // output serialized List<State> so we can see that the excel file was read successfully
            lblOutput.Text = States.Serialize();

            // show panel
            pnlOutput.Visible = true;
        }

        // state class
        public class State
        {
            public State()
            {
                Name = string.Empty;
                Code = string.Empty;
                Capital = string.Empty;
                Year = string.Empty;
            }

            public string Name { get; set; }
            public string Code { get; set; }
            public string Capital { get; set; }
            public string Year { get; set; }
        }
    }
}

ASPX Page
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Web.excel._default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <!-- title -->
    <title>Excel Demo</title>

    <!-- jquery -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>

    <!-- bootstrap -->
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

    <!-- custom style -->
    <style>
        body {
            margin: 25px !important;
        }

        .form-group {
            width: 800px;
        }
    </style>
</head>

<body>
    <form id="form1" runat="server">
    <div>
        <p>
            <asp:Button ID="btnDownload" Text="Download Excel File" CssClass="btn btn-default" Width="200px" OnClick="btnDownload_Click" ClientIDMode="Static" runat="server" />
        </p>

        <p>
            <asp:Button ID="btnUpload" Text="Read Excel File" CssClass="btn btn-default" Width="200px" OnClick="btnUpload_Click" ClientIDMode="Static" runat="server" />
        </p>

        <asp:PlaceHolder ID="pnlOutput" Visible="false" runat="server">
            <p>
                <pre><asp:Literal ID="lblOutput" runat="server" /></pre>
            </p>
        </asp:PlaceHolder>
    </div>
    </form>
</body>
</html>


Extensions
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.Xml.Serialization;
using System.IO;
using System.Collections.Specialized;
using System.Web;
using System.ComponentModel;
using System.Xml;
using System.Xml.Linq;
using System.Web.UI;
using System.Text.RegularExpressions;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Linq.Expressions;
using System.Net;
using OfficeOpenXml;

namespace Utilities
{
    public static class Extensions
    {
        private static Dictionary<Type, IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();

        public static IList<PropertyInfo> GetPropertiesForType<T>()
        {
            //variables
            var type = typeof(T);

            //get types
            if (!typeDictionary.ContainsKey(typeof(T)))
            {
                typeDictionary.Add(type, type.GetProperties().ToList());
            }

            //return
            return typeDictionary[type];
        }

        public static T ToObject<T>(this DataRow row) where T : new()
        {
            //variables
            IList<PropertyInfo> properties = GetPropertiesForType<T>();

            //return
            return CreateItemFromRow<T>(row, properties);
        }

        public static IList<T> ToList<T>(this DataTable table) where T : new()
        {
            //variables
            IList<T> result = new List<T>();

            //foreach
            foreach (DataRow row in table.Rows)
            {
                result.Add(row.ToObject<T>());
            }

            //return
            return result;
        }

        private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
        {
            //variables
            T item = new T();

            //foreach
            foreach (var property in properties)
            {
                //make sure a column exists in the table with this property name
                if (row.Table.Columns.Contains(property.Name))
                {
                    //get the value from the current data row
                    object value = row[property.Name];

                    //set property accordingly
                    if (value != null & value != DBNull.Value)
                    {
                        SetProperty<T>(item, property.Name, value);
                    }
                }
            }

            //return
            return item;
        }

        public static string GetProperty<T>(this T obj, string Property)
        {
            //reflection
            PropertyInfo propertyInfo = obj.GetType().GetProperty(Property, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            object property = null;

            //make sure property is valid
            if (propertyInfo != null)
            {
                property = propertyInfo.GetValue(obj, null);
            }

            //return value
            if (property != null)
            {
                return property.ToString();
            }
            else
            {
                return string.Empty;
            }
        }

        public static T SetProperty<T>(this T obj, string Property, object Value)
        {
            //reflection
            PropertyInfo prop = obj.GetType().GetProperty(Property, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

            //trim strings
            if (Value.GetType() == typeof(string))
            {
                Value = Value.ToString().Trim();
            }

            //make sure property is valid
            if (prop != null && prop.CanWrite)
            {
                prop.SetValue(obj, Value, null);
            }

            //return
            return obj;
        }

        public static string Serialize<T>(this T obj)
        {
            // Json.NET (Newtonsoft.Json)
            // https://www.nuget.org/packages/Newtonsoft.Json
            return Newtonsoft.Json.JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented);
        }

        public static T Deserialize<T>(string data) where T : new()
        {
            // Json.NET (Newtonsoft.Json)
            // https://www.nuget.org/packages/Newtonsoft.Json
            return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(data);
        }

        public static DataTable ToDataTable<T>(this IList<T> data)
        {
            // variables
            PropertyDescriptorCollection Properties = TypeDescriptor.GetProperties(typeof(T));
            object[] values = new object[Properties.Count];
            DataTable DT = new DataTable();

            // columns
            foreach (PropertyDescriptor PropertyInfo in Properties)
            {
                // data column
                DataColumn DataColumn = new DataColumn();

                // name
                DataColumn.ColumnName = PropertyInfo.Name;

                // data type
                if (PropertyInfo.PropertyType.Name.Contains("Nullable"))
                {
                    DataColumn.DataType = typeof(String);
                }
                else
                {
                    DataColumn.DataType = PropertyInfo.PropertyType;
                }

                // add to table
                DT.Columns.Add(DataColumn);
            }

            // rows
            foreach (T item in data)
            {
                for (int i = 0; i < values.Length; i++)
                {
                    values[i] = Properties[i].GetValue(item);
                }
                DT.Rows.Add(values);
            }

            // return
            return DT;
        }

        public static bool HasValue(this string Value)
        {
            return !Value.IsBlank();
        }

        public static bool IsBlank(this string Value)
        {
            bool ReturnValue = true;

            if (Value != null)
            {
                ReturnValue = Value.Trim().Length == 0;
            }

            return ReturnValue;
        }

        public static byte[] ToExcel(this DataTable DT, string WorksheetName = "Data", bool AutoFormatDates = false)
        {
            // EPPlus
            // https://www.nuget.org/packages/EPPlus

            // variables
            byte[] Data = null;
            ExcelPackage ExcelPackage = new ExcelPackage();
            ExcelWorksheet ExcelWorksheet = ExcelPackage.Workbook.Worksheets.Add(WorksheetName.HasValue() ? WorksheetName : "Data");

            // load data table with column headings
            ExcelWorksheet.Cells["A1"].LoadFromDataTable(DT, true);

            // auto-format dates
            if (AutoFormatDates)
            {
                var DateColumns = from DataColumn d in DT.Columns where d.DataType == typeof(DateTime) || d.ColumnName.Contains("Date") select d.Ordinal + 1;

                foreach (var Column in DateColumns)
                {
                    ExcelWorksheet.Cells[2, Column, DT.Rows.Count + 1, Column].Style.Numberformat.Format = "mm/dd/yyyy";
                }
            }

            // get data as byte array
            Data = ExcelPackage.GetAsByteArray();

            // cleanup
            ExcelPackage.Dispose();

            // return byte array
            return Data;
        }

        public static DataTable ToDataTable(this Stream Data, bool HasHeader = true)
        {
            // EPPlus
            // https://www.nuget.org/packages/EPPlus

            // variables
            DataTable DT = new DataTable();
            var RowStart = HasHeader ? 2 : 1;

            using (ExcelPackage ExcelPackage = new ExcelPackage())
            {
                // load data from stream
                ExcelPackage.Load(Data);

                // worksheet
                ExcelWorksheet ExcelWorksheet = ExcelPackage.Workbook.Worksheets.First();

                // create column headings
                foreach (var Cell in ExcelWorksheet.Cells[1, 1, 1, ExcelWorksheet.Dimension.End.Column])
                {
                    DT.Columns.Add(HasHeader ? Cell.Text : string.Format("Column {0}", Cell.Start.Column));
                }

                // copy data from each row to the data table
                for (int RowNumber = RowStart; RowNumber <= ExcelWorksheet.Dimension.End.Row; RowNumber++)
                {
                    // variables
                    ExcelRange Row = ExcelWorksheet.Cells[RowNumber, 1, RowNumber, ExcelWorksheet.Dimension.End.Column];
                    DataRow DataRow = DT.NewRow();

                    // copy the data from each cell to the data row
                    foreach (ExcelRangeBase cell in Row)
                    {
                        DataRow[cell.Start.Column - 1] = cell.Text;
                    }

                    // add row to data table
                    DT.Rows.Add(DataRow);
                }
            }

            // return data table
            return DT;
        }
    }
}

ASP.Net C# Extracting parts of URL including domain and sub-domain

Here is a simple example using C# to extract the various parts of a URL.

Code-Behind
        protected void Page_Load(object sender, EventArgs e)
        {
            // debug
            Response.Write(string.Format("{0}: {1}<br />", "Protocol", Request.Protocol()));
            Response.Write(string.Format("{0}: {1}<br />", "Host", Request.Host()));
            Response.Write(string.Format("{0}: {1}<br />", "SubDomains", string.Join(".", Request.SubDomains())));
            Response.Write(string.Format("{0}: {1}<br />", "SubDomain", Request.SubDomain()));
            Response.Write(string.Format("{0}: {1}<br />", "Domain", Request.Domain()));
            Response.Write(string.Format("{0}: {1}<br />", "BaseUrl", Request.BaseUrl()));
            Response.Write(string.Format("{0}: {1}<br />", "RawUrl", Request.RawUrl));
            Response.Write(string.Format("{0}: {1}<br />", "AbsoluteUri", Request.Url.AbsoluteUri));
            Response.Write(string.Format("{0}: {1}<br />", "AbsolutePath", Request.Url.AbsolutePath));
            Response.Write(string.Format("{0}: {1}<br />", "FileName", Path.GetFileName(Request.Url.AbsolutePath)));
            Response.Write(string.Format("{0}: {1}<br />", "VirtualPath", VirtualPathUtility.ToAbsolute("~/")));
            Response.Write(string.Format("{0}: {1}<br />", "UrlHttps", Request.UrlHttps()));
            Response.Write(string.Format("{0}: {1}<br />", "UrlHttp", Request.UrlHttp()));
            Response.Write(string.Format("{0}: {1}<br />", "PageName v1", Request.PageName()));
            Response.Write(string.Format("{0}: {1}<br />", "PageName v2", Request.PageName(IncludeExtension: true)));
        }

Extensions
        // e.g. https
        public static string Protocol(this HttpRequest Request)
        {
            return Request.Url.Scheme;
        }

        // e.g. pavey.azurewebsites.net
        public static string Host(this HttpRequest Request)
        {
            return Request.Url.Host.ToLower();
        }

        // e.g. https://pavey.azurewebsites.net/debug/?x=1&y=2
        public static string UrlHttps(this HttpRequest Request)
        {
            return string.Format("https://{0}{1}", Request.Host(), Request.RawUrl);
        }

        // e.g. http://pavey.azurewebsites.net/debug/?x=1&y=2
        public static string UrlHttp(this HttpRequest Request)
        {
            return string.Format("http://{0}{1}", Request.Host(), Request.RawUrl);
        }

        // e.g. true if value is formatted as a valid IPv4 address
        public static bool IsValidIPAddress(this string Value)
        {
            return Regex.Match(Value, "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b").Success;
        }

        // e.g. true if testing using an IP address instead of a fully qualified host name
        public static bool IsIPAddress(this HttpRequest Request)
        {
            return Request.Host().IsValidIPAddress();
        }

        // e.g. true if testing using localhost
        public static bool IsLocalHost(this HttpRequest Request)
        {
            return Request.Host().ToLower().Equals("localhost");
        }

        // e.g. dev.www
        public static List SubDomains(this HttpRequest Request)
        {
            // variables
            string[] MyArray = Request.Host().Split(".".ToCharArray());
            List MySubDomains = new List();

            // make sure this is not an ip address
            if (Request.IsIPAddress())
            {
                return MySubDomains;
            }

            // make sure we have all the parts necessary
            if (MyArray == null)
            {
                return MySubDomains;
            }

            // last part is the tld (e.g. .com)
            // second to last part is the domain (e.g. mydomain)
            // the remaining parts are the sub-domain(s)
            if (MyArray.Length > 2)
            {
                for (int i = 0; i <= MyArray.Length - 3; i++)
                {
                    MySubDomains.Add(MyArray[i]);
                }
            }

            // return
            return MySubDomains;
        }

        // e.g. www
        public static string SubDomain(this HttpRequest Request)
        {
            if (Request.SubDomains().Count > 0)
            {
                // handle cases where multiple sub-domains (e.g. dev.www)
                return Request.SubDomains().Last();
            }
            else
            {
                // handle cases where no sub-domains
                return string.Empty;
            }
        }

        // e.g. azurewebsites.net
        public static string Domain(this HttpRequest Request)
        {
            // variables
            string[] MyArray = Request.Host().Split(".".ToCharArray());

            // make sure this is not an ip address
            if (Request.IsIPAddress())
            {
                return string.Empty;
            }

            // special case for localhost
            if (Request.IsLocalHost())
            {
                return Request.Host().ToLower();
            }

            // make sure we have all the parts necessary
            if (MyArray == null)
            {
                return string.Empty;
            }

            // make sure we have all the parts necessary
            if (MyArray.Length > 1)
            {
                return string.Format("{0}.{1}", MyArray[MyArray.Length - 2], MyArray[MyArray.Length - 1]);
            }

            // return empty string
            return string.Empty;
        }

        // e.g. https://pavey.azurewebsites.net/
        public static string BaseUrl(this HttpRequest Request)
        {
            // variables
            string Authority = Request.Url.GetLeftPart(UriPartial.Authority).TrimStart('/').TrimEnd('/');
            string ApplicationPath = Request.ApplicationPath.TrimStart('/').TrimEnd('/');

            // add trailing slashes if necessary
            if (Authority.Length > 0)
            {
                Authority += "/";
            }

            if (ApplicationPath.Length > 0)
            {
                ApplicationPath += "/";
            }

            // return
            return string.Format("{0}{1}", Authority, ApplicationPath);
        }

        // e.g. default, default.aspx
        public static string PageName(this HttpRequest Request, bool IncludeExtension = false, bool IncludeQueryString = false)
        {
            // variables
            string AbsolutePath = Request.Url.AbsolutePath;
            string PageName = Path.GetFileName(AbsolutePath);
            string Extension = Path.GetExtension(AbsolutePath);
            string QueryString = Request.QueryString.ToString();

            // remove extension
            if (!IncludeExtension && !IncludeQueryString && PageName.HasValue())
            {
                PageName = PageName.Replace(Extension, string.Empty);
            }

            // include querystring
            if (IncludeQueryString && PageName.HasValue() && QueryString.HasValue())
            {
                PageName = string.Format("{0}?{1}", PageName, QueryString);
            }

            // return
            return PageName;
        }

Click here to see a demo with sample output.

ASP.Net C# Finding Controls in Nested Repeaters on Button Click Event

Here's a simple way to find controls in a nested repeater on a button click event.

Let's start with the ASPX page.
 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Web.nested_repeaters._default" %>  
   
 <!DOCTYPE html>  
   
 <html xmlns="http://www.w3.org/1999/xhtml">  
 <head runat="server">  
   <!-- title -->  
   <title>Repeater Test</title>  
   
   <!-- jquery -->  
   <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>

   <!-- bootstrap -->  
   <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />  
   <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>  
   
   <!-- custom style -->  
   <style>  
     body {  
       margin: 25px !important;  
     }  
   
     .form-group {  
       width: 800px;  
     }  
   </style>  
 </head>  
   
 <body>  
   <form id="form1" runat="server">  
     <asp:Repeater ID="lstCategories" runat="server">  
       <ItemTemplate>  
         <div class="col-sm-12">  
           <h2><%#Eval("Name")%></h2>   
               
           <asp:Repeater ID="lstTasks" DataSource='<%#Eval("Tasks")%>' runat="server">  
             <ItemTemplate>   
               <div class="form-group">   
                 <div class="col-sm-12">  
                   <div class="col-sm-2">  
                     <%#Eval("Name")%>  
                   </div>  
                   <div class="col-sm-10">  
                     <!-- hidden fields -->  
                     <asp:HiddenField ID="lblID" Value='<%#DataBinder.Eval(Container.DataItem, "ID")%>' runat="server" />  
                     <asp:HiddenField ID="lblValue" Value='<%#DataBinder.Eval(Container.DataItem, "Value")%>' runat="server" />  
   
                     <!-- textbox to show current value and to allow updates -->  
                     <asp:TextBox ID="txtValue" Text='<%#DataBinder.Eval(Container.DataItem, "Value")%>' MaxLength="100" CssClass="form-control" runat="server" />  
                   </div>  
                 </div>  
               </div>  
             </ItemTemplate>  
           </asp:Repeater>  
         </div>  
       </ItemTemplate>  
     </asp:Repeater>  
   
     <div class="col-sm-12" style="padding-top: 25px">  
       <asp:Button ID="btnSave" Text="Save Changes" CssClass="btn btn-primary" Style="margin-right: 25px" OnClick="btnSave_Click" ClientIDMode="Static" runat="server" />  
       <asp:Button ID="btnCancel" Text="Cancel" CssClass="btn btn-primary" OnClick="btnCancel_Click" ClientIDMode="Static" runat="server" />  
     </div>  
   
     <!-- debug -->  
     <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" style="padding-top: 25px">  
       <asp:PlaceHolder ID="pnlDebug" Visible="true" runat="server" />  
     </div>  
   </form>  
 </body>  
 </html>  

For this example I'm using lstCategories for the outer repeater and lstTasks for the nested repeater. The nested repeater (lstTasks) defines 3 controls:

- lblID This is the hidden field for the ID to identify your record (e.g. primary key)
- lblValue This is the hidden field representing the original value of the field
- txtValue This is a textbox that defaults to the current value, but can be edited

Then there are 2 buttons.

- btnSave Iterates the repeaters to access the controls
- btnCancel Reloads the page

Then the code-behind looks like this:
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Web;  
 using System.Web.UI;  
 using System.Web.UI.WebControls;  
   
 namespace Web.nested_repeaters  
 {  
   public partial class _default : System.Web.UI.Page  
   {  
     protected void Page_Load(object sender, EventArgs e)  
     {  
       if (!Page.IsPostBack)  
       {  
         BindData();  
       }  
     }  
   
     private void BindData()  
     {  
       lstCategories.DataSource = GetData();  
       lstCategories.DataBind();  
     }  
   
     private List<Category> GetData()  
     {  
       // variables  
       List<Category> Categories = new List<Category>();  
   
       // category 1  
       Category Category1 = new Category() { Name = "General", Tasks = new List<Task>() };  
       Category1.Tasks.Add(new Task() { ID = 1, Name = "Name", Value = "John Wick" });  
       Category1.Tasks.Add(new Task() { ID = 2, Name = "Email", Value = "info@pavey.net" });  
       Category1.Tasks.Add(new Task() { ID = 3, Name = "Phone", Value = "" });  
       Category1.Tasks.Add(new Task() { ID = 4, Name = "Website", Value = "http://www.pavey.me/" });  
       Category1.Tasks.Add(new Task() { ID = 5, Name = "Comments", Value = "" });  
   
       // category 2  
       Category Category2 = new Category() { Name = "Social Media", Tasks = new List<Task>() };  
       Category2.Tasks.Add(new Task() { ID = 6, Name = "Twitter", Value = "https://twitter.com/matthewpavey" });  
       Category2.Tasks.Add(new Task() { ID = 7, Name = "Facebook", Value = "" });  
       Category2.Tasks.Add(new Task() { ID = 8, Name = "LinkedIn ", Value = "" });  
   
       // category 3  
       Category Category3 = new Category() { Name = "Favorite Websites", Tasks = new List<Task>() };  
       Category3.Tasks.Add(new Task() { ID = 9, Name = "Website", Value = "http://www.pavey.me/" });  
       Category3.Tasks.Add(new Task() { ID = 10, Name = "Website", Value = "https://mysafeinfo.com/" });  
       Category3.Tasks.Add(new Task() { ID = 11, Name = "Website", Value = "" });  
       Category3.Tasks.Add(new Task() { ID = 12, Name = "Website", Value = "" });  
       Category3.Tasks.Add(new Task() { ID = 13, Name = "Website", Value = "" });  
   
       // add to list  
       Categories.Add(Category1);  
       Categories.Add(Category2);  
       Categories.Add(Category3);  
   
       // return  
       return Categories;  
     }  
   
     public void Debug(string Value = "")  
     {  
       pnlDebug.Controls.Add(new LiteralControl(string.Format("<div>{0}</div>", Value)));  
     }  
   
     protected void btnSave_Click(object sender, EventArgs e)  
     {  
       // iterate outer repeater (categories)  
       foreach (RepeaterItem Category in lstCategories.Items)  
       {  
         // iterate inner repeater (tasks)  
         foreach (RepeaterItem Task in ((Repeater)(Category.FindControl("lstTasks"))).Items)  
         {  
           // get reference to controls  
           HiddenField lblID = (HiddenField)Task.FindControl("lblID");  
           HiddenField lblValue = (HiddenField)Task.FindControl("lblValue");  
           TextBox txtValue = (TextBox)Task.FindControl("txtValue");  
   
           // extract values  
           int ID = Convert.ToInt32(lblID.Value);  
           string ValueOriginal = lblValue.Value;  
           string ValueNew = txtValue.Text.Trim();  
   
           // debug  
           Debug(string.Format("ID={0}; ValueOriginal={1}; ValueNew={2}", ID, ValueOriginal, ValueNew));  
         }  
       }  
     }  
   
     protected void btnCancel_Click(object sender, EventArgs e)  
     {  
       Response.Redirect("default.aspx");  
       Response.End();  
     }  
   
     private class Category  
     {  
       public string Name { get; set; }  
       public List<Task> Tasks { get; set; }  
     }  
   
     private class Task  
     {  
       public int ID { get; set; }  
       public string Name { get; set; }  
       public string Value { get; set; }  
     }  
   }  
 }  

The btnSave_Click event is really where everything is happening. We're simply doing a foreach around the lstCategories repeater items. Then inside that loop we're iterating the lstTasks repeater items. It's inside this nested repeater (lstTasks) that we can get reference to the controls (lblID, lblValue, txtValue). From that point it's just a matter of casting the controls, extracting the values, and then using the values. In this case we're just outputting the values for debugging. But this is where you could use the ID and compare the original/new values to determine if you need to apply an update to the record, etc.